Skip to content

Commit 93fd7ab

Browse files
authored
feat: add timer and update button to podcasts (#846)
1 parent fe478ff commit 93fd7ab

16 files changed

+1159
-239
lines changed

lib/common/view/icons.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ class Iconz {
202202
IconData get replay => yaruStyled
203203
? YaruIcons.history
204204
: appleStyled
205-
? CupertinoIcons.restart
205+
? CupertinoIcons.clock
206206
: Icons.history;
207207
IconData get speakerLowFilled => yaruStyled
208208
? YaruIcons.speaker_low_filled
@@ -425,6 +425,11 @@ class Iconz {
425425
: appleStyled
426426
? CupertinoIcons.plus_app
427427
: Icons.queue;
428+
IconData get sleep => yaruStyled
429+
? YaruIcons.clear_night
430+
: appleStyled
431+
? CupertinoIcons.moon
432+
: Icons.mode_night;
428433

429434
Widget getAnimatedStar(bool isStarred, [Color? color]) {
430435
if (yaruStyled) {

lib/l10n/app_de.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,5 +327,7 @@
327327
"radioTagDisclaimerSubTitle": "Manchmal senden Stationen Tags, die keinem Musikgenre entsprechen. MusicPod ist nicht für den Inhalt verantwortlich!",
328328
"podcastFeedLoadingTimeout": "Das Laden des Podcast-Feeds dauert länger als gewöhnlich....",
329329
"restartEpisode": "Episode von Anfang spielen",
330-
"restartAllEpisodes": "Alle Episoden von Anfang spielen"
330+
"restartAllEpisodes": "Alle Episoden von Anfang spielen",
331+
"checkForUpdates": "Auf Aktualisierung überprüfen",
332+
"playbackWillStopIn": "Wiedergabe wird gestoppt in: "
331333
}

lib/l10n/app_en.arb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,5 +328,7 @@
328328
"podcastFeedLoadingTimeout": "Loading the podcast feed takes longer than usual...",
329329
"gitHubClientConnectError": "Could not load online version from GitHub.",
330330
"replayEpisode": "Replay episode",
331-
"replayAllEpisodes": "Replay all episodes"
331+
"replayAllEpisodes": "Replay all episodes",
332+
"checkForUpdates": "Check for updates",
333+
"playbackWillStopIn": "Playback will stop in: "
332334
}

lib/main.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Future<void> main(List<String> args) async {
101101
PodcastService(
102102
notificationsService: notificationsService,
103103
settingsService: settingsService,
104+
libraryService: libraryService,
104105
),
105106
);
106107

@@ -170,10 +171,7 @@ Future<void> main(List<String> args) async {
170171
dispose: (s) => s.dispose(),
171172
);
172173
di.registerSingleton<PodcastModel>(
173-
PodcastModel(
174-
libraryService: libraryService,
175-
podcastService: di.get<PodcastService>(),
176-
),
174+
PodcastModel(podcastService: di.get<PodcastService>()),
177175
dispose: (s) => s.dispose(),
178176
);
179177
di.registerSingleton<RadioModel>(

lib/player/player_model.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ class PlayerModel extends SafeChangeNotifier {
184184
.replaceAll(')', '');
185185
}
186186

187+
void setTimer(Duration duration) => _service.setTimer(duration);
188+
187189
@override
188190
Future<void> dispose() async {
189191
await _queueNameChangedSub?.cancel();

lib/player/player_service.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,11 @@ class PlayerService {
765765
_radioHistoryController.add(true);
766766
}
767767

768+
Timer? _timer;
769+
void setTimer(Duration duration) {
770+
_timer = Timer(duration, () => pause());
771+
}
772+
768773
Future<void> dispose() async {
769774
await _writePlayerState();
770775
await _smtcSub?.cancel();
@@ -791,7 +796,7 @@ class PlayerService {
791796
await _rateController.close();
792797
await _radioHistoryController.close();
793798
await _bufferSub?.cancel();
794-
799+
_timer?.cancel();
795800
await _player.dispose();
796801
}
797802

lib/podcasts/podcast_model.dart

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@ import 'dart:async';
22

33
import 'package:safe_change_notifier/safe_change_notifier.dart';
44

5-
import '../library/library_service.dart';
5+
import '../common/data/audio.dart';
66
import 'podcast_service.dart';
77

88
class PodcastModel extends SafeChangeNotifier {
99
PodcastModel({
1010
required PodcastService podcastService,
11-
required LibraryService libraryService,
12-
}) : _podcastService = podcastService,
13-
_libraryService = libraryService;
11+
}) : _podcastService = podcastService;
1412

1513
final PodcastService _podcastService;
16-
final LibraryService _libraryService;
1714

1815
bool _loadingFeed = false;
1916
bool get loadingFeed => _loadingFeed;
@@ -27,24 +24,29 @@ class PodcastModel extends SafeChangeNotifier {
2724
Future<void> init({
2825
required String updateMessage,
2926
bool forceInit = false,
27+
Function({required String message})? notify,
3028
}) async {
3129
await _podcastService.init(forceInit: forceInit);
3230

3331
if (_firstUpdateChecked == false) {
34-
update(updateMessage);
32+
update(updateMessage: updateMessage);
3533
}
3634
_firstUpdateChecked = true;
3735

3836
notifyListeners();
3937
}
4038

41-
void update(String updateMessage) {
39+
void update({
40+
required String updateMessage,
41+
Map<String, List<Audio>>? oldPodcasts,
42+
Function({required String message})? notify,
43+
}) {
4244
_setCheckingForUpdates(true);
4345
_podcastService
4446
.updatePodcasts(
45-
oldPodcasts: _libraryService.podcasts,
46-
updatePodcast: _libraryService.updatePodcast,
47+
oldPodcasts: oldPodcasts,
4748
updateMessage: updateMessage,
49+
notify: notify,
4850
)
4951
.then((_) => _setCheckingForUpdates(false));
5052
}

lib/podcasts/podcast_service.dart

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@ import '../common/data/audio.dart';
66
import '../common/data/podcast_genre.dart';
77
import '../common/view/audio_filter.dart';
88
import '../common/view/languages.dart';
9+
import '../library/library_service.dart';
910
import '../notifications/notifications_service.dart';
1011
import '../settings/settings_service.dart';
1112
import 'podcast_utils.dart';
1213

1314
class PodcastService {
1415
final NotificationsService _notificationsService;
1516
final SettingsService _settingsService;
17+
final LibraryService _libraryService;
1618
PodcastService({
1719
required NotificationsService notificationsService,
1820
required SettingsService settingsService,
21+
required LibraryService libraryService,
1922
}) : _notificationsService = notificationsService,
20-
_settingsService = settingsService;
23+
_settingsService = settingsService,
24+
_libraryService = libraryService;
2125

2226
SearchResult? _searchResult;
2327
Search? _search;
@@ -80,11 +84,11 @@ class PodcastService {
8084
}
8185

8286
Future<void> updatePodcasts({
83-
required Map<String, List<Audio>> oldPodcasts,
84-
required void Function(String name, List<Audio> audios) updatePodcast,
87+
Map<String, List<Audio>>? oldPodcasts,
8588
required String updateMessage,
89+
Function({required String message})? notify,
8690
}) async {
87-
for (final old in oldPodcasts.entries) {
91+
for (final old in (oldPodcasts ?? _libraryService.podcasts).entries) {
8892
if (old.value.isNotEmpty) {
8993
final list = old.value;
9094
sortListByAudioFilter(
@@ -102,10 +106,14 @@ class PodcastService {
102106
audios.firstOrNull?.year == firstOld.year ||
103107
audios.isEmpty) return;
104108

105-
updatePodcast(old.key, audios);
106-
_notificationsService.notify(
107-
message: '$updateMessage ${firstOld.album ?? old.value}',
108-
);
109+
_libraryService.updatePodcast(old.key, audios);
110+
if (notify != null) {
111+
notify(message: '$updateMessage ${firstOld.album ?? old.value}');
112+
} else {
113+
_notificationsService.notify(
114+
message: '$updateMessage ${firstOld.album ?? old.value}',
115+
);
116+
}
109117
});
110118
}
111119
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import '../../app/connectivity_model.dart';
2+
import '../../common/view/offline_page.dart';
3+
import '../../common/view/snackbars.dart';
4+
import '../../common/view/theme.dart';
5+
import '../../extensions/build_context_x.dart';
6+
import '../../l10n/l10n.dart';
7+
import '../podcast_model.dart';
8+
import 'dart:io';
9+
import 'package:flutter/material.dart';
10+
import 'package:watch_it/watch_it.dart';
11+
import 'package:yaru/yaru.dart';
12+
13+
class PodcastCollectionControlPanel extends StatelessWidget with WatchItMixin {
14+
const PodcastCollectionControlPanel({super.key});
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
final theme = context.t;
19+
final model = di<PodcastModel>();
20+
21+
final isOnline = watchPropertyValue((ConnectivityModel m) => m.isOnline);
22+
if (!isOnline) return const OfflineBody();
23+
24+
final loading =
25+
watchPropertyValue((PodcastModel m) => m.checkingForUpdates);
26+
final updatesOnly = watchPropertyValue((PodcastModel m) => m.updatesOnly);
27+
final downloadsOnly =
28+
watchPropertyValue((PodcastModel m) => m.downloadsOnly);
29+
30+
return YaruChoiceChipBar(
31+
chipBackgroundColor: chipColor(theme),
32+
selectedChipBackgroundColor: chipSelectionColor(theme, loading),
33+
borderColor: chipBorder(theme, loading),
34+
yaruChoiceChipBarStyle: YaruChoiceChipBarStyle.wrap,
35+
clearOnSelect: false,
36+
selectedFirst: false,
37+
labels: [
38+
Text(context.l10n.newEpisodes),
39+
Text(
40+
context.l10n.downloadsOnly,
41+
),
42+
],
43+
isSelected: [
44+
updatesOnly,
45+
downloadsOnly,
46+
],
47+
onSelected: loading
48+
? null
49+
: (index) {
50+
if (index == 0) {
51+
if (updatesOnly) {
52+
model.setUpdatesOnly(false);
53+
} else {
54+
model.update(
55+
updateMessage: context.l10n.newEpisodeAvailable,
56+
notify: Platform.isLinux
57+
? null
58+
: ({required message}) {
59+
if (context.mounted) {
60+
showSnackBar(
61+
context: context,
62+
content: Text(message),
63+
);
64+
}
65+
},
66+
);
67+
68+
model.setUpdatesOnly(true);
69+
model.setDownloadsOnly(false);
70+
}
71+
} else {
72+
if (downloadsOnly) {
73+
model.setDownloadsOnly(false);
74+
} else {
75+
model.setDownloadsOnly(true);
76+
model.setUpdatesOnly(false);
77+
}
78+
}
79+
},
80+
);
81+
}
82+
}

lib/podcasts/view/podcast_page.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ import '../../search/search_model.dart';
2525
import '../../search/search_type.dart';
2626
import '../../settings/settings_model.dart';
2727
import '../podcast_model.dart';
28+
import 'podcast_refresh_button.dart';
2829
import 'podcast_reorder_button.dart';
2930
import 'podcast_replay_button.dart';
3031
import 'podcast_sub_button.dart';
32+
import 'podcast_timer_button.dart';
3133
import 'sliver_podcast_page_list.dart';
3234

3335
class PodcastPage extends StatelessWidget with WatchItMixin {
@@ -121,6 +123,7 @@ class PodcastPage extends StatelessWidget with WatchItMixin {
121123
mainAxisAlignment: MainAxisAlignment.center,
122124
children: [
123125
PodcastReplayButton(audios: audiosWithDownloads),
126+
const PodcastTimerButton(),
124127
PodcastSubButton(
125128
audios: audiosWithDownloads,
126129
pageId: pageId,
@@ -129,6 +132,7 @@ class PodcastPage extends StatelessWidget with WatchItMixin {
129132
audios: audiosWithDownloads,
130133
pageId: pageId,
131134
),
135+
PodcastRefreshButton(pageId: pageId),
132136
PodcastReorderButton(feedUrl: pageId),
133137
ExploreOnlinePopup(text: title),
134138
],

0 commit comments

Comments
 (0)