Skip to content

Commit

Permalink
chore: follow the navigator api instead of creating our own stack (#988)
Browse files Browse the repository at this point in the history
  • Loading branch information
Feichtmeier authored Oct 29, 2024
1 parent f13c134 commit 09d6582
Show file tree
Hide file tree
Showing 21 changed files with 145 additions and 115 deletions.
40 changes: 19 additions & 21 deletions lib/app/view/master_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,25 @@ class MasterDetailPage extends StatelessWidget with WatchItMixin {
),
if (context.showMasterPanel) const VerticalDivider(),
Expanded(
child: BackGesture(
child: Navigator(
// ignore: deprecated_member_use
onPopPage: (route, result) => route.didPop(result),
key: masterNavigator,
onGenerateRoute: (settings) {
final page = (masterItems.firstWhereOrNull(
(e) =>
e.pageId == settings.name ||
e.pageId == libraryModel.selectedPageId,
) ??
masterItems.elementAt(0))
.pageBuilder(context);
child: Navigator(
initialRoute: libraryModel.selectedPageId ?? kSearchPageId,
onDidRemovePage: (page) {},
key: libraryModel.masterNavigatorKey,
observers: [libraryModel],
onGenerateRoute: (settings) {
final page = (masterItems.firstWhereOrNull(
(e) => e.pageId == settings.name,
) ??
masterItems.elementAt(0))
.pageBuilder(context);

return PageRouteBuilder(
pageBuilder: (_, __, ___) => page,
transitionsBuilder: (_, a, __, c) =>
FadeTransition(opacity: a, child: c),
);
},
),
return PageRouteBuilder(
settings: settings,
pageBuilder: (_, __, ___) => BackGesture(child: page),
transitionsBuilder: (_, a, __, c) =>
FadeTransition(opacity: a, child: c),
);
},
),
),
],
Expand Down Expand Up @@ -146,7 +144,7 @@ class MasterPanel extends StatelessWidget {
},
);
} else {
libraryModel.pushNamed(pageId: item.pageId);
libraryModel.push(pageId: item.pageId);
}

