diff --git a/lib/app/view/desktop_scaffold.dart b/lib/app/view/desktop_home_page.dart similarity index 86% rename from lib/app/view/desktop_scaffold.dart rename to lib/app/view/desktop_home_page.dart index cd61c5d53..9d4bc2875 100644 --- a/lib/app/view/desktop_scaffold.dart +++ b/lib/app/view/desktop_home_page.dart @@ -11,19 +11,20 @@ import '../../extensions/build_context_x.dart'; import '../../l10n/l10n.dart'; import '../../patch_notes/patch_notes_dialog.dart'; import '../../player/view/player_view.dart'; +import '../../podcasts/download_model.dart'; import '../../settings/settings_model.dart'; import '../app_model.dart'; import '../connectivity_model.dart'; import 'master_detail_page.dart'; -class DesktopScaffold extends StatefulWidget with WatchItStatefulWidgetMixin { - const DesktopScaffold({super.key}); +class DesktopHomePage extends StatefulWidget with WatchItStatefulWidgetMixin { + const DesktopHomePage({super.key}); @override - State createState() => _DesktopScaffoldState(); + State createState() => _DesktopHomePageState(); } -class _DesktopScaffoldState extends State { +class _DesktopHomePageState extends State { @override void initState() { super.initState(); @@ -64,6 +65,16 @@ class _DesktopScaffoldState extends State { ); } + registerStreamHandler( + select: (DownloadModel m) => m.messageStream, + initialValue: null, + handler: (context, snapshot, cancel) { + if (snapshot.hasData) { + showSnackBar(context: context, content: Text(snapshot.data ?? '')); + } + }, + ); + return Stack( alignment: Alignment.center, children: [ diff --git a/lib/app/view/desktop_musicpod_app.dart b/lib/app/view/desktop_musicpod_app.dart index 76a66ef2e..3e0ff64b2 100644 --- a/lib/app/view/desktop_musicpod_app.dart +++ b/lib/app/view/desktop_musicpod_app.dart @@ -11,7 +11,7 @@ import '../../library/library_model.dart'; import '../../radio/radio_model.dart'; import '../../settings/settings_model.dart'; import '../connectivity_model.dart'; -import 'desktop_scaffold.dart'; +import 'desktop_home_page.dart'; import 'splash_screen.dart'; class DesktopMusicPodApp extends StatefulWidget @@ -57,6 +57,7 @@ class _DesktopMusicPodAppState extends State { Widget build(BuildContext context) { final themeIndex = watchPropertyValue((SettingsModel m) => m.themeIndex); final phoenix = phoenixTheme(color: widget.accent ?? Colors.greenAccent); + return MaterialApp( debugShowCheckedModeBanner: false, themeMode: ThemeMode.values[themeIndex], @@ -69,11 +70,9 @@ class _DesktopMusicPodAppState extends State { onGenerateTitle: (context) => kAppTitle, home: FutureBuilder( future: _initFuture, - builder: (context, snapshot) { - return snapshot.data == true - ? const DesktopScaffold() - : const SplashScreen(); - }, + builder: (context, snapshot) => snapshot.data == true + ? const DesktopHomePage() + : const SplashScreen(), ), scrollBehavior: const MaterialScrollBehavior().copyWith( dragDevices: { diff --git a/lib/app/view/mobile_page.dart b/lib/app/view/mobile_page.dart index ba4782c0f..ac1990b7b 100644 --- a/lib/app/view/mobile_page.dart +++ b/lib/app/view/mobile_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:watch_it/watch_it.dart'; +import '../../common/view/snackbars.dart'; import '../../extensions/build_context_x.dart'; import '../../player/view/full_height_player.dart'; import '../../player/view/player_view.dart'; +import '../../podcasts/download_model.dart'; import '../app_model.dart'; import 'mobile_bottom_bar.dart'; @@ -20,21 +22,28 @@ class MobilePage extends StatelessWidget with WatchItMixin { final fullWindowMode = watchPropertyValue((AppModel m) => m.fullWindowMode) ?? false; - return Material( - color: context.theme.scaffoldBackgroundColor, - child: Stack( + registerStreamHandler( + select: (DownloadModel m) => m.messageStream, + initialValue: null, + handler: (context, snapshot, cancel) { + if (snapshot.hasData) { + showSnackBar(context: context, content: Text(snapshot.data ?? '')); + } + }, + ); + + return Scaffold( + extendBody: true, + extendBodyBehindAppBar: true, + body: Stack( fit: StackFit.expand, children: [ page, if (fullWindowMode) - const Hero( - tag: 'full_height_player', - child: Scaffold( - extendBody: true, - extendBodyBehindAppBar: true, - body: FullHeightPlayer( - playerPosition: PlayerPosition.fullWindow, - ), + Material( + color: context.theme.scaffoldBackgroundColor, + child: const FullHeightPlayer( + playerPosition: PlayerPosition.fullWindow, ), ) else diff --git a/lib/podcasts/download_model.dart b/lib/podcasts/download_model.dart index b6dfcbba2..ee0f52d47 100644 --- a/lib/podcasts/download_model.dart +++ b/lib/podcasts/download_model.dart @@ -1,12 +1,11 @@ +import 'dart:async'; import 'dart:io'; import 'package:dio/dio.dart'; -import 'package:flutter/material.dart'; import 'package:path/path.dart' as p; import 'package:safe_change_notifier/safe_change_notifier.dart'; import '../common/data/audio.dart'; -import '../l10n/l10n.dart'; import '../library/library_service.dart'; import '../settings/settings_service.dart'; @@ -25,6 +24,15 @@ class DownloadModel extends SafeChangeNotifier { final _values = {}; final _cancelTokens = {}; + final _messageStreamController = StreamController.broadcast(); + String _lastMessage = ''; + void _addMessage(String message) { + if (message == _lastMessage) return; + _lastMessage = message; + _messageStreamController.add(message); + } + + Stream get messageStream => _messageStreamController.stream; double? getValue(String? url) => _values[url]; void setValue({ @@ -41,10 +49,7 @@ class DownloadModel extends SafeChangeNotifier { notifyListeners(); } - Future deleteDownload({ - required BuildContext context, - required Audio? audio, - }) async { + Future deleteDownload({required Audio? audio}) async { if (audio?.url != null && _settingsService.downloadsDir != null && audio?.website != null) { @@ -67,8 +72,9 @@ class DownloadModel extends SafeChangeNotifier { } Future startDownload({ - required BuildContext context, required Audio? audio, + required String canceledMessage, + required String finishedMessage, }) async { final downloadsDir = _settingsService.downloadsDir; if (audio?.url == null || downloadsDir == null) return; @@ -93,7 +99,7 @@ class DownloadModel extends SafeChangeNotifier { final path = p.join(downloadsDir, _createAudioDownloadId(audio)); await _download( - context: context, + canceledMessage: canceledMessage, url: url, path: path, name: audio.title ?? '', @@ -104,13 +110,8 @@ class DownloadModel extends SafeChangeNotifier { path: path, feedUrl: audio.website!, ); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.downloadFinished(audio.title ?? '')), - ), - ); - } + _addMessage(finishedMessage); + _cancelTokens.containsKey(url) ? _cancelTokens.update(url, (value) => null) : _cancelTokens.putIfAbsent(url, () => null); @@ -125,10 +126,10 @@ class DownloadModel extends SafeChangeNotifier { } Future?> _download({ - required BuildContext context, required String url, required String path, required String name, + required String canceledMessage, }) async { _cancelTokens.containsKey(url) ? _cancelTokens.update(url, (value) => CancelToken()) @@ -146,14 +147,17 @@ class DownloadModel extends SafeChangeNotifier { String? message; if (e.toString().contains('[request cancelled]')) { - message = context.l10n.downloadCancelled(name); + message = canceledMessage; } - if (context.mounted) { - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(message ?? e.toString()))); - } + _addMessage(message ?? e.toString()); return null; } } + + @override + Future dispose() async { + await _messageStreamController.close(); + super.dispose(); + } } diff --git a/lib/podcasts/view/download_button.dart b/lib/podcasts/view/download_button.dart index 598c55738..d95634ea5 100644 --- a/lib/podcasts/view/download_button.dart +++ b/lib/podcasts/view/download_button.dart @@ -55,10 +55,16 @@ class DownloadButton extends StatelessWidget with WatchItMixin { ? null : () { if (download) { - model.deleteDownload(context: context, audio: audio); + model.deleteDownload(audio: audio); } else { addPodcast?.call(); - model.startDownload(context: context, audio: audio); + model.startDownload( + finishedMessage: + context.l10n.downloadFinished(audio?.title ?? ''), + canceledMessage: + context.l10n.downloadCancelled(audio?.title ?? ''), + audio: audio, + ); } }, iconSize: iconSize, diff --git a/lib/register.dart b/lib/register.dart index cb8409a73..a25483e3c 100644 --- a/lib/register.dart +++ b/lib/register.dart @@ -215,6 +215,7 @@ Future registerDependencies({required List args}) async { libraryService: di(), dio: di(), ), + dispose: (s) => s.dispose(), ) ..registerLazySingleton( () => SearchModel(