Skip to content

Commit

Permalink
fix: remove build context and snackbars from podcast model
Browse files Browse the repository at this point in the history
  • Loading branch information
Feichtmeier committed Dec 10, 2024
1 parent 1be2d35 commit 006a481
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 161 deletions.
32 changes: 32 additions & 0 deletions lib/app/view/desktop_home_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import '../../patch_notes/patch_notes_dialog.dart';
import '../../player/player_model.dart';
import '../../player/view/player_view.dart';
import '../../podcasts/download_model.dart';
import '../../podcasts/podcast_model.dart';
import '../../podcasts/podcast_search_state.dart';
import '../../podcasts/view/podcast_snackbar_contents.dart';
import '../../settings/settings_model.dart';
import '../app_model.dart';
import '../connectivity_model.dart';
Expand Down Expand Up @@ -77,6 +80,35 @@ class _DesktopHomePageState extends State<DesktopHomePage> {
},
);

registerStreamHandler(
select: (PodcastModel m) => m.stateStream,
initialValue: null,
handler: (context, newValue, cancel) {
if (newValue.hasData) {
if (newValue.data == PodcastSearchState.done) {
ScaffoldMessenger.of(context).clearSnackBars();
} else {
showSnackBar(
context: context,
content: switch (newValue.data) {
PodcastSearchState.loading =>
const PodcastSearchLoadingSnackBarContent(),
PodcastSearchState.empty =>
const PodcastSearchEmptyFeedSnackBarContent(),
PodcastSearchState.timeout =>
const PodcastSearchTimeoutSnackBarContent(),
_ => const SizedBox.shrink()
},
duration: switch (newValue.data) {
PodcastSearchState.loading => const Duration(seconds: 1000),
_ => const Duration(seconds: 3),
},
);
}
}
},
);