if (!context.showMasterPanel) {
Expand Down
1 change: 0 additions & 1 deletion lib/common/view/global_keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ final GlobalKey<NavigatorState> manualAddNavigatorKey =
GlobalKey<NavigatorState>();

final GlobalKey<ScaffoldState> masterScaffoldKey = GlobalKey();
final GlobalKey<NavigatorState> masterNavigator = GlobalKey<NavigatorState>();
2 changes: 1 addition & 1 deletion lib/common/view/sliver_audio_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class SliverAudioPage extends StatelessWidget {
padding: appBarSingleActionSpacing,
child: SearchButton(
onPressed: () {
di<LibraryModel>().pushNamed(pageId: kSearchPageId);
di<LibraryModel>().push(pageId: kSearchPageId);
final searchModel = di<SearchModel>();
if (searchModel.audioType != AudioType.local) {
searchModel
Expand Down
139 changes: 75 additions & 64 deletions lib/library/library_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ import 'package:flutter/material.dart';
import 'package:safe_change_notifier/safe_change_notifier.dart';

import '../common/data/audio.dart';
import '../common/view/global_keys.dart';
import '../common/logging.dart';
import '../constants.dart';
import 'library_service.dart';

class LibraryModel extends SafeChangeNotifier {
class LibraryModel extends SafeChangeNotifier implements NavigatorObserver {
LibraryModel(this._service);

final LibraryService _service;
StreamSubscription<bool>? _propertiesChangedSub;

Future<bool> init() async {
await _service.init();
_pageIdStack.add(_service.selectedPageId ?? kSearchPageId);

_propertiesChangedSub ??=
_service.propertiesChanged.listen((_) => notifyListeners());
Expand Down Expand Up @@ -110,7 +109,7 @@ class LibraryModel extends SafeChangeNotifier {
playlists.entries.elementAt(index).value.toList();
List<Audio>? getPlaylistById(String id) => _service.playlists[id];

bool isPlaylistSaved(String? name) => playlists.containsKey(name);
bool isPlaylistSaved(String? id) => _service.isPlaylistSaved(id);

Future<void> addPlaylist(String name, List<Audio> audios) async =>
_service.addPlaylist(name, audios);
Expand Down Expand Up @@ -208,83 +207,95 @@ class LibraryModel extends SafeChangeNotifier {
pop();
}

final List<String> _pageIdStack = [];
String? get selectedPageId => _pageIdStack.lastOrNull;
Future<void> pushNamed({required String pageId, bool replace = false}) async {
if (pageId == _pageIdStack.lastOrNull) return;
_putOnStack(pageId: pageId, replace: replace);
if (replace) {
await masterNavigator.currentState?.pushReplacementNamed(pageId);
} else {
await masterNavigator.currentState?.pushNamed(pageId);
}
}
/// Navigation inside the Library
void _putOnStack({
required String pageId,
bool replace = false,
}) {
if (replace) {
_pageIdStack.last = pageId;
} else {
_pageIdStack.add(pageId);
}

if (isPageInLibrary(pageId)) {
_service.setSelectedPageId(pageId);
}
notifyListeners();
}

bool get canPop => _pageIdStack.length > 1;
String? get selectedPageId => _service.selectedPageId;
void _setSelectedPageId(String pageId) => _service.setSelectedPageId(pageId);

Future<void> push({
required Widget Function(BuildContext) builder,
required String pageId,
Widget Function(BuildContext)? builder,
bool maintainState = false,
bool replace = false,
}) async {
if (isPageInLibrary(pageId)) {
await pushNamed(pageId: pageId, replace: replace);
} else {
_putOnStack(pageId: pageId, replace: replace);
final inLibrary = isPageInLibrary(pageId);
assert(inLibrary || builder != null);
if (inLibrary) {
await _masterNavigatorKey.currentState?.pushNamed(pageId);
} else if (builder != null) {
final materialPageRoute = MaterialPageRoute(
builder: builder,
maintainState: maintainState,
settings: RouteSettings(
name: pageId,
),
);
if (replace) {
await masterNavigator.currentState?.pushReplacement(
materialPageRoute,
);
} else {
await masterNavigator.currentState?.push(
materialPageRoute,
);
}
await _masterNavigatorKey.currentState?.push(
materialPageRoute,
);
}
}

void pop({bool popStack = true}) {
if (_pageIdStack.length > 1 && popStack) {
_pageIdStack.removeLast();
void pop() => _masterNavigatorKey.currentState?.maybePop();

notifyListeners();
}
masterNavigator.currentState?.maybePop();
bool get canPop => _masterNavigatorKey.currentState?.canPop() == true;

bool isPageInLibrary(String? pageId) => _service.isPageInLibrary(pageId);

@override
void didPop(Route route, Route? previousRoute) {
final pageId = previousRoute?.settings.name;

printMessageInDebugMode(
'didPop: ${route.settings.name}, previousPageId: ${previousRoute?.settings.name}',
);
if (pageId == null) return;
_setSelectedPageId(pageId);
}

bool isPageInLibrary(String? pageId) =>
pageId != null &&
(pageId == kSearchPageId ||
pageId == kLikedAudiosPageId ||
pageId == kLocalAudioPageId ||
pageId == kPodcastsPageId ||
pageId == kRadioPageId ||
isPinnedAlbum(pageId) ||
isStarredStation(pageId) ||
isPlaylistSaved(pageId) ||
isPodcastSubscribed(pageId));
@override
void didPush(Route route, Route? previousRoute) {
final pageId = route.settings.name;
printMessageInDebugMode(
'didPush: $pageId, previousPageId: ${previousRoute?.settings.name}',
);
if (pageId == null) return;
_setSelectedPageId(pageId);
}

@override
void didRemove(Route route, Route? previousRoute) {
final pageId = route.settings.name;
printMessageInDebugMode(
'didRemove: $pageId, previousPageId: ${previousRoute?.settings.name}',
);
if (pageId == null) return;
_setSelectedPageId(pageId);
}

@override
void didReplace({Route? newRoute, Route? oldRoute}) {
printMessageInDebugMode(
'didReplace: ${oldRoute?.settings.name}, newPageId: ${newRoute?.settings.name}',
);
}

@override
void didStartUserGesture(Route route, Route? previousRoute) {
printMessageInDebugMode(
'didStartUserGesture: ${route.settings.name}, previousPageId: ${previousRoute?.settings.name}',
);
}

@override
void didStopUserGesture() {
printMessageInDebugMode('didStopUserGesture');
}

// Note: Navigator.initState ensures assert(observer.navigator == null);
// Afterwards the Navigator itself!!! sets the navigator of its observers...
@override
NavigatorState? get navigator => null;
final GlobalKey<NavigatorState> _masterNavigatorKey =
GlobalKey<NavigatorState>();
GlobalKey<NavigatorState> get masterNavigatorKey => _masterNavigatorKey;
}
38 changes: 30 additions & 8 deletions lib/library/library_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ class LibraryService {

Map<String, List<Audio>> _playlists = {};
Map<String, List<Audio>> get playlists => _playlists;
bool isPlaylistSaved(String? id) =>
id == null ? false : _playlists.containsKey(id);

Future<void> addPlaylist(String id, List<Audio> audios) async {
if (!_playlists.containsKey(id)) {
Expand Down Expand Up @@ -371,11 +373,12 @@ class LibraryService {
}

Map<String, List<Audio>> _podcasts = {};
bool isPodcastSubscribed(String feedUrl) => _podcasts.containsKey(feedUrl);
Map<String, List<Audio>> get podcasts => _podcasts;
int get podcastsLength => _podcasts.length;

void addPodcast(String feedUrl, List<Audio> audios) {
if (_podcasts.containsKey(feedUrl)) return;
if (isPodcastSubscribed(feedUrl)) return;
_podcasts.putIfAbsent(feedUrl, () => audios);
writeAudioMap(_podcasts, kPodcastsFileName)
.then((_) => _propertiesChangedController.add(true));
Expand Down Expand Up @@ -442,13 +445,13 @@ class LibraryService {
.then((_) => _propertiesChangedController.add(true));
}

void removePodcast(String name) {
if (!_podcasts.containsKey(name)) return;
_podcasts.remove(name);
void removePodcast(String feedUrl) {
if (!isPodcastSubscribed(feedUrl)) return;
_podcasts.remove(feedUrl);
writeAudioMap(_podcasts, kPodcastsFileName)
.then((_) => _propertiesChangedController.add(true))
.then((_) => removePodcastUpdate(name))
.then((_) => _removeFeedWithDownload(name));
.then((_) => removePodcastUpdate(feedUrl))
.then((_) => _removeFeedWithDownload(feedUrl));
}

//
Expand Down Expand Up @@ -503,11 +506,30 @@ class LibraryService {
}

String? get selectedPageId => _sharedPreferences.getString(kSelectedPageId);
Future<void> setSelectedPageId(String value) {
return _sharedPreferences.setString(kSelectedPageId, value);
Future<void> setSelectedPageId(String value) async {
final success = await _sharedPreferences.setString(kSelectedPageId, value);
if (success) {
_propertiesChangedController.add(true);
}
}

Future<void> dispose() async {
await _propertiesChangedController.close();
}

bool isPageInLibrary(String? pageId) =>
pageId != null &&
(_mainPages.contains(pageId) ||
isPinnedAlbum(pageId) ||
isStarredStation(pageId) ||
isPlaylistSaved(pageId) ||
isPodcastSubscribed(pageId));

final _mainPages = [
kSearchPageId,
kLikedAudiosPageId,
kLocalAudioPageId,
kPodcastsPageId,
kRadioPageId,
];
}
1 change: 1 addition & 0 deletions lib/local_audio/view/album_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class AlbumPage extends StatelessWidget {
pageTitle: album.firstWhereOrNull((e) => e.album != null)?.album,
pageSubTitle: album.firstWhereOrNull((e) => e.artist != null)?.artist,
onPageSubTitleTab: onArtistTap,
onPageLabelTab: onArtistTap,
controlPanel: AlbumPageControlButton(album: album, id: id),
);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/local_audio/view/artist_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class ArtistPage extends StatelessWidget with WatchItMixin {
padding: appBarSingleActionSpacing,
child: SearchButton(
onPressed: () {
di<LibraryModel>().pushNamed(pageId: kSearchPageId);
di<LibraryModel>().push(pageId: kSearchPageId);
final searchmodel = di<SearchModel>();
searchmodel
..setAudioType(AudioType.local)
Expand Down
2 changes: 1 addition & 1 deletion lib/local_audio/view/genre_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class _GenrePageState extends State<GenrePage> {
IconButton(
tooltip: context.l10n.searchForRadioStationsWithGenreName,
onPressed: () => radioModel.init().then((value) {
di<LibraryModel>().pushNamed(pageId: kSearchPageId);
di<LibraryModel>().push(pageId: kSearchPageId);
di<SearchModel>()
..setTag(
Tag(name: widget.genre.toLowerCase(), stationCount: 1),
Expand Down
2 changes: 1 addition & 1 deletion lib/local_audio/view/local_audio_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class _LocalAudioPageState extends State<LocalAudioPage> {
child: SearchButton(
active: false,
onPressed: () {
di<LibraryModel>().pushNamed(pageId: kSearchPageId);
di<LibraryModel>().push(pageId: kSearchPageId);
final searchmodel = di<SearchModel>();
searchmodel
..setAudioType(AudioType.local)
Expand Down
2 changes: 1 addition & 1 deletion lib/playlists/view/add_to_playlist_snack_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ ScaffoldFeatureController<SnackBar, SnackBarClosedReason>
if (appModel.fullWindowMode == true) {
appModel.setFullWindowMode(false);
}
libraryModel.pushNamed(pageId: id);
libraryModel.push(pageId: id);
},
label: context.l10n.open,
),
Expand Down
4 changes: 2 additions & 2 deletions lib/playlists/view/manual_add_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ class _PlaylistContentState extends State<PlaylistContent> {
di<LocalAudioModel>().localAudioindex =
LocalAudioView.playlists.index;
widget.libraryModel
..pushNamed(pageId: kLocalAudioPageId)
..push(pageId: kLocalAudioPageId)
..updatePlaylistName(
widget.playlistName!,
_controller.text,
Expand Down Expand Up @@ -253,7 +253,7 @@ class _PlaylistContentState extends State<PlaylistContent> {
const Duration(milliseconds: 300),
);
await widget.libraryModel
.pushNamed(pageId: _controller.text);
.push(pageId: _controller.text);
});
},
child: Text(
Expand Down
Loading

0 comments on commit 09d6582

Please sign in to comment.