Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new mobile scaffold #1034

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 95 additions & 11 deletions lib/app/view/app.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import 'dart:ui';

import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:phoenix_theme/phoenix_theme.dart' hide ColorX;
import 'package:system_theme/system_theme.dart';
import 'package:watch_it/watch_it.dart';
import 'package:yaru/yaru.dart';

import '../../common/view/theme.dart';
import '../../constants.dart';
import '../../external_path/external_path_service.dart';
import '../../l10n/l10n.dart';
import '../../library/library_model.dart';
import '../../radio/radio_model.dart';
import '../../settings/settings_model.dart';
import '../connectivity_model.dart';
import 'scaffold.dart';
import 'desktop_scaffold.dart';
import 'master_items.dart';
import 'splash_screen.dart';

class YaruMusicPodApp extends StatelessWidget {
Expand All @@ -22,7 +25,7 @@ class YaruMusicPodApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return YaruTheme(
builder: (context, yaru, child) => _MusicPodApp(
builder: (context, yaru, child) => _DesktopMusicPodApp(
highContrastTheme: yaruHighContrastLight,
highContrastDarkTheme: yaruHighContrastDark,
lightTheme: yaruLightWithTweaks(yaru),
Expand All @@ -38,15 +41,16 @@ class MaterialMusicPodApp extends StatelessWidget {
@override
Widget build(BuildContext context) => SystemThemeBuilder(
builder: (context, accent) {
return _MusicPodApp(
accent: accent.accent,
);
return isMobile
? _MobileMusicPodApp(accent: accent.accent)
: _DesktopMusicPodApp(accent: accent.accent);
},
);
}

class _MusicPodApp extends StatefulWidget with WatchItStatefulWidgetMixin {
const _MusicPodApp({
class _DesktopMusicPodApp extends StatefulWidget
with WatchItStatefulWidgetMixin {
const _DesktopMusicPodApp({
this.lightTheme,
this.darkTheme,
this.accent,
Expand All @@ -61,10 +65,10 @@ class _MusicPodApp extends StatefulWidget with WatchItStatefulWidgetMixin {
final Color? accent;

@override
State<_MusicPodApp> createState() => _MusicPodAppState();
State<_DesktopMusicPodApp> createState() => _DesktopMusicPodAppState();
}

class _MusicPodAppState extends State<_MusicPodApp> {
class _DesktopMusicPodAppState extends State<_DesktopMusicPodApp> {
late Future<bool> _initFuture;

@override
Expand Down Expand Up @@ -95,12 +99,12 @@ class _MusicPodAppState extends State<_MusicPodApp> {
darkTheme: widget.darkTheme ?? phoenix.darkTheme,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: supportedLocales,
onGenerateTitle: (context) => 'MusicPod',
onGenerateTitle: (context) => kAppTitle,
home: FutureBuilder(
future: _initFuture,
builder: (context, snapshot) {
return snapshot.data == true
? const MusicPodScaffold()
? const DesktopScaffold()
: const SplashScreen();
},
),
Expand All @@ -116,3 +120,83 @@ class _MusicPodAppState extends State<_MusicPodApp> {
);
}
}

class _MobileMusicPodApp extends StatefulWidget
with WatchItStatefulWidgetMixin {
const _MobileMusicPodApp({this.accent});

final Color? accent;

@override
State<_MobileMusicPodApp> createState() => _MobileMusicPodAppState();
}

class _MobileMusicPodAppState extends State<_MobileMusicPodApp> {
late Future<bool> _initFuture;

@override
void initState() {
super.initState();
_initFuture = _init();
}

Future<bool> _init() async {
await di<ConnectivityModel>().init();
await di<LibraryModel>().init();
await di<RadioModel>().init();
if (!mounted) return false;
di<ExternalPathService>().init();
return true;
}

@override
Widget build(BuildContext context) {
final themeIndex = watchPropertyValue((SettingsModel m) => m.themeIndex);
final phoenix = phoenixTheme(color: widget.accent ?? Colors.greenAccent);

final libraryModel = watchIt<LibraryModel>();
final masterItems = createMasterItems(libraryModel: libraryModel);

return MaterialApp(
navigatorKey: libraryModel.masterNavigatorKey,
navigatorObservers: [libraryModel],
initialRoute:
isMobile ? (libraryModel.selectedPageId ?? kSearchPageId) : null,
onGenerateRoute: (settings) {
final page = (masterItems.firstWhereOrNull(
(e) => e.pageId == settings.name,
) ??
masterItems.elementAt(0))
.pageBuilder(context);

return PageRouteBuilder(
settings: settings,
pageBuilder: (_, __, ___) => FutureBuilder(
future: _initFuture,
builder: (context, snapshot) {
return snapshot.data == true ? page : const SplashScreen();
},
),
transitionsBuilder: (_, a, __, c) =>
FadeTransition(opacity: a, child: c),
);
},
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.values[themeIndex],
theme: phoenix.lightTheme,
darkTheme: phoenix.darkTheme,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: supportedLocales,
onGenerateTitle: (context) => kAppTitle,
scrollBehavior: const MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown,
PointerDeviceKind.trackpad,
},
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ import '../app_model.dart';
import '../connectivity_model.dart';
import 'master_detail_page.dart';

class MusicPodScaffold extends StatefulWidget with WatchItStatefulWidgetMixin {
const MusicPodScaffold({super.key});
class DesktopScaffold extends StatefulWidget with WatchItStatefulWidgetMixin {
const DesktopScaffold({super.key});

@override
State<MusicPodScaffold> createState() => _MusicPodScaffoldState();
State<DesktopScaffold> createState() => _DesktopScaffoldState();
}

class _MusicPodScaffoldState extends State<MusicPodScaffold> {
class _DesktopScaffoldState extends State<DesktopScaffold> {
@override
void initState() {
super.initState();
Expand Down
165 changes: 4 additions & 161 deletions lib/app/view/master_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,16 @@ import 'package:flutter/material.dart';
import 'package:watch_it/watch_it.dart';
import 'package:yaru/yaru.dart';

import '../../app_config.dart';
import '../../common/data/audio.dart';
import '../../common/view/back_gesture.dart';
import '../../common/view/global_keys.dart';
import '../../common/view/header_bar.dart';
import '../../common/view/icons.dart';
import '../../common/view/side_bar_fall_back_image.dart';
import '../../common/view/theme.dart';
import '../../constants.dart';
import '../../extensions/build_context_x.dart';
import '../../l10n/l10n.dart';
import '../../library/library_model.dart';
import '../../local_audio/view/album_page.dart';
import '../../local_audio/view/local_audio_page.dart';
import '../../playlists/view/liked_audio_page.dart';
import '../../playlists/view/manual_add_dialog.dart';
import '../../playlists/view/playlist_page.dart';
import '../../podcasts/view/podcast_page.dart';
import '../../podcasts/view/podcast_page_side_bar_icon.dart';
import '../../podcasts/view/podcast_page_title.dart';
import '../../podcasts/view/podcasts_page.dart';
import '../../radio/view/radio_page.dart';
import '../../radio/view/station_page.dart';
import '../../radio/view/station_page_icon.dart';
import '../../search/view/search_page.dart';
import '../../settings/view/settings_tile.dart';
import 'main_page_icon.dart';
import '../../settings/view/settings_action.dart';
import 'master_items.dart';
import 'master_tile.dart';

class MasterDetailPage extends StatelessWidget with WatchItMixin {
Expand Down Expand Up @@ -92,8 +75,7 @@ class MasterDetailPage extends StatelessWidget with WatchItMixin {

return PageRouteBuilder(
settings: settings,
pageBuilder: (_, __, ___) =>
useCustomBackGestures ? page : BackGesture(child: page),
pageBuilder: (_, __, ___) => BackGesture(child: page),
transitionsBuilder: (_, a, __, c) =>
FadeTransition(opacity: a, child: c),
);
Expand Down Expand Up @@ -171,148 +153,9 @@ class MasterPanel extends StatelessWidget {
),
),
),
const SettingsTile(),
const SettingsButton.tile(),
],
),
);
}
}

class MasterItem {
MasterItem({
required this.titleBuilder,
this.subtitleBuilder,
required this.pageBuilder,
this.iconBuilder,
required this.pageId,
});

final WidgetBuilder titleBuilder;
final WidgetBuilder? subtitleBuilder;
final WidgetBuilder pageBuilder;
final Widget Function(bool selected)? iconBuilder;
final String pageId;
}

List<MasterItem> createMasterItems({required LibraryModel libraryModel}) {
return [
MasterItem(
titleBuilder: (context) => Text(context.l10n.search),
pageBuilder: (_) => const SearchPage(),
iconBuilder: (_) => Icon(Iconz.search),
pageId: kSearchPageId,
),
MasterItem(
titleBuilder: (context) => Text(context.l10n.local),
pageBuilder: (_) => const LocalAudioPage(),
iconBuilder: (selected) => MainPageIcon(
audioType: AudioType.local,
selected: selected,
),
pageId: kLocalAudioPageId,
),
MasterItem(
titleBuilder: (context) => Text(context.l10n.radio),
pageBuilder: (_) => const RadioPage(),
iconBuilder: (selected) => MainPageIcon(
audioType: AudioType.radio,
selected: selected,
),
pageId: kRadioPageId,
),
MasterItem(
titleBuilder: (context) => Text(context.l10n.podcasts),
pageBuilder: (_) => const PodcastsPage(),
iconBuilder: (selected) => MainPageIcon(
audioType: AudioType.podcast,
selected: selected,
),
pageId: kPodcastsPageId,
),
MasterItem(
iconBuilder: (selected) => Icon(Iconz.plus),
titleBuilder: (context) => Text(context.l10n.add),
pageBuilder: (_) => const SizedBox.shrink(),
pageId: kNewPlaylistPageId,
),
MasterItem(
titleBuilder: (context) => Text(context.l10n.likedSongs),
pageId: kLikedAudiosPageId,
pageBuilder: (_) => const LikedAudioPage(),
subtitleBuilder: (context) => Text(context.l10n.playlist),
iconBuilder: (selected) => LikedAudioPageIcon(selected: selected),
),
for (final playlist in libraryModel.playlists.entries)
MasterItem(
titleBuilder: (context) => Text(playlist.key),
subtitleBuilder: (context) => Text(context.l10n.playlist),
pageId: playlist.key,
pageBuilder: (_) => PlaylistPage(pageId: playlist.key),
iconBuilder: (selected) => SideBarFallBackImage(
color: getAlphabetColor(playlist.key),
child: Icon(
Iconz.playlist,
),
),
),
for (final podcast in libraryModel.podcasts.entries)
MasterItem(
titleBuilder: (_) => PodcastPageTitle(
feedUrl: podcast.key,
title: podcast.value.firstOrNull?.album ??
podcast.value.firstOrNull?.title ??
podcast.value.firstOrNull.toString(),
),
subtitleBuilder: (context) => Text(
podcast.value.firstOrNull?.artist ?? context.l10n.podcast,
),
pageId: podcast.key,
pageBuilder: (_) => PodcastPage(
feedUrl: podcast.key,
title: podcast.value.firstOrNull?.album ??
podcast.value.firstOrNull?.title ??
podcast.value.firstOrNull.toString(),
imageUrl: podcast.value.firstOrNull?.albumArtUrl ??
podcast.value.firstOrNull?.imageUrl,
),
iconBuilder: (selected) => PodcastPageSideBarIcon(
imageUrl: podcast.value.firstOrNull?.albumArtUrl ??
podcast.value.firstOrNull?.imageUrl,
),
),
for (final album in libraryModel.pinnedAlbums.entries)
MasterItem(
titleBuilder: (context) => Text(
album.value.firstOrNull?.album ?? album.key,
),
subtitleBuilder: (context) =>
Text(album.value.firstOrNull?.artist ?? context.l10n.album),
pageId: album.key,
pageBuilder: (_) => AlbumPage(
album: album.value,
id: album.key,
),
iconBuilder: (selected) => AlbumPageSideBarIcon(
audio: album.value.firstOrNull,
),
),
for (final station in libraryModel.starredStations.entries
.where((e) => e.value.isNotEmpty))
MasterItem(
titleBuilder: (context) =>
Text(station.value.first.title ?? station.key),
subtitleBuilder: (context) {
return Text(context.l10n.station);
},
pageId: station.key,
pageBuilder: (_) => StationPage(
station: station.value.first,
),
iconBuilder: (selected) => StationPageIcon(
imageUrl: station.value.first.imageUrl,
fallBackColor: getAlphabetColor(station.value.first.title ?? 'a'),
selected: selected,
),
),
];
}
Loading