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

chore: follow the navigator api instead of creating our own stack #988

Merged
merged 1 commit into from
Oct 29, 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
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