From af273cc063f0229ad3af12c6b64226218a23a49d Mon Sep 17 00:00:00 2001 From: Frederik Feichtmeier Date: Wed, 27 Nov 2024 20:41:02 +0100 Subject: [PATCH] fix: android notification media controls and improve player style (#1058) --- android/app/src/main/AndroidManifest.xml | 62 +-- lib/app/view/app.dart | 8 +- lib/app/view/mobile_navigation_bar.dart | 9 +- lib/app/view/music_pod_scaffold.dart | 2 +- lib/common/view/header_bar.dart | 6 + lib/common/view/theme.dart | 36 +- lib/constants.dart | 1 + lib/player/player_service.dart | 7 +- needs_translation.json | 587 ++++++++++++++++++++++- 9 files changed, 657 insertions(+), 61 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9392a6a76..ae23df43c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,41 +1,18 @@ - - + - - - - - + - - - - - + - - - - - - - - - - - - + android:foregroundServiceType="mediaPlayback" + android:exported="true" tools:ignore="Instantiatable"> + + + - - - - - - + + + + - - - \ No newline at end of file diff --git a/lib/app/view/app.dart b/lib/app/view/app.dart index 65539f196..da3943693 100644 --- a/lib/app/view/app.dart +++ b/lib/app/view/app.dart @@ -183,8 +183,12 @@ class _MobileMusicPodAppState extends State<_MobileMusicPodApp> { }, debugShowCheckedModeBanner: false, themeMode: ThemeMode.values[themeIndex], - theme: phoenix.lightTheme, - darkTheme: phoenix.darkTheme, + theme: phoenix.lightTheme.copyWith( + navigationBarTheme: navigationBarTheme(theme: phoenix.lightTheme), + ), + darkTheme: phoenix.darkTheme.copyWith( + navigationBarTheme: navigationBarTheme(theme: phoenix.darkTheme), + ), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: supportedLocales, onGenerateTitle: (context) => kAppTitle, diff --git a/lib/app/view/mobile_navigation_bar.dart b/lib/app/view/mobile_navigation_bar.dart index 9e742d008..bed1ced59 100644 --- a/lib/app/view/mobile_navigation_bar.dart +++ b/lib/app/view/mobile_navigation_bar.dart @@ -3,6 +3,8 @@ import 'package:watch_it/watch_it.dart'; import '../../common/data/audio_type.dart'; import '../../common/view/icons.dart'; +import '../../common/view/theme.dart'; +import '../../common/view/ui_constants.dart'; import '../../constants.dart'; import '../../extensions/build_context_x.dart'; import '../../l10n/l10n.dart'; @@ -20,10 +22,13 @@ class MobilePlayerAndNavigationBar extends StatelessWidget with WatchItMixin { @override Widget build(BuildContext context) { + final fullWindowMode = + watchPropertyValue((AppModel m) => m.fullWindowMode) ?? false; + return RepaintBoundary( child: Material( color: context.theme.cardColor, - child: watchPropertyValue((AppModel m) => m.fullWindowMode) ?? false + child: fullWindowMode ? const FullHeightPlayer( playerPosition: PlayerPosition.fullWindow, ) @@ -31,6 +36,7 @@ class MobilePlayerAndNavigationBar extends StatelessWidget with WatchItMixin { mainAxisSize: MainAxisSize.min, children: [ BottomPlayer(), + SizedBox(height: kMediumSpace), MobileNavigationBar(), ], ), @@ -76,6 +82,7 @@ class MobileNavigationBar extends StatelessWidget with WatchItMixin { }; return NavigationBar( + height: bottomPlayerHeight - 25, backgroundColor: context.theme.cardColor, selectedIndex: selectedPageId == null ? 0 diff --git a/lib/app/view/music_pod_scaffold.dart b/lib/app/view/music_pod_scaffold.dart index 758af0c4f..4757ae7cc 100644 --- a/lib/app/view/music_pod_scaffold.dart +++ b/lib/app/view/music_pod_scaffold.dart @@ -12,7 +12,7 @@ class MusicPodScaffold extends StatelessWidget { @override Widget build(BuildContext context) => Scaffold( resizeToAvoidBottomInset: isMobile ? false : null, - body: isMobile ? SafeArea(child: body) : body, + body: body, appBar: appBar, bottomNavigationBar: isMobile ? const MobilePlayerAndNavigationBar() : null, diff --git a/lib/common/view/header_bar.dart b/lib/common/view/header_bar.dart index be127d660..96217323c 100644 --- a/lib/common/view/header_bar.dart +++ b/lib/common/view/header_bar.dart @@ -67,7 +67,13 @@ class HeaderBar extends StatelessWidget } if (isMobile) { + final fullWindowMode = + watchPropertyValue((AppModel m) => m.fullWindowMode) == true; return AppBar( + systemOverlayStyle: systemOverlayStyle( + theme: context.theme, + fullWindowMode: fullWindowMode, + ), backgroundColor: backgroundColor, titleSpacing: titleSpacing, centerTitle: true, diff --git a/lib/common/view/theme.dart b/lib/common/view/theme.dart index 500075a4f..42294ae84 100644 --- a/lib/common/view/theme.dart +++ b/lib/common/view/theme.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:yaru/yaru.dart'; import '../../app_config.dart'; @@ -280,7 +281,40 @@ double get inputHeight => isMobile double get audioCardDimension => kAudioCardDimension - (isMobile ? 15 : 0); -double get bottomPlayerHeight => isMobile ? 80.0 : 90.0; +double get bottomPlayerHeight => isMobile ? 75.0 : 90.0; + +NavigationBarThemeData navigationBarTheme({required ThemeData theme}) => + theme.navigationBarTheme.copyWith( + iconTheme: WidgetStatePropertyAll( + theme.iconTheme.copyWith( + size: 18, + applyTextScaling: true, + ), + ), + ); + +SystemUiOverlayStyle systemOverlayStyle({ + required ThemeData theme, + required bool fullWindowMode, +}) { + return theme.colorScheme.isLight + ? SystemUiOverlayStyle.dark.copyWith( + systemNavigationBarColor: + (fullWindowMode ? Colors.transparent : theme.cardColor), + statusBarColor: (fullWindowMode + ? Colors.transparent + : theme.scaffoldBackgroundColor), + statusBarBrightness: theme.brightness, + ) + : SystemUiOverlayStyle.light.copyWith( + systemNavigationBarColor: + (fullWindowMode ? Colors.transparent : theme.cardColor), + statusBarColor: (fullWindowMode + ? Colors.transparent + : theme.scaffoldBackgroundColor), + statusBarBrightness: theme.brightness, + ); +} List space({ double widthGap = kSmallestSpace, diff --git a/lib/constants.dart b/lib/constants.dart index 155ae0421..f9d45b3e9 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -2,6 +2,7 @@ const kAppName = 'musicpod'; const kAppTitle = 'MusicPod'; const kAppId = 'org.feichtmeier.Musicpod'; +const kAndroidAppId = 'org.feichtmeier.musicpod'; const kDiscordApplicationId = '1235321910221602837'; const kLinuxDBusName = 'org.mpris.MediaPlayer2.musicpod'; const kAndroidChannelId = 'org.feichtmeier.musicpod.channel.audio'; diff --git a/lib/player/player_service.dart b/lib/player/player_service.dart index e610a67cd..e4bbd7252 100644 --- a/lib/player/player_service.dart +++ b/lib/player/player_service.dart @@ -577,9 +577,12 @@ class PlayerService { Future _initAudioService() async { _audioHandler = await AudioService.init( - config: const AudioServiceConfig( + config: AudioServiceConfig( androidNotificationOngoing: true, androidNotificationChannelName: kAppName, + androidNotificationChannelId: Platform.isAndroid ? kAndroidAppId : null, + androidStopForegroundOnPause: false, + androidNotificationChannelDescription: 'MusicPod Media Controls', ), builder: () { return PlayerServiceAudioHandler( @@ -669,6 +672,8 @@ class PlayerService { _audioHandler!.playbackState.value.copyWith( playing: playing, controls: _determineMediaControls(playing), + processingState: + playing ? AudioProcessingState.ready : AudioProcessingState.idle, ), ); } else if (_smtc != null) { diff --git a/needs_translation.json b/needs_translation.json index c105ba68b..106f50119 100644 --- a/needs_translation.json +++ b/needs_translation.json @@ -618,6 +618,586 @@ "listenBrainzApiKeyEmpty" ], + "eu": [ + "play", + "pause", + "stop", + "shuffle", + "repeat", + "repeatAll", + "next", + "back", + "fastForward30", + "rewind10", + "fullWindow", + "leaveFullWindow", + "fullScreen", + "leaveFullScreen", + "playbackRate", + "addToFavorites", + "removeFromFavorites", + "share", + "local", + "localAudio", + "localAudioDescription", + "localAudioSubtitle", + "music", + "radio", + "podcasts", + "podcast", + "likedSongs", + "likedSongsDescription", + "likedSongsSubtitle", + "cancel", + "add", + "addTo", + "deletePlaylist", + "createNewPlaylist", + "playlistDialogTitleNew", + "playlistDialogTitleEdit", + "save", + "saveAndAuthorize", + "title", + "titles", + "artist", + "artists", + "album", + "albums", + "genres", + "genre", + "years", + "year", + "albumArtist", + "albumArtists", + "track", + "trackNumber", + "diskNumber", + "totalDisks", + "searchLocalAudioHint", + "library", + "playlists", + "playlist", + "discover", + "forYou", + "search", + "noPodcastFound", + "noPodcastChartsFound", + "noPodcastSubsFound", + "charts", + "upNext", + "all", + "arts", + "business", + "comedy", + "education", + "fiction", + "government", + "healthAndFitness", + "history", + "kidsAndFamily", + "leisure", + "news", + "religionAndSpirituality", + "science", + "societyAndCulture", + "sports", + "tvAndFilm", + "technology", + "trueCrime", + "offline", + "offlineDescription", + "newEpisodeAvailable", + "noStationFound", + "nothingFound", + "noStarredStations", + "tags", + "quality", + "station", + "stations", + "country", + "tag", + "failedToImport", + "unknown", + "volume", + "queue", + "limit", + "decreaseSearchLimit", + "podcastFeedIsEmpty", + "video", + "ok", + "noLocalTitlesFound", + "noLocalSearchFound", + "buyMusicOnline", + "settings", + "findUsOnGitHub", + "musicPodSubTitle", + "pickMusicCollection", + "newEpisode", + "dontShowAgain", + "queueConfirmMessage", + "emptyPlaylist", + "copiedToClipBoard", + "copyToClipBoard", + "insertIntoQueue", + "insertedIntoQueue", + "about", + "localAudioCacheSuggestion", + "noThankYou", + "recreateLocalAudioCache", + "useALocalAudioCache", + "newEpisodes", + "collection", + "addToCollection", + "removeFromCollection", + "loadingPodcastFeed", + "downloadStarted", + "downloadCancelled", + "downloadFinished", + "downloadsOnly", + "downloadsDirectory", + "downloadsDirectoryDescription", + "downloadsChangeWarning", + "moreOptions", + "noRadioServerFound", + "connectedTo", + "disconnectedFrom", + "tryReconnect", + "addedTo", + "addToPlaylist", + "open", + "removeFrom", + "noCountryFound", + "noStarredTags", + "name", + "state", + "playNext", + "contributors", + "version", + "theme", + "useMoreAnimationsTitle", + "useMoreAnimationsDescription", + "showPositionDurationTitle", + "showPositionDurationDescription", + "license", + "dependencies", + "light", + "system", + "dark", + "podcastProvider", + "iTunes", + "podcastIndex", + "usePodcastIndex", + "select", + "requiresAppRestart", + "musicCollectionLocation", + "astronomyXXXPodcastIndexOnly", + "automotiveXXXPodcastIndexOnly", + "aviationXXXPodcastIndexOnly", + "baseballXXXPodcastIndexOnly", + "basketballXXXPodcastIndexOnly", + "beautyXXXPodcastIndexOnly", + "booksXXXPodcastIndexOnly", + "buddhismXXXPodcastIndexOnly", + "careersXXXPodcastIndexOnly", + "chemistryXXXPodcastIndexOnly", + "christianityXXXPodcastIndexOnly", + "climateXXXPodcastIndexOnly", + "commentaryXXXPodcastIndexOnly", + "coursesXXXPodcastIndexOnly", + "craftsXXXPodcastIndexOnly", + "cricketXXXPodcastIndexOnly", + "cryptocurrencyXXXPodcastIndexOnly", + "cultureXXXPodcastIndexOnly", + "dailyXXXPodcastIndexOnly", + "designXXXPodcastIndexOnly", + "documentaryXXXPodcastIndexOnly", + "dramaXXXPodcastIndexOnly", + "earthXXXPodcastIndexOnly", + "entertainmentXXXPodcastIndexOnly", + "entrepreneurshipXXXPodcastIndexOnly", + "familyXXXPodcastIndexOnly", + "fantasyXXXPodcastIndexOnly", + "fashionXXXPodcastIndexOnly", + "filmXXXPodcastIndexOnly", + "fitnessXXXPodcastIndexOnly", + "foodXXXPodcastIndexOnly", + "footballXXXPodcastIndexOnly", + "gamesXXXPodcastIndexOnly", + "gardenXXXPodcastIndexOnly", + "golfXXXPodcastIndexOnly", + "healthXXXPodcastIndexOnly", + "hinduismXXXPodcastIndexOnly", + "hobbiesXXXPodcastIndexOnly", + "hockeyXXXPodcastIndexOnly", + "homeXXXPodcastIndexOnly", + "howToXXXPodcastIndexOnly", + "improvXXXPodcastIndexOnly", + "interviewsXXXPodcastIndexOnly", + "investingXXXPodcastIndexOnly", + "islamXXXPodcastIndexOnly", + "journalsXXXPodcastIndexOnly", + "judaismXXXPodcastIndexOnly", + "kidsXXXPodcastIndexOnly", + "languageXXXPodcastIndexOnly", + "learningXXXPodcastIndexOnly", + "lifeXXXPodcastIndexOnly", + "managementXXXPodcastIndexOnly", + "mangaXXXPodcastIndexOnly", + "marketingXXXPodcastIndexOnly", + "mathematicsXXXPodcastIndexOnly", + "medicineXXXPodcastIndexOnly", + "mentalXXXPodcastIndexOnly", + "naturalXXXPodcastIndexOnly", + "natureXXXPodcastIndexOnly", + "nonProfitXXXPodcastIndexOnly", + "nutritionXXXPodcastIndexOnly", + "parentingXXXPodcastIndexOnly", + "performingXXXPodcastIndexOnly", + "personalXXXPodcastIndexOnly", + "petsXXXPodcastIndexOnly", + "philosophyXXXPodcastIndexOnly", + "physicsXXXPodcastIndexOnly", + "placesXXXPodcastIndexOnly", + "politicsXXXPodcastIndexOnly", + "relationshipsXXXPodcastIndexOnly", + "religionXXXPodcastIndexOnly", + "reviewsXXXPodcastIndexOnly", + "rolePlayingXXXPodcastIndexOnly", + "rugbyXXXPodcastIndexOnly", + "runningXXXPodcastIndexOnly", + "selfImprovementXXXPodcastIndexOnly", + "sexualityXXXPodcastIndexOnly", + "soccerXXXPodcastIndexOnly", + "socialXXXPodcastIndexOnly", + "societyXXXPodcastIndexOnly", + "spiritualityXXXPodcastIndexOnly", + "standUpXXXPodcastIndexOnly", + "storiesXXXPodcastIndexOnly", + "swimmingXXXPodcastIndexOnly", + "tVXXXPodcastIndexOnly", + "tabletopXXXPodcastIndexOnly", + "tennisXXXPodcastIndexOnly", + "travelXXXPodcastIndexOnly", + "videoGamesXXXPodcastIndexOnly", + "visualXXXPodcastIndexOnly", + "volleyballXXXPodcastIndexOnly", + "weatherXXXPodcastIndexOnly", + "wildernessXXXPodcastIndexOnly", + "wrestlingXXXPodcastIndexOnly", + "updateAvailable", + "showMetaData", + "metadata", + "writeMetadata", + "reorder", + "move", + "pinAlbum", + "unPinAlbum", + "playAll", + "hearingHistory", + "emptyHearingHistory", + "searchForRadioStationsWithGenreName", + "clearPlaylist", + "editPlaylist", + "stationUrl", + "podcastFeedUrl", + "stationName", + "podcastName", + "url", + "loadFromFileOptional", + "loadMore", + "searchOnline", + "shareThisEpisode", + "downloadEpisode", + "removeDownloadEpisode", + "language", + "duration", + "radioTagDisclaimerTitle", + "radioTagDisclaimerSubTitle", + "podcastFeedLoadingTimeout", + "gitHubClientConnectError", + "replayEpisode", + "replayAllEpisodes", + "checkForUpdates", + "playbackWillStopIn", + "schedulePlaybackStopTimer", + "alwaysAsk", + "hideToTray", + "closeBtnAction", + "whenCloseBtnClicked", + "closeApp", + "closeMusicPod", + "confirmCloseOrHideTip", + "doNotAskAgain", + "skipToLivStream", + "searchSimilarStation", + "onlineArtError", + "clicks", + "exposeOnlineHeadline", + "exposeToDiscordTitle", + "exposeToDiscordSubTitle", + "exposeToLastfmTitle", + "exposeToLastfmSubTitle", + "lastfmApiKey", + "lastfmSecret", + "lastfmApiKeyEmpty", + "lastfmSecretEmpty", + "exposeToListenBrainzTitle", + "exposeToListenBrainzSubTitle", + "listenBrainzApiKey", + "listenBrainzApiKeyEmpty", + "featureDisabledOnPlatform", + "regionNone", + "regionAfghanistan", + "regionAlandislands", + "regionAlbania", + "regionAlgeria", + "regionAmericansamoa", + "regionAndorra", + "regionAngolia", + "regionAnguilla", + "regionAntarctica", + "regionAntiguaandbarbuda", + "regionArgentina", + "regionArmenia", + "regionAruba", + "regionAustralia", + "regionAustria", + "regionAzerbaijan", + "regionBahamas", + "regionBahrain", + "regionBangladesh", + "regionBarbados", + "regionBelarus", + "regionBelgium", + "regionBelize", + "regionBenin", + "regionBermuda", + "regionBhutan", + "regionBolivia", + "regionBonaire", + "regionBosniaandherzegovina", + "regionBotswana", + "regionBouvetisland", + "regionBrazil", + "regionBritishindianoceanterrirory", + "regionBritishvirginislands", + "regionBruneidarussalam", + "regionBulgaria", + "regionBurkinafaso", + "regionBurundi", + "regionCaboverde", + "regionCambodia", + "regionCameroon", + "regionCanada", + "regionCaymanislands", + "regionCentralafricanrepublic", + "regionChad", + "regionChile", + "regionChina", + "regionChristmasisland", + "regionCocosislands", + "regionColombia", + "regionComoros", + "regionCongo", + "regionCongodemocraticrepublicof", + "regionCookislands", + "regionCostarica", + "regionCotedivoire", + "regionCroatia", + "regionCuba", + "regionCuracao", + "regionCyprus", + "regionCzechia", + "regionDenmark", + "regionDjibouti", + "regionDominica", + "regionDominicanrepublic", + "regionEcuador", + "regionEgypt", + "regionElsalvador", + "regionEquatorialguinea", + "regionEritrea", + "regionEstonia", + "regionEthiopia", + "regionFalklandislands", + "regionFaroeislands", + "regionFiji", + "regionFinland", + "regionFrance", + "regionFrenchguiana", + "regionFrenchpolynesia", + "regionFrenchsouthernterritories", + "regionGabon", + "regionGambia", + "regionGeorgia", + "regionGermany", + "regionGhana", + "regionGibraltar", + "regionGreece", + "regionGreenland", + "regionGrenada", + "regionGuadeloupe", + "regionGuam", + "regionGuatemala", + "regionGuernsey", + "regionGuinea", + "regionGuineabissau", + "regionGuyana", + "regionHaiti", + "regionHeardislandandmcdonaldislands", + "regionHonduras", + "regionHongkong", + "regionHungary", + "regionIceland", + "regionIndia", + "regionIndonesia", + "regionIran", + "regionIraq", + "regionIreland", + "regionIsleofman", + "regionIsrael", + "regionItaly", + "regionJamaica", + "regionJapan", + "regionJersey", + "regionJordan", + "regionKazakhstan", + "regionKenya", + "regionKiribati", + "regionKuwait", + "regionKyrgyzstan", + "regionLaos", + "regionLatvia", + "regionLebanon", + "regionLesotho", + "regionLiberia", + "regionLibya", + "regionLiechtenstein", + "regionLithuania", + "regionLuxembourg", + "regionMacao", + "regionMacedonia", + "regionMadagascar", + "regionMalawi", + "regionMalaysia", + "regionMaldives", + "regionMali", + "regionMalta", + "regionMarshallislands", + "regionMartinique", + "regionMauritania", + "regionMauritius", + "regionMayotte", + "regionMexico", + "regionMicronesia", + "regionMoldova", + "regionMonaco", + "regionMongolia", + "regionMontenegro", + "regionMontserrat", + "regionMorocco", + "regionMozambique", + "regionMyanmar", + "regionNamibia", + "regionNauru", + "regionNepal", + "regionNetherlands", + "regionNewcaledonia", + "regionNewzealand", + "regionNicaragua", + "regionNiger", + "regionNigeria", + "regionNiue", + "regionNorfolkisland", + "regionNorthkorea", + "regionNorthernmarianaislands", + "regionNorway", + "regionOman", + "regionPakistan", + "regionPalau", + "regionPalestine", + "regionPanama", + "regionPapuanewguinea", + "regionParaguay", + "regionPeru", + "regionPhilippines", + "regionPitcairn", + "regionPoland", + "regionPortugal", + "regionPuertorico", + "regionQatar", + "regionReunion", + "regionRomania", + "regionRussianfederation", + "regionRwanda", + "regionSaintbarthelemy", + "regionSainthelena", + "regionSaintkittsandnevis", + "regionSaintlucia", + "regionSaintmartin", + "regionSaintpierreandmiquelon", + "regionSaintvincentandthegrenadines", + "regionSamoa", + "regionSanmarino", + "regionSaotomeandprincipe", + "regionSaudiarabia", + "regionSenegal", + "regionSerbia", + "regionSeychelles", + "regionSierraleone", + "regionSingapore", + "regionSintmaarten", + "regionSlovakia", + "regionSlovenia", + "regionSolomonislands", + "regionSomalia", + "regionSouthafrica", + "regionSouthgeorgiaandthesouthsandwichislands", + "regionSouthkorea", + "regionSouthsudan", + "regionSpain", + "regionSrilanka", + "regionSudan", + "regionSuriname", + "regionSvalbardandjanmayen", + "regionSwaziland", + "regionSweden", + "regionSwitzerland", + "regionSyrianarabrepublic", + "regionTaiwan", + "regionTajikistan", + "regionTanzania", + "regionThailand", + "regionTimorleste", + "regionTogo", + "regionTokelau", + "regionTonga", + "regionTrinidadandtobago", + "regionTunisia", + "regionTurkey", + "regionTurkmenistan", + "regionTurksandcaicosislands", + "regionTuvalu", + "regionUganda", + "regionUkraine", + "regionUnitedarabemirates", + "regionUnitedkingdom", + "regionUnitedstates", + "regionUnitedstatesminoroutlyingislands", + "regionUruguay", + "regionUsvirginislands", + "regionUzbekistan", + "regionVanuatu", + "regionVaticancity", + "regionVenezuela", + "regionVietnam", + "regionWallisandfutuna", + "regionWesternsahara", + "regionYemen", + "regionZambia", + "regionZimbabwe" + ], + "fr": [ "local", "saveAndAuthorize", @@ -2738,13 +3318,6 @@ "regionZimbabwe" ], - "sk": [ - "exposeToListenBrainzTitle", - "exposeToListenBrainzSubTitle", - "listenBrainzApiKey", - "listenBrainzApiKeyEmpty" - ], - "sv": [ "shuffle", "repeat",