Skip to content

Commit

Permalink
feat: new mobile scaffold (#1034)
Browse files Browse the repository at this point in the history
  • Loading branch information
Feichtmeier authored Nov 20, 2024
1 parent 008ce6d commit d19308e
Show file tree
Hide file tree
Showing 35 changed files with 735 additions and 418 deletions.
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

0 comments on commit d19308e

Please sign in to comment.