diff --git a/lib/app/app_model.dart b/lib/app/app_model.dart index 48ff15658..9c2f479fe 100644 --- a/lib/app/app_model.dart +++ b/lib/app/app_model.dart @@ -2,8 +2,8 @@ import 'package:flutter/widgets.dart'; import 'package:github/github.dart'; import 'package:safe_change_notifier/safe_change_notifier.dart'; -import '../common/view/snackbars.dart'; import '../constants.dart'; +import '../expose/expose_service.dart'; import '../settings/settings_service.dart'; class AppModel extends SafeChangeNotifier { @@ -12,13 +12,24 @@ class AppModel extends SafeChangeNotifier { required SettingsService settingsService, required GitHub gitHub, required bool allowManualUpdates, + required ExposeService exposeService, }) : _countryCode = WidgetsBinding .instance.platformDispatcher.locale.countryCode ?.toLowerCase(), _gitHub = gitHub, _allowManualUpdates = allowManualUpdates, _settingsService = settingsService, - _version = appVersion; + _version = appVersion, + _exposeService = exposeService; + + final ExposeService _exposeService; + Stream get errorStream => _exposeService.discordErrorStream; + Stream get isDiscordConnectedStream => + _exposeService.isDiscordConnectedStream; + + Future connectToDiscord() async => _exposeService.connectToDiscord(); + Future disconnectFromDiscord() async => + _exposeService.disconnectFromDiscord(); final GitHub _gitHub; final SettingsService _settingsService; @@ -55,7 +66,10 @@ class AppModel extends SafeChangeNotifier { bool? get updateAvailable => _updateAvailable; String? _onlineVersion; String? get onlineVersion => _onlineVersion; - Future checkForUpdate(bool isOnline, BuildContext context) async { + Future checkForUpdate({ + required bool isOnline, + Function(String error)? onError, + }) async { _updateAvailable == null; notifyListeners(); @@ -66,9 +80,7 @@ class AppModel extends SafeChangeNotifier { } _onlineVersion = await getOnlineVersion().onError( (error, stackTrace) { - if (context.mounted) { - showSnackBar(context: context, content: Text(error.toString())); - } + onError?.call(error.toString()); return null; }, ); diff --git a/lib/app/view/scaffold.dart b/lib/app/view/scaffold.dart index 74edda6ba..d44eea95b 100644 --- a/lib/app/view/scaffold.dart +++ b/lib/app/view/scaffold.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter_tabler_icons/flutter_tabler_icons.dart'; import 'package:watch_it/watch_it.dart'; import 'package:yaru/yaru.dart'; +import '../../common/view/snackbars.dart'; +import '../../common/view/theme.dart'; import '../../constants.dart'; import '../../extensions/build_context_x.dart'; +import '../../l10n/l10n.dart'; import '../../patch_notes/patch_notes_dialog.dart'; import '../../player/view/player_view.dart'; import '../app_model.dart'; @@ -23,7 +27,9 @@ class _MusicPodScaffoldState extends State { super.initState(); final appModel = di(); appModel - .checkForUpdate(di().isOnline == true, context) + .checkForUpdate( + isOnline: di().isOnline == true, + ) .then((_) { if (!appModel.recentPatchNotesDisposed() && mounted) { showDialog( @@ -38,6 +44,32 @@ class _MusicPodScaffoldState extends State { Widget build(BuildContext context) { final playerToTheRight = context.mediaQuerySize.width > kSideBarThreshHold; final isFullScreen = watchPropertyValue((AppModel m) => m.fullWindowMode); + final l10n = context.l10n; + registerStreamHandler( + select: (AppModel m) => m.isDiscordConnectedStream, + handler: (context, snapshot, cancel) { + if (!snapshot.hasData || snapshot.hasError) return; + showSnackBar( + context: context, + content: Row( + mainAxisSize: MainAxisSize.min, + children: space( + widthGap: 10, + children: [ + Text( + '${snapshot.data == true ? l10n.connectedTo : l10n.disconnectedFrom}' + ' ${l10n.exposeToDiscordTitle}', + ), + Icon( + TablerIcons.brand_discord_filled, + color: context.theme.primaryColor, + ), + ], + ), + ), + ); + }, + ); return Stack( alignment: Alignment.center, diff --git a/lib/common/view/theme.dart b/lib/common/view/theme.dart index 0ce1f1c7f..7156c52e6 100644 --- a/lib/common/view/theme.dart +++ b/lib/common/view/theme.dart @@ -286,11 +286,15 @@ double get audioCardDimension => kAudioCardDimension - (isMobile ? 15 : 0); double get bottomPlayerHeight => isMobile ? 80.0 : 90.0; -List space({double gap = 5, required Iterable children}) => +List space({ + double widthGap = 5, + double heightGap = 5, + required Iterable children, +}) => children .expand( (item) sync* { - yield SizedBox(width: gap); + yield SizedBox(width: widthGap); yield item; }, ) diff --git a/lib/expose/expose_service.dart b/lib/expose/expose_service.dart index 913a98c6f..2c053548a 100644 --- a/lib/expose/expose_service.dart +++ b/lib/expose/expose_service.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter_discord_rpc/flutter_discord_rpc.dart'; import '../constants.dart'; @@ -7,20 +9,41 @@ class ExposeService { : _discordRPC = discordRPC; final FlutterDiscordRPC? _discordRPC; + final _errorController = StreamController.broadcast(); + Stream get discordErrorStream => _errorController.stream; + Stream get isDiscordConnectedStream => + _discordRPC?.isConnectedStream ?? Stream.value(false); Future? exposeTitleOnline({ required String songDetails, required String state, - }) { - return _discordRPC?.setActivity( - activity: RPCActivity( - assets: RPCAssets( - largeText: songDetails, - smallText: kAppTitle, - ), - details: songDetails, - state: state, - ), - ); + }) async { + try { + if (_discordRPC?.isConnected == false) { + await _discordRPC?.connect(autoRetry: true); + } + if (_discordRPC?.isConnected == true) { + await _discordRPC?.setActivity( + activity: RPCActivity( + assets: RPCAssets( + largeText: songDetails, + smallText: kAppTitle, + ), + details: songDetails, + state: state, + ), + ); + } + } on Exception catch (_) {} + } + + Future connectToDiscord() async => + _discordRPC?.connect(autoRetry: true); + + Future disconnectFromDiscord() async => _discordRPC?.disconnect(); + + Future dispose() async { + await _discordRPC?.disconnect(); + await _errorController.close(); } } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index f1c594ac9..8dfaa6cff 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -182,6 +182,7 @@ "moreOptions": "Mehr Optionen", "noRadioServerFound": "Es wurde kein Radio-Server gefunden", "connectedTo": "Verbunden mit", + "disconnectedFrom": "Getrennt von", "tryReconnect": "Versuche Neuverbindung", "addedTo": "Hinzugefügt zu", "addToPlaylist": "Zu Wiedergabeliste hinzufügen", @@ -352,7 +353,7 @@ "clicks": "Clicks", "exposeOnlineHeadline": "Veröffentliche deine Höraktivität online", "exposeToDiscordTitle": "Discord", - "exposeToDiscordSubTitle": "Der Künstler und Titel des Lieds/Radio-Senders/Podcast das du gerade hörst wird online geteilt. Benötigt App-Neustart.", + "exposeToDiscordSubTitle": "Der Künstler und Titel des Lieds/Radio-Senders/Podcast das du gerade hörst wird online geteilt.", "featureDisabledOnPlatform": "Diese Funktion ist momentan nicht für dieses Betriebssystem aktiviert.", "regionNone": "Keine", "onlineArtError": "Die Albumkunst-Online-Suche ist momentan nicht verfügbar", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 041059509..1782eb865 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -182,6 +182,7 @@ "moreOptions": "More options", "noRadioServerFound": "No radio server found", "connectedTo": "Connected to", + "disconnectedFrom": "Disconnected from", "tryReconnect": "Try reconnect", "addedTo": "Added to", "addToPlaylist": "Add to playlist", @@ -351,7 +352,7 @@ "clicks": "Clicks", "exposeOnlineHeadline": "Expose your listening activity online", "exposeToDiscordTitle": "Discord", - "exposeToDiscordSubTitle": "The artist and title of the song/station/podcast you are currently listening to are shared. Requires app restart.", + "exposeToDiscordSubTitle": "The artist and title of the song/station/podcast you are currently listening to are shared.", "featureDisabledOnPlatform": "This feature is currently disabled for this operating system.", "regionNone": "None", "regionAfghanistan": "Afghanistan", diff --git a/lib/main.dart b/lib/main.dart index 6ea47a0a8..357679ded 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,7 +34,6 @@ import 'player/player_service.dart'; import 'podcasts/download_model.dart'; import 'podcasts/podcast_model.dart'; import 'podcasts/podcast_service.dart'; -import 'radio/online_art_model.dart'; import 'radio/online_art_service.dart'; import 'radio/radio_model.dart'; import 'radio/radio_service.dart'; @@ -60,11 +59,8 @@ Future main(List args) async { final sharedPreferences = await SharedPreferences.getInstance(); final version = (await PackageInfo.fromPlatform()).version; - final enableDiscord = - allowDiscordRPC && sharedPreferences.get(kEnableDiscordRPC) == true; - if (enableDiscord) { + if (allowDiscordRPC) { await FlutterDiscordRPC.initialize(kDiscordApplicationId); - FlutterDiscordRPC.instance.connect(); di.registerLazySingleton( () => FlutterDiscordRPC.instance, dispose: (s) { @@ -80,7 +76,7 @@ Future main(List args) async { sharedPreferences: sharedPreferences, args: args, version: version, - enableDiscord: enableDiscord, + allowDiscordRPC: allowDiscordRPC, downloadsDefaultDir: downloadsDefaultDir, ); @@ -96,7 +92,7 @@ void registerServicesAndViewModels({ required SharedPreferences sharedPreferences, required List args, required String version, - required bool enableDiscord, + required bool allowDiscordRPC, }) { di ..registerLazySingleton(() => sharedPreferences) @@ -112,8 +108,9 @@ void registerServicesAndViewModels({ ) ..registerLazySingleton( () => ExposeService( - discordRPC: enableDiscord ? di() : null, + discordRPC: allowDiscordRPC ? di() : null, ), + dispose: (s) => s.dispose(), ) ..registerLazySingleton( () => PlayerService( @@ -181,7 +178,7 @@ void registerServicesAndViewModels({ ..registerLazySingleton( () => PlayerModel( service: di(), - connectivity: di(), + onlineArtService: di(), )..init(), dispose: (s) => s.dispose(), ) @@ -190,6 +187,7 @@ void registerServicesAndViewModels({ appVersion: version, gitHub: di(), settingsService: di(), + exposeService: di(), allowManualUpdates: Platform.isLinux ? false : true, ), dispose: (s) => s.dispose(), @@ -227,11 +225,5 @@ void registerServicesAndViewModels({ libraryService: di(), localAudioService: di(), )..init(), - ) - ..registerLazySingleton( - () => OnlineArtModel( - service: di(), - ), - dispose: (m) => m.dispose(), ); } diff --git a/lib/player/player_model.dart b/lib/player/player_model.dart index 9ee73a4a9..cab4879a2 100644 --- a/lib/player/player_model.dart +++ b/lib/player/player_model.dart @@ -7,41 +7,47 @@ import 'package:safe_change_notifier/safe_change_notifier.dart'; import '../common/data/audio.dart'; import '../common/data/mpv_meta_data.dart'; +import '../radio/online_art_service.dart'; import 'player_service.dart'; const rateValues = [.75, 1.0, 1.25, 1.5, 1.75, 2.0]; class PlayerModel extends SafeChangeNotifier { - final PlayerService _service; PlayerModel({ required PlayerService service, - required Connectivity connectivity, - }) : _service = service; + required OnlineArtService onlineArtService, + }) : _playerService = service, + _onlineArtService = onlineArtService; - VideoController get controller => _service.controller; + final PlayerService _playerService; + final OnlineArtService _onlineArtService; + + VideoController get controller => _playerService.controller; StreamSubscription? _propertiesChangedSub; StreamSubscription>? _connectivitySubscription; - String? get queueName => _service.queue.name; + Stream get onlineArtError => _onlineArtService.error; + + String? get queueName => _playerService.queue.name; - List