return Stack(
alignment: Alignment.center,
children: [
Expand Down
32 changes: 32 additions & 0 deletions lib/app/view/mobile_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import '../../player/player_model.dart';
import '../../player/view/full_height_player.dart';
import '../../player/view/player_view.dart';
import '../../podcasts/download_model.dart';
import '../../podcasts/podcast_model.dart';
import '../../podcasts/podcast_search_state.dart';
import '../../podcasts/view/podcast_snackbar_contents.dart';
import '../app_model.dart';
import 'mobile_bottom_bar.dart';

Expand All @@ -34,6 +37,35 @@ class MobilePage extends StatelessWidget with WatchItMixin {
},
);

registerStreamHandler(
select: (PodcastModel m) => m.stateStream,
initialValue: null,
handler: (context, newValue, cancel) {
if (newValue.hasData) {
if (newValue.data == PodcastSearchState.done) {
ScaffoldMessenger.of(context).clearSnackBars();
} else {
showSnackBar(
context: context,
content: switch (newValue.data) {
PodcastSearchState.loading =>
const PodcastSearchLoadingSnackBarContent(),
PodcastSearchState.empty =>
const PodcastSearchEmptyFeedSnackBarContent(),
PodcastSearchState.timeout =>
const PodcastSearchTimeoutSnackBarContent(),
_ => const SizedBox.shrink()
},
duration: switch (newValue.data) {
PodcastSearchState.loading => const Duration(seconds: 1000),
_ => const Duration(seconds: 3),
},
);
}
}
},
);

return Scaffold(
resizeToAvoidBottomInset: false,
extendBody: true,
Expand Down
3 changes: 3 additions & 0 deletions lib/library/library_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,9 @@ class LibraryModel extends SafeChangeNotifier implements NavigatorObserver {
printMessageInDebugMode(
'didReplace: ${oldRoute?.settings.name}, newPageId: ${newRoute?.settings.name}',
);
final pageId = newRoute?.settings.name;
if (pageId == null) return;
_setSelectedPageId(pageId);
}

@override
Expand Down
36 changes: 24 additions & 12 deletions lib/player/view/player_title_and_artist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import '../../local_audio/local_audio_model.dart';
import '../../local_audio/view/album_page.dart';
import '../../local_audio/view/artist_page.dart';
import '../../podcasts/podcast_model.dart';
import '../../podcasts/view/podcast_page.dart';
import '../../radio/view/station_page.dart';
import '../../settings/settings_model.dart';
import '../player_model.dart';
Expand Down Expand Up @@ -97,9 +98,7 @@ class PlayerTitleAndArtist extends StatelessWidget with WatchItMixin {
? null
: () => _onArtistTap(
audio: audio,
context: context,
libraryModel: libraryModel,
playerModel: playerModel,
localAudioModel: localAudioModel,
appModel: appModel,
podcastModel: podcastModel,
Expand Down Expand Up @@ -254,9 +253,7 @@ class PlayerTitleAndArtist extends StatelessWidget with WatchItMixin {

void _onArtistTap({
required Audio audio,
required BuildContext context,
required PodcastModel podcastModel,
required PlayerModel playerModel,
required LocalAudioModel localAudioModel,
required LibraryModel libraryModel,
required AppModel appModel,
Expand All @@ -274,14 +271,29 @@ class PlayerTitleAndArtist extends StatelessWidget with WatchItMixin {
_onRadioArtistTap(audio: audio, libraryModel: libraryModel);
return;
case AudioType.podcast:
if (audio.website != null) {
podcastModel.loadPodcast(
context: context,
libraryModel: libraryModel,
feedUrl: audio.website!,
itemImageUrl: audio.albumArtUrl,
genre: audio.genre,
);
if (audio.website != null &&
libraryModel.selectedPageId != audio.website) {
final feedUrl = audio.website!;
if (libraryModel.isPageInLibrary(feedUrl)) {
libraryModel.push(pageId: feedUrl);
} else {
podcastModel.loadPodcast(
feedUrl: feedUrl,
itemImageUrl: audio.albumArtUrl,
genre: audio.genre,
onFind: (podcast) => libraryModel.push(
builder: (_) => PodcastPage(
imageUrl: audio.albumArtUrl ?? podcast.firstOrNull?.imageUrl,
preFetchedEpisodes: podcast,
feedUrl: feedUrl,
title: podcast.firstOrNull?.album ??
podcast.firstOrNull?.title ??
feedUrl,
),
pageId: feedUrl,
),
);
}
}
return;
default:
Expand Down
14 changes: 12 additions & 2 deletions lib/playlists/view/manual_add_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '../../library/library_model.dart';
import '../../local_audio/local_audio_model.dart';
import '../../local_audio/local_audio_view.dart';
import '../../podcasts/podcast_model.dart';
import '../../podcasts/view/podcast_page.dart';

class ManualAddDialog extends StatelessWidget {
const ManualAddDialog({
Expand Down Expand Up @@ -425,9 +426,18 @@ class _AddPodcastContentState extends State<AddPodcastContent> {
? null
: () {
di<PodcastModel>().loadPodcast(
context: context,
feedUrl: _urlController.text,
libraryModel: di<LibraryModel>(),
onFind: (podcast) => di<LibraryModel>().push(
builder: (_) => PodcastPage(
imageUrl: podcast.firstOrNull?.imageUrl,
preFetchedEpisodes: podcast,
feedUrl: _urlController.text,
title: podcast.firstOrNull?.album ??
podcast.firstOrNull?.title ??
_urlController.text,
),
pageId: _urlController.text,
),
);
},
child: Text(
Expand Down
114 changes: 40 additions & 74 deletions lib/podcasts/podcast_model.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:safe_change_notifier/safe_change_notifier.dart';

import '../common/data/audio.dart';
import '../common/view/snackbars.dart';
import '../library/library_model.dart';
import '../player/player_model.dart';
import 'podcast_search_state.dart';
import 'podcast_service.dart';
import 'view/podcast_page.dart';
import 'view/podcast_snackbar_contents.dart';

class PodcastModel extends SafeChangeNotifier {
PodcastModel({
Expand All @@ -18,12 +13,16 @@ class PodcastModel extends SafeChangeNotifier {

final PodcastService _podcastService;

bool _loadingFeed = false;
bool get loadingFeed => _loadingFeed;
void setLoadingFeed(bool value) {
if (_loadingFeed == value) return;
_loadingFeed = value;
final _searchStateController =
StreamController<PodcastSearchState>.broadcast();
Stream<PodcastSearchState> get stateStream => _searchStateController.stream;
PodcastSearchState _lastState = PodcastSearchState.done;
PodcastSearchState get lastState => _lastState;
void _sendState(PodcastSearchState state) {
if (state == _lastState) return;
_lastState = state;
notifyListeners();
_searchStateController.add(state);
}

var _firstUpdateChecked = false;
Expand Down Expand Up @@ -78,77 +77,44 @@ class PodcastModel extends SafeChangeNotifier {
notifyListeners();
}

// This is not optimal since the model uses widgets
// but tolerable since snackbars update over the rest of the UI
Future<void> loadPodcast({
required BuildContext context,
required LibraryModel libraryModel,
required String feedUrl,
String? itemImageUrl,
String? genre,
PlayerModel? playerModel,
required Function(List<Audio> podcast) onFind,
}) async {
if (libraryModel.isPageInLibrary(feedUrl)) {
return libraryModel.push(pageId: feedUrl);
}

showSnackBar(
context: context,
duration: const Duration(seconds: 1000),
content: const PodcastSearchLoadingSnackBarContent(),
);
_sendState(PodcastSearchState.loading);

setLoadingFeed(true);
return _podcastService
.findEpisodes(
feedUrl: feedUrl,
itemImageUrl: itemImageUrl,
genre: genre,
)
feedUrl: feedUrl,
itemImageUrl: itemImageUrl,
genre: genre,
)
.then(
(podcast) async {
if (podcast.isEmpty) {
if (context.mounted) {
showSnackBar(
context: context,
content: const PodcastSearchEmptyFeedSnackBarContent(),
);
}
return;
}

if (playerModel != null) {
playerModel.startPlaylist(listName: feedUrl, audios: podcast);
} else {
libraryModel.push(
builder: (_) => PodcastPage(
imageUrl: itemImageUrl ?? podcast.firstOrNull?.imageUrl,
preFetchedEpisodes: podcast,
feedUrl: feedUrl,
title: podcast.firstOrNull?.album ??
podcast.firstOrNull?.title ??
feedUrl,
),
pageId: feedUrl,
);
}
},
).whenComplete(
() {
setLoadingFeed(false);
if (context.mounted) ScaffoldMessenger.of(context).clearSnackBars();
},
).timeout(
const Duration(seconds: 15),
onTimeout: () {
setLoadingFeed(false);
if (context.mounted) {
showSnackBar(
context: context,
content: const PodcastSearchTimeoutSnackBarContent(),
);
}
},
);
(podcast) async {
if (podcast.isEmpty) {
_sendState(PodcastSearchState.empty);
return;
}

onFind(podcast);
},
)
.whenComplete(
() => _sendState(PodcastSearchState.done),
)
.timeout(
const Duration(seconds: 15),
onTimeout: () {
_sendState(PodcastSearchState.timeout);
},
);
}

@override
Future<void> dispose() async {
super.dispose();
await _searchStateController.close();
}
}
6 changes: 6 additions & 0 deletions lib/podcasts/podcast_search_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
enum PodcastSearchState {
loading,
timeout,
empty,
done;
}
1 change: 1 addition & 0 deletions lib/podcasts/view/podcast_sub_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class PodcastSubButton extends StatelessWidget with WatchItMixin {
: () {
if (subscribed) {
libraryModel.removePodcast(pageId);
libraryModel.pop();
} else if (audios.isNotEmpty) {
libraryModel.addPodcast(pageId, audios);
}
Expand Down
Loading

0 comments on commit 006a481

Please sign in to comment.