From 828ce5f0332055df9210bb85eebc62c202ad5806 Mon Sep 17 00:00:00 2001 From: MSOB7YY Date: Sat, 9 Mar 2024 14:31:23 +0200 Subject: [PATCH] chore: many performance & ui tweaks --- lib/base/audio_handler.dart | 121 ++++++++++------ lib/base/pull_to_refresh.dart | 49 +++++-- lib/base/youtube_streams_manager.dart | 4 +- lib/controller/current_color.dart | 9 +- lib/controller/lyrics_controller.dart | 1 + lib/controller/player_controller.dart | 7 +- lib/controller/thumbnail_manager.dart | 2 - lib/main.dart | 132 +++++++++--------- lib/packages/lyrics_lrc_parsed_view.dart | 4 +- lib/packages/miniplayer.dart | 1 + lib/packages/miniplayer_base.dart | 8 +- lib/ui/pages/home_page.dart | 25 +++- lib/ui/pages/main_page.dart | 3 +- lib/ui/pages/playlists_page.dart | 32 +---- .../subpages/playlist_tracks_subpage.dart | 23 +-- lib/ui/pages/tracks_page.dart | 12 +- lib/ui/widgets/artwork.dart | 42 +++--- lib/ui/widgets/custom_widgets.dart | 4 +- .../library/multi_artwork_container.dart | 3 + lib/ui/widgets/library/queue_tile.dart | 1 + lib/ui/widgets/video_widget.dart | 8 +- lib/youtube/functions/download_sheet.dart | 2 +- .../pages/yt_playlist_download_subpage.dart | 76 +++++----- lib/youtube/widgets/yt_thumbnail.dart | 41 ++++-- lib/youtube/youtube_miniplayer.dart | 18 ++- lib/youtube/yt_utils.dart | 2 +- 26 files changed, 352 insertions(+), 278 deletions(-) diff --git a/lib/base/audio_handler.dart b/lib/base/audio_handler.dart index daf60a83..3b8303ec 100644 --- a/lib/base/audio_handler.dart +++ b/lib/base/audio_handler.dart @@ -53,6 +53,9 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { late final equalizer = AndroidEqualizer(); late final loudnessEnhancer = AndroidLoudnessEnhancer(); + Duration? get currentItemDuration => _currentItemDuration.value; + final _currentItemDuration = Rxn(); + Timer? _resourcesDisposeTimer; @override @@ -441,6 +444,8 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { @override Future onItemPlay(Q item, int index, bool startPlaying) async { + _currentItemDuration.value = null; + await item._execute( selectable: (finalItem) async { await onItemPlaySelectable(item, finalItem, index, startPlaying); @@ -504,44 +509,55 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { Duration? duration; + bool checkInterrupted() { + if (item.track != currentTrack.track) { + return true; + } else { + if (duration != null) _currentItemDuration.value = duration; + return false; + } + } + try { duration = await setPls(); + if (checkInterrupted()) return; } on Exception catch (e) { - if (duration != null && currentPositionMS > 0) return; - if (item.track != currentTrack.track) return; - printy(e, isError: true); - // -- playing music from root folders still require `all_file_access` - // -- this is a fix for not playing some external files reported by some users. - final hadPermissionBefore = await Permission.manageExternalStorage.isGranted; - if (hadPermissionBefore) { - pause(); - cancelPlayErrorSkipTimer(); - _playErrorRemainingSecondsToSkip.value = 7; - - _playErrorSkipTimer = Timer.periodic( - const Duration(seconds: 1), - (timer) { - _playErrorRemainingSecondsToSkip.value--; - if (_playErrorRemainingSecondsToSkip.value <= 0) { - NamidaNavigator.inst.closeDialog(); - skipToNext(); - timer.cancel(); - } - }, - ); - NamidaDialogs.inst.showTrackDialog( - tr, - isFromPlayerQueue: true, - errorPlayingTrack: e, - source: QueueSource.playerQueue, - ); - return; - } else { - final hasPermission = await requestManageStoragePermission(); - if (hasPermission) { - duration = await setPls(); - } else { + if (checkInterrupted()) return; + final reallyError = !(duration != null && currentPositionMS > 0); + if (reallyError) { + printy(e, isError: true); + // -- playing music from root folders still require `all_file_access` + // -- this is a fix for not playing some external files reported by some users. + final hadPermissionBefore = await Permission.manageExternalStorage.isGranted; + if (hadPermissionBefore) { + pause(); + cancelPlayErrorSkipTimer(); + _playErrorRemainingSecondsToSkip.value = 7; + + _playErrorSkipTimer = Timer.periodic( + const Duration(seconds: 1), + (timer) { + _playErrorRemainingSecondsToSkip.value--; + if (_playErrorRemainingSecondsToSkip.value <= 0) { + NamidaNavigator.inst.closeDialog(); + skipToNext(); + timer.cancel(); + } + }, + ); + NamidaDialogs.inst.showTrackDialog( + tr, + isFromPlayerQueue: true, + errorPlayingTrack: e, + source: QueueSource.playerQueue, + ); return; + } else { + final hasPermission = await requestManageStoragePermission(); + if (!hasPermission) return; + try { + duration = await setPls(); + } catch (_) {} } } } @@ -837,7 +853,20 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { possibleAudioFiles: audioCacheMap[item.id] ?? [], possibleLocalFiles: Indexer.inst.allTracksMappedByYTID[item.id] ?? [], ); - if (item != currentVideo) return; + + Duration? duration = playedFromCacheDetails.duration; + + // race avoidance when playing multiple videos + bool checkInterrupted() { + if (item != currentVideo) { + return true; + } else { + if (duration != null) _currentItemDuration.value = duration; + return false; + } + } + + if (checkInterrupted()) return; currentCachedAudio.value = playedFromCacheDetails.audio; currentCachedVideo.value = playedFromCacheDetails.video; @@ -852,7 +881,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { heyIhandledAudioPlaying = false; } - if (item != currentVideo) return; + if (checkInterrupted()) return; if (ConnectivityController.inst.hasConnection) { try { @@ -865,7 +894,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { } _isFetchingInfo.value = false; if (streams == null) return; - if (item != currentVideo) return; // race avoidance when playing multiple videos + if (checkInterrupted()) return; YoutubeController.inst.currentYTQualities.value = streams.videoOnlyStreams ?? []; YoutubeController.inst.currentYTAudioStreams.value = streams.audioOnlyStreams ?? []; currentVideoInfo.value = streams.videoInfo; @@ -895,7 +924,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { final cachedAudio = prefferedAudioStream?.getCachedFile(item.id); final mediaItem = item.toMediaItem(currentVideoInfo.value, currentVideoThumbnail.value, index, currentQueue.length); _isCurrentAudioFromCache = cachedAudio != null; - if (item != currentVideo) return; // race avoidance when playing multiple videos + if (checkInterrupted()) return; final isVideoCacheSameAsPrevSet = cachedVideo != null && playedFromCacheDetails.video != null && playedFromCacheDetails.video?.path == cachedVideo.path; // only if not the same cache path (i.e. diff resolution) @@ -920,13 +949,13 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { ); } await playerStoppingSeikoo.future; - if (item != currentVideo) return; + if (checkInterrupted()) return; if (cachedVideo?.path != null) { File(cachedVideo!.path).setLastAccessedTry(DateTime.now()); } if (shouldResetAudioSource) { - cachedAudio != null + duration = cachedAudio != null ? await setSource( AudioSource.file(cachedAudio.path, tag: mediaItem), startPlaying: startPlaying, @@ -955,7 +984,7 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { refreshNotification(); } } catch (e) { - if (item != currentVideo) return; // race avoidance when playing multiple videos + if (checkInterrupted()) return; void showSnackError(String nextAction) { SchedulerBinding.instance.addPostFrameCallback((timeStamp) { if (item == currentItem) { @@ -977,10 +1006,12 @@ class NamidaAudioVideoHandler extends BasicAudioHandler { possibleAudioFiles: audioCacheMap[item.id] ?? [], possibleLocalFiles: Indexer.inst.allTracksMappedByYTID[item.id] ?? [], ); - if (item == currentVideo) generateWaveform(); - if (!okaySetFromCache()) { - showSnackError('skipping'); - skipToNext(); + if (!checkInterrupted()) { + generateWaveform(); + if (!okaySetFromCache()) { + showSnackError('skipping'); + skipToNext(); + } } } } diff --git a/lib/base/pull_to_refresh.dart b/lib/base/pull_to_refresh.dart index 9ae017c6..d71681a6 100644 --- a/lib/base/pull_to_refresh.dart +++ b/lib/base/pull_to_refresh.dart @@ -6,11 +6,14 @@ import 'package:namida/core/icon_fonts/broken_icons.dart'; mixin PullToRefreshMixin on State implements TickerProvider { final turnsTween = Tween(begin: 0.0, end: 1.0); late final animation = AnimationController(vsync: this, duration: Duration.zero); - AnimationController get animation2; + + AnimationController? get refreshAnimation => null; + AnimationController? _animation2Backup; + AnimationController get _animation2 => refreshAnimation ?? _animation2Backup!; final _minTrigger = 20; - num get pullNormalizer => 20; + num get pullNormalizer => 100; double _distanceDragged = 0; bool onVerticalDragUpdate(double dy) { @@ -23,16 +26,28 @@ mixin PullToRefreshMixin on State implements Ticker return true; } + void onPointerMove(ScrollController sc, PointerMoveEvent event) { + if (!sc.hasClients) return; + final p = sc.position.pixels; + if (p <= 0 && event.delta.dx < 0.1) onVerticalDragUpdate(event.delta.dy); + } + void onVerticalDragFinish() { animation.animateTo(0, duration: const Duration(milliseconds: 100)); _distanceDragged = 0; } - Future showRefreshingAnimation(Future Function() whileExecuting) async { - animation2.repeat(); - await whileExecuting(); - await animation2.fling(); - animation2.stop(); + bool _isRefreshing = false; + Future onRefresh(Future Function() execute) async { + onVerticalDragFinish(); + if (animation.value != 1 || _isRefreshing) return; + _isRefreshing = true; + _animation2.repeat(); + await execute(); + await _animation2.fling(); + _animation2.stop(); + _isRefreshing = false; + onVerticalDragFinish(); } Widget get pullToRefreshWidget { @@ -48,7 +63,7 @@ mixin PullToRefreshMixin on State implements Ticker ), builder: (context, circleAvatar) { final p = animation.value; - if (!animation2.isAnimating && p == 0) return const SizedBox(); + if (!_animation2.isAnimating && p == 0) return const SizedBox(); const multiplier = 4.5; const minus = multiplier / 3; return Padding( @@ -56,14 +71,14 @@ mixin PullToRefreshMixin on State implements Ticker child: Transform.rotate( angle: (p * multiplier) - minus, child: AnimatedBuilder( - animation: animation2, + animation: _animation2, child: circleAvatar, builder: (context, circleAvatar) { return Opacity( - opacity: animation2.status == AnimationStatus.forward ? 1.0 : p, + opacity: _animation2.status == AnimationStatus.forward ? 1.0 : p, child: RotationTransition( key: const Key('rotatie'), - turns: turnsTween.animate(animation2), + turns: turnsTween.animate(_animation2), child: circleAvatar, ), ); @@ -76,9 +91,21 @@ mixin PullToRefreshMixin on State implements Ticker ); } + @override + void initState() { + super.initState(); + if (refreshAnimation == null) { + _animation2Backup = AnimationController( + duration: const Duration(milliseconds: 1200), + vsync: this, + ); + } + } + @override void dispose() { animation.dispose(); + _animation2Backup?.dispose(); super.dispose(); } } diff --git a/lib/base/youtube_streams_manager.dart b/lib/base/youtube_streams_manager.dart index 33326117..1bdf5ee7 100644 --- a/lib/base/youtube_streams_manager.dart +++ b/lib/base/youtube_streams_manager.dart @@ -121,7 +121,9 @@ mixin YoutubeStreamsManager { case YTVideosSorting.date: return (lang.DATE, Broken.calendar); case YTVideosSorting.views: - return (lang.VIEWS, Broken.eye); + final word = lang.VIEWS; + final finalw = word.length > 1 ? "${word[0].toUpperCase()}${word.substring(1)}" : word; + return (finalw, Broken.eye); case YTVideosSorting.duration: return (lang.DURATION, Broken.timer_1); } diff --git a/lib/controller/current_color.dart b/lib/controller/current_color.dart index 918167d0..20530a68 100644 --- a/lib/controller/current_color.dart +++ b/lib/controller/current_color.dart @@ -37,7 +37,7 @@ class CurrentColor { final _namidaColorMiniplayer = Rxn(); - final Rx _namidaColor = NamidaColor( + late final Rx _namidaColor = NamidaColor( used: playerStaticColor, mix: playerStaticColor, palette: [playerStaticColor], @@ -352,8 +352,8 @@ class CurrentColor { final nc = await extractPaletteFromImage(imagePath, track: track, forceReExtract: true, useIsolate: useIsolate); _updateInColorMap(filename, nc); } - if (Player.inst.nowPlayingTrack == track) { - updatePlayerColorFromTrack(track, null); + if (Player.inst.nowPlayingTWD == track) { + updatePlayerColorFromTrack(Player.inst.nowPlayingTWD, null); } } @@ -424,9 +424,6 @@ class CurrentColor { void _updateInColorMap(String filenameWoExt, NamidaColor? nc) { if (nc != null) _colorsMap[filenameWoExt] = nc; - if (filenameWoExt == Player.inst.nowPlayingTrack.path.getFilename) { - updatePlayerColorFromTrack(Player.inst.nowPlayingTrack, null); - } } void _updateCurrentPaletteHalfs(NamidaColor nc) { diff --git a/lib/controller/lyrics_controller.dart b/lib/controller/lyrics_controller.dart index 7caaf79e..c87142ea 100644 --- a/lib/controller/lyrics_controller.dart +++ b/lib/controller/lyrics_controller.dart @@ -279,6 +279,7 @@ class Lyrics { if (lyricsRes.contains('')) return ''; if (lyricsRes.contains('please enable javascript on your web browser')) return ''; if (lyricsRes.contains('Error 500 (Server Error)')) return ''; + if (lyricsRes.contains('systems have detected unusual traffic from your computer network')) return ''; return lyricsRes; } diff --git a/lib/controller/player_controller.dart b/lib/controller/player_controller.dart index dadda193..ca5da848 100644 --- a/lib/controller/player_controller.dart +++ b/lib/controller/player_controller.dart @@ -82,7 +82,12 @@ class Player { bool get isBuffering => _audioHandler.currentState == ProcessingState.buffering; bool get isLoading => _audioHandler.currentState == ProcessingState.loading; bool get isFetchingInfo => _audioHandler.isFetchingInfo; - bool get shouldShowLoadingIndicator => (isFetchingInfo && _audioHandler.currentCachedVideo.value == null) || isBuffering || isLoading; + bool get shouldShowLoadingIndicator { + if (isBuffering || isLoading) return true; + if (isFetchingInfo && _audioHandler.currentState != ProcessingState.ready) return true; + return false; + } + Duration get buffered => _audioHandler.buffered; int get numberOfRepeats => _audioHandler.numberOfRepeats; int get latestInsertedIndex => _audioHandler.latestInsertedIndex; diff --git a/lib/controller/thumbnail_manager.dart b/lib/controller/thumbnail_manager.dart index 3d1b346b..e1ce2099 100644 --- a/lib/controller/thumbnail_manager.dart +++ b/lib/controller/thumbnail_manager.dart @@ -108,7 +108,6 @@ class ThumbnailManager { String? id, String? channelUrlOrID, bool isImportantInCache = true, - FutureOr Function()? beforeFetchingFromInternet, void Function(Uint8List? bytes)? bytesIfWontWriteToFile, bool hqChannelImage = false, }) async { @@ -128,7 +127,6 @@ class ThumbnailManager { } _printie('Downloading Thumbnail Started'); - await beforeFetchingFromInternet?.call(); if (channelUrlOrID != null && hqChannelImage) { final res = await _getChannelAvatarUrlIsolate.thready(channelUrlOrID); diff --git a/lib/main.dart b/lib/main.dart index a9c07759..a4fcdd74 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -259,8 +259,9 @@ Future _initializeIntenties() async { final paths = []; final m3uPaths = {}; files.loop((f, _) { - final path = f.realPath?.replaceAll('\\', ''); - if (path != null) { + final realPath = f.realPath; + if (realPath != null) { + final path = realPath.replaceAll('\\', ''); if (kM3UPlaylistsExtensions.any((ext) => path.endsWith(ext))) { m3uPaths.add(path); } else { @@ -418,59 +419,65 @@ class Namida extends StatelessWidget { @override Widget build(BuildContext context) { + // -- no need to listen, widget is rebuilt on applifecycle + final showPipOnly = NamidaChannel.inst.isInPip.value && Player.inst.videoPlayerInfo != null; return Stack( alignment: Alignment.bottomLeft, children: [ - Obx( - () { - final codes = settings.selectedLanguage.value.code.split('_'); - return Localizations( - locale: Locale(codes.first, codes.last), - delegates: const [ - DefaultWidgetsLocalizations.delegate, - DefaultMaterialLocalizations.delegate, - ], - child: GetMaterialApp( - key: const Key('namida_app'), - debugShowCheckedModeBanner: false, - title: 'Namida', - // restorationScopeId: 'Namida', - builder: (context, widget) { - return ScrollConfiguration( - behavior: const ScrollBehaviorModified(), - child: Obx( - () { - final mode = settings.themeMode.value; - final useDarkTheme = mode == ThemeMode.dark || (mode == ThemeMode.system && MediaQuery.platformBrightnessOf(context) == Brightness.dark); - final isLight = !useDarkTheme; - final theme = AppThemes.inst.getAppTheme(CurrentColor.inst.currentColorScheme, isLight); - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - systemNavigationBarContrastEnforced: false, - systemNavigationBarColor: const Color(0x00000000), - systemNavigationBarDividerColor: const Color(0x00000000), - systemNavigationBarIconBrightness: isLight ? Brightness.dark : Brightness.light, - ), - ); - return AnimatedTheme( - duration: const Duration(milliseconds: kThemeAnimationDurationMS), - data: theme, - child: widget!, - ); - }, - ), - ); - }, - home: MainPageWrapper( - shouldShowOnBoarding: shouldShowOnBoarding, - onContextAvailable: (ctx) { - _initialContext = ctx; - _waitForFirstBuildContext.isCompleted ? null : _waitForFirstBuildContext.complete(true); + Visibility( + maintainState: true, + visible: !showPipOnly, + child: Obx( + () { + final codes = settings.selectedLanguage.value.code.split('_'); + return Localizations( + locale: Locale(codes.first, codes.last), + delegates: const [ + DefaultWidgetsLocalizations.delegate, + DefaultMaterialLocalizations.delegate, + ], + child: GetMaterialApp( + key: const Key('namida_app'), + debugShowCheckedModeBanner: false, + title: 'Namida', + // restorationScopeId: 'Namida', + builder: (context, widget) { + return ScrollConfiguration( + behavior: const ScrollBehaviorModified(), + child: Obx( + () { + final mode = settings.themeMode.value; + final useDarkTheme = mode == ThemeMode.dark || (mode == ThemeMode.system && MediaQuery.platformBrightnessOf(context) == Brightness.dark); + final isLight = !useDarkTheme; + final theme = AppThemes.inst.getAppTheme(CurrentColor.inst.currentColorScheme, isLight); + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarContrastEnforced: false, + systemNavigationBarColor: const Color(0x00000000), + systemNavigationBarDividerColor: const Color(0x00000000), + systemNavigationBarIconBrightness: isLight ? Brightness.dark : Brightness.light, + ), + ); + return AnimatedTheme( + duration: const Duration(milliseconds: kThemeAnimationDurationMS), + data: theme, + child: widget!, + ); + }, + ), + ); }, + home: MainPageWrapper( + shouldShowOnBoarding: shouldShowOnBoarding, + onContextAvailable: (ctx) { + _initialContext = ctx; + _waitForFirstBuildContext.isCompleted ? null : _waitForFirstBuildContext.complete(true); + }, + ), ), - ), - ); - }, + ); + }, + ), ), // prevent accidental opening for drawer when performing back gesture @@ -494,19 +501,18 @@ class Namida extends StatelessWidget { ), ), - NamidaChannel.inst.isInPip.value && Player.inst.videoPlayerInfo != null - ? Container( - color: Colors.black, - alignment: Alignment.topLeft, - child: const NamidaVideoControls( - key: Key('pip_widget_child'), - isFullScreen: true, - showControls: false, - onMinimizeTap: null, - isLocal: true, - ), - ) - : const SizedBox(), + if (showPipOnly) + Container( + color: Colors.black, + alignment: Alignment.topLeft, + child: const NamidaVideoControls( + key: Key('pip_widget_child'), + isFullScreen: true, + showControls: false, + onMinimizeTap: null, + isLocal: true, + ), + ) ], ); } diff --git a/lib/packages/lyrics_lrc_parsed_view.dart b/lib/packages/lyrics_lrc_parsed_view.dart index 1610b979..cbd50d5f 100644 --- a/lib/packages/lyrics_lrc_parsed_view.dart +++ b/lib/packages/lyrics_lrc_parsed_view.dart @@ -185,7 +185,7 @@ class LyricsLRCParsedViewState extends State { child: Obx( () => Container( color: Color.alphaBlend( - CurrentColor.inst.color.withOpacity(0.2), + CurrentColor.inst.miniplayerColor.withOpacity(0.2), context.isDarkMode ? Colors.black.withOpacity(0.5) : Colors.white.withOpacity(0.5), ), ), @@ -249,7 +249,7 @@ class LyricsLRCParsedViewState extends State { return const SizedBox(); } - final color = CurrentColor.inst.color; + final color = CurrentColor.inst.miniplayerColor; final highlighted = timestampsMap[_latestUpdatedLine.value]?.$2; return PageStorage( bucket: PageStorageBucket(), diff --git a/lib/packages/miniplayer.dart b/lib/packages/miniplayer.dart index 499faa31..91a8e0e0 100644 --- a/lib/packages/miniplayer.dart +++ b/lib/packages/miniplayer.dart @@ -297,6 +297,7 @@ class NamidaMiniPlayerTrack extends StatelessWidget { iconSize: 20.0, blurRadius: 6.0, baseIconColor: context.theme.colorScheme.onSecondaryContainer, + secondaryIconColor: context.theme.colorScheme.onSecondaryContainer, ) : Icon( Broken.document, diff --git a/lib/packages/miniplayer_base.dart b/lib/packages/miniplayer_base.dart index 2673e9d4..0d45e539 100644 --- a/lib/packages/miniplayer_base.dart +++ b/lib/packages/miniplayer_base.dart @@ -646,7 +646,7 @@ class _NamidaMiniPlayerBaseState extends State> { if (widget.canShowBuffering) IgnorePointer( child: Obx( - () => Player.inst.isBuffering || Player.inst.isLoading + () => Player.inst.shouldShowLoadingIndicator ? ThreeArchedCircle( color: Colors.white.withAlpha(120), size: iconSize * 1.4, @@ -815,7 +815,7 @@ class _NamidaMiniPlayerBaseState extends State> { Widget getTextWidget(IconData icon, String title, double value) { return Row( children: [ - Icon(icon, color: context.defaultIconColor()), + Icon(icon, color: context.defaultIconColor(CurrentColor.inst.miniplayerColor)), const SizedBox(width: 12.0), Text( title, @@ -1250,7 +1250,7 @@ class _TrackInfo extends StatelessWidget { maxLines: textData.secondLine == '' ? 2 : 1, overflow: TextOverflow.ellipsis, style: context.textTheme.displayMedium?.copyWith( - fontSize: velpy(a: 15.0, b: 20.0, c: p).multipliedFontScale, + fontSize: velpy(a: 14.5, b: 20.0, c: p).multipliedFontScale, height: 1, ), ), @@ -1261,7 +1261,7 @@ class _TrackInfo extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, style: context.textTheme.displayMedium?.copyWith( - fontSize: velpy(a: 13.0, b: 15.0, c: p).multipliedFontScale, + fontSize: velpy(a: 12.5, b: 15.0, c: p).multipliedFontScale, ), ), ], diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart index c9d52d87..8dc2bf3c 100644 --- a/lib/ui/pages/home_page.dart +++ b/lib/ui/pages/home_page.dart @@ -70,10 +70,12 @@ class _HomePageState extends State with SingleTickerProviderStateMixin final _mixes = >{}; int currentYearLostMemories = 0; + late final ScrollController lostMemoriesScrollController; @override void initState() { _fillLists(); + lostMemoriesScrollController = ScrollController(); super.initState(); } @@ -90,6 +92,7 @@ class _HomePageState extends State with SingleTickerProviderStateMixin _topRecentAlbums.clear(); _topRecentArtists.clear(); _mixes.clear(); + lostMemoriesScrollController.dispose(); super.dispose(); } @@ -177,6 +180,7 @@ class _HomePageState extends State with SingleTickerProviderStateMixin isStartOfDay: false, ); currentYearLostMemories = year; + if (lostMemoriesScrollController.hasClients) lostMemoriesScrollController.jumpTo(0); } List _listOrShimmer(List listy) { @@ -364,6 +368,7 @@ class _HomePageState extends State with SingleTickerProviderStateMixin case HomePageItems.recentListens: return _TracksList( + listId: 'recentListens', homepageItem: element, title: lang.RECENT_LISTENS, icon: Broken.command_square, @@ -379,6 +384,7 @@ class _HomePageState extends State with SingleTickerProviderStateMixin case HomePageItems.topRecentListens: return _TracksList( + listId: 'topRecentListens', homepageItem: element, title: lang.TOP_RECENTS, icon: Broken.crown_1, @@ -389,6 +395,8 @@ class _HomePageState extends State with SingleTickerProviderStateMixin case HomePageItems.lostMemories: return _TracksList( + listId: 'lostMemories_$currentYearLostMemories', + controller: lostMemoriesScrollController, homepageItem: element, title: lang.LOST_MEMORIES, subtitle: () { @@ -455,6 +463,7 @@ class _HomePageState extends State with SingleTickerProviderStateMixin case HomePageItems.recentlyAdded: return _TracksList( + listId: 'recentlyAdded', queueSource: QueueSource.recentlyAdded, homepageItem: element, title: lang.RECENTLY_ADDED, @@ -542,6 +551,8 @@ class _TracksList extends StatelessWidget { final Widget? leading; final String? Function(Selectable? track)? topRightText; final QueueSource queueSource; + final String listId; + final ScrollController? controller; const _TracksList({ super.key, @@ -556,6 +567,8 @@ class _TracksList extends StatelessWidget { this.leading, this.topRightText, this.queueSource = QueueSource.homePageItem, + required this.listId, + this.controller, }); @override @@ -566,6 +579,7 @@ class _TracksList extends StatelessWidget { final queue = listWithListens?.firstOrNull == null ? [] : listWithListens!.map((e) => e!.key); return SliverToBoxAdapter( child: _HorizontalList( + controller: controller, title: title, icon: icon, leading: leading, @@ -578,6 +592,7 @@ class _TracksList extends StatelessWidget { itemBuilder: (context, index) { final twl = finalListWithListens[index]; return _TrackCard( + listId: listId, homepageItem: homepageItem, title: title, index: index, @@ -607,6 +622,7 @@ class _TracksList extends StatelessWidget { itemBuilder: (context, index) { final tr = finalList[index]; return _TrackCard( + listId: listId, homepageItem: homepageItem, title: title, index: index, @@ -735,6 +751,7 @@ class _HorizontalList extends StatelessWidget { final Widget? leading; final NullableIndexedWidgetBuilder itemBuilder; final Color? iconColor; + final ScrollController? controller; const _HorizontalList({ required this.title, @@ -749,6 +766,7 @@ class _HorizontalList extends StatelessWidget { this.thirdWidget, this.leading, this.iconColor, + this.controller, }); @override @@ -789,6 +807,7 @@ class _HorizontalList extends StatelessWidget { height: height, width: context.width, child: ListView.builder( + controller: controller, itemExtent: itemExtent, padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12.0), scrollDirection: Axis.horizontal, @@ -1115,6 +1134,7 @@ class _TrackCard extends StatefulWidget { final double width; final Color? color; final Track? track; + final String listId; final Iterable queue; final int index; final Iterable? listens; @@ -1127,6 +1147,7 @@ class _TrackCard extends StatefulWidget { required this.width, this.color, required this.track, + required this.listId, required this.queue, required this.index, this.listens, @@ -1173,7 +1194,7 @@ class _TrackCardState extends State<_TrackCard> with LoadingItemsDelayMixin { } return NamidaInkWell( onTap: () { - if (mounted) setState(() => _enabledTrack = (widget.title, widget.index)); + if (mounted) setState(() => _enabledTrack = (widget.listId, widget.index)); Player.inst.playOrPause( widget.index, @@ -1190,7 +1211,7 @@ class _TrackCardState extends State<_TrackCard> with LoadingItemsDelayMixin { width: widget.width, bgColor: color, decoration: BoxDecoration( - border: _enabledTrack == (widget.title, widget.index) + border: _enabledTrack == (widget.listId, widget.index) ? Border.all( color: _cardColor ?? color, width: 1.5, diff --git a/lib/ui/pages/main_page.dart b/lib/ui/pages/main_page.dart index 6415443a..4a245b1b 100644 --- a/lib/ui/pages/main_page.dart +++ b/lib/ui/pages/main_page.dart @@ -101,7 +101,8 @@ class MainPage extends StatelessWidget { animation: animation, child: main, builder: (context, child) { - return Visibility.maintain( + return Visibility( + maintainState: true, visible: animation.value < 1, // not expanded/queue child: Transform.scale( scale: 1 - (animation.value * 0.05), diff --git a/lib/ui/pages/playlists_page.dart b/lib/ui/pages/playlists_page.dart index 52550e08..df478185 100644 --- a/lib/ui/pages/playlists_page.dart +++ b/lib/ui/pages/playlists_page.dart @@ -52,23 +52,6 @@ class PlaylistsPage extends StatefulWidget { } class _PlaylistsPageState extends State with TickerProviderStateMixin, PullToRefreshMixin { - @override - AnimationController get animation2 => _animation2; - - late final _animation2 = AnimationController( - duration: const Duration(milliseconds: 1200), - vsync: this, - ); - - @override - void dispose() { - _animation2.dispose(); - super.dispose(); - } - - @override - num get pullNormalizer => 100; - bool get _shouldAnimate => widget.animateTiles && LibraryTab.playlists.shouldAnimateTiles; @override @@ -83,18 +66,13 @@ class _PlaylistsPageState extends State with TickerProviderStateM child: Listener( onPointerMove: (event) { final c = scrollController; - if (c == null || !c.hasClients) return; - final p = c.position.pixels; - if (p <= 0 && event.delta.dx < 0.1) onVerticalDragUpdate(event.delta.dy); + if (c != null) onPointerMove(c, event); }, onPointerUp: (event) async { - if (animation.value == 1) { - showRefreshingAnimation(() async { - await PlaylistController.inst.prepareM3UPlaylists(); - PlaylistController.inst.sortPlaylists(); - }); - } - onVerticalDragFinish(); + onRefresh(() async { + await PlaylistController.inst.prepareM3UPlaylists(); + PlaylistController.inst.sortPlaylists(); + }); }, onPointerCancel: (event) => onVerticalDragFinish(), child: NamidaScrollbar( diff --git a/lib/ui/pages/subpages/playlist_tracks_subpage.dart b/lib/ui/pages/subpages/playlist_tracks_subpage.dart index 6f2313fa..371866f0 100644 --- a/lib/ui/pages/subpages/playlist_tracks_subpage.dart +++ b/lib/ui/pages/subpages/playlist_tracks_subpage.dart @@ -384,27 +384,15 @@ class NormalPlaylistTracksPage extends StatefulWidget { } class _NormalPlaylistTracksPageState extends State with TickerProviderStateMixin, PullToRefreshMixin { - @override - AnimationController get animation2 => _animation2; - - late final _animation2 = AnimationController( - duration: const Duration(milliseconds: 1200), - vsync: this, - ); - late final _scrollController = ScrollController(); late String? _playlistM3uPath = PlaylistController.inst.getPlaylist(widget.playlistName)?.m3uPath; @override void dispose() { - _animation2.dispose(); _scrollController.dispose(); super.dispose(); } - @override - num get pullNormalizer => 100; - @override Widget build(BuildContext context) { final child = Obx( @@ -482,19 +470,18 @@ class _NormalPlaylistTracksPageState extends State wit child: _playlistM3uPath != null ? Listener( onPointerMove: (event) { - if (!_scrollController.hasClients) return; - final p = _scrollController.position.pixels; - if (p <= 0 && event.delta.dx < 0.1) onVerticalDragUpdate(event.delta.dy); + onPointerMove(_scrollController, event); }, onPointerUp: (event) async { final m3uPath = _playlistM3uPath; - if (m3uPath != null && animation.value == 1) { - showRefreshingAnimation(() async { + if (m3uPath != null) { + onRefresh(() async { await PlaylistController.inst.prepareM3UPlaylists(forPaths: {m3uPath}); PlaylistController.inst.sortPlaylists(); }); + } else { + onVerticalDragFinish(); } - onVerticalDragFinish(); }, onPointerCancel: (event) => onVerticalDragFinish(), child: child, diff --git a/lib/ui/pages/tracks_page.dart b/lib/ui/pages/tracks_page.dart index d6d40988..183b79a9 100644 --- a/lib/ui/pages/tracks_page.dart +++ b/lib/ui/pages/tracks_page.dart @@ -33,7 +33,7 @@ class _TracksPageState extends State with TickerProviderStateMixin, final _animationKey = 'tracks_page'; @override - AnimationController get animation2 => RefreshLibraryIconController.getController(_animationKey, this); + AnimationController get refreshAnimation => RefreshLibraryIconController.getController(_animationKey, this); @override void initState() { @@ -53,16 +53,10 @@ class _TracksPageState extends State with TickerProviderStateMixin, return BackgroundWrapper( child: Listener( onPointerMove: (event) { - final c = scrollController; - if (!c.hasClients) return; - final p = c.position.pixels; - if (p <= 0 && event.delta.dx < 0.1) onVerticalDragUpdate(event.delta.dy); + onPointerMove(scrollController, event); }, onPointerUp: (event) { - if (animation.value == 1) { - showRefreshPromptDialog(false); - } - onVerticalDragFinish(); + onRefresh(() async => await showRefreshPromptDialog(false)); }, onPointerCancel: (event) => onVerticalDragFinish(), child: Obx( diff --git a/lib/ui/widgets/artwork.dart b/lib/ui/widgets/artwork.dart index f62f2609..a4989687 100644 --- a/lib/ui/widgets/artwork.dart +++ b/lib/ui/widgets/artwork.dart @@ -340,6 +340,7 @@ class MultiArtworks extends StatelessWidget { final bool disableHero; final double iconSize; final bool fallbackToFolderCover; + final bool reduceQuality; const MultiArtworks({ super.key, @@ -351,6 +352,7 @@ class MultiArtworks extends StatelessWidget { this.disableHero = false, this.iconSize = 29.0, this.fallbackToFolderCover = true, + this.reduceQuality = false, }); @override @@ -393,8 +395,7 @@ class MultiArtworks extends StatelessWidget { compressed: false, width: c.maxWidth, height: c.maxHeight, - fallbackToFolderCover: fallbackToFolderCover, - ) + fallbackToFolderCover: fallbackToFolderCover) : tracks.length == 2 ? Row( children: [ @@ -410,6 +411,7 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 80 : 100, ), ArtworkWidget( key: Key("1_${tracks[1].pathToImage}"), @@ -423,6 +425,7 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 80 : 100, ), ], ) @@ -443,6 +446,7 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight / 2, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 60 : 100, ), ArtworkWidget( key: Key("1_${tracks[1].pathToImage}"), @@ -456,25 +460,23 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight / 2, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 60 : 100, ), ], ), - Column( - children: [ - ArtworkWidget( - key: Key("2_${tracks[2].pathToImage}"), - thumbnailSize: thumbnailSize / 2, - track: tracks[2], - path: tracks[2].pathToImage, - forceSquared: true, - blur: 0, - borderRadius: 0, - iconSize: iconSize, - width: c.maxWidth / 2, - height: c.maxHeight, - fallbackToFolderCover: fallbackToFolderCover, - ), - ], + ArtworkWidget( + key: Key("2_${tracks[2].pathToImage}"), + thumbnailSize: thumbnailSize / 2, + track: tracks[2], + path: tracks[2].pathToImage, + forceSquared: true, + blur: 0, + borderRadius: 0, + iconSize: iconSize, + width: c.maxWidth / 2, + height: c.maxHeight, + fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 80 : 100, ), ], ) @@ -494,6 +496,7 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight / 2, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 60 : 100, ), ArtworkWidget( key: Key("1_${tracks[1].pathToImage}"), @@ -507,6 +510,7 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight / 2, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 60 : 100, ), ], ), @@ -524,6 +528,7 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight / 2, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 60 : 100, ), ArtworkWidget( key: Key("3_${tracks[3].pathToImage}"), @@ -537,6 +542,7 @@ class MultiArtworks extends StatelessWidget { width: c.maxWidth / 2, height: c.maxHeight / 2, fallbackToFolderCover: fallbackToFolderCover, + cacheHeight: reduceQuality ? 60 : 100, ), ], ), diff --git a/lib/ui/widgets/custom_widgets.dart b/lib/ui/widgets/custom_widgets.dart index e322ad61..7babc720 100644 --- a/lib/ui/widgets/custom_widgets.dart +++ b/lib/ui/widgets/custom_widgets.dart @@ -1083,7 +1083,7 @@ class StackedIcon extends StatelessWidget { ), child: smallChild ?? (secondaryText != null - ? Text(secondaryText!, style: context.textTheme.displaySmall?.copyWith(color: context.defaultIconColor())) + ? Text(secondaryText!, style: context.textTheme.displaySmall?.copyWith(color: _getColory(context, secondaryIconColor))) : Icon( secondaryIcon, size: secondaryIconSize, @@ -1536,7 +1536,7 @@ class NamidaPartyContainer extends StatelessWidget { decoration: BoxDecoration( boxShadow: [ BoxShadow( - color: CurrentColor.inst.color.withAlpha(150), + color: CurrentColor.inst.miniplayerColor.withAlpha(150), spreadRadius: 150 * finalScale * spreadRadiusMultiplier, blurRadius: 10 + (200 * finalScale), ), diff --git a/lib/ui/widgets/library/multi_artwork_container.dart b/lib/ui/widgets/library/multi_artwork_container.dart index f653b457..2c79a859 100644 --- a/lib/ui/widgets/library/multi_artwork_container.dart +++ b/lib/ui/widgets/library/multi_artwork_container.dart @@ -15,6 +15,7 @@ class MultiArtworkContainer extends StatelessWidget { final EdgeInsetsGeometry? margin; final String heroTag; final bool fallbackToFolderCover; + final bool reduceQuality; const MultiArtworkContainer({ super.key, @@ -25,6 +26,7 @@ class MultiArtworkContainer extends StatelessWidget { this.onTopWidget, required this.heroTag, this.fallbackToFolderCover = true, + this.reduceQuality = false, }); @override @@ -63,6 +65,7 @@ class MultiArtworkContainer extends StatelessWidget { tracks: tracks!, thumbnailSize: size - 6.0, fallbackToFolderCover: fallbackToFolderCover, + reduceQuality: reduceQuality, ), if (child != null) child!, if (onTopWidget != null) onTopWidget!, diff --git a/lib/ui/widgets/library/queue_tile.dart b/lib/ui/widgets/library/queue_tile.dart index c9ed564d..7d1b83b8 100644 --- a/lib/ui/widgets/library/queue_tile.dart +++ b/lib/ui/widgets/library/queue_tile.dart @@ -77,6 +77,7 @@ class QueueTile extends StatelessWidget { heroTag: hero, size: Dimensions.queueThumbnailSize, tracks: queue.tracks.toImageTracks(), + reduceQuality: true, ), ), Column( diff --git a/lib/ui/widgets/video_widget.dart b/lib/ui/widgets/video_widget.dart index 1dd58110..390c2321 100644 --- a/lib/ui/widgets/video_widget.dart +++ b/lib/ui/widgets/video_widget.dart @@ -380,7 +380,7 @@ class NamidaVideoControlsState extends State with TickerPro }, decoration: const BoxDecoration(), borderRadius: 6.0, - bgColor: selected ? CurrentColor.inst.color.withAlpha(100) : null, + bgColor: selected ? CurrentColor.inst.miniplayerColor.withAlpha(100) : null, margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), padding: const EdgeInsets.all(6.0), child: Row( @@ -473,7 +473,7 @@ class NamidaVideoControlsState extends State with TickerPro ), Container( decoration: BoxDecoration( - color: CurrentColor.inst.color, + color: CurrentColor.inst.miniplayerColor, borderRadius: BorderRadius.circular(8.0.multipliedRadius), ), width: 4.0, @@ -788,7 +788,7 @@ class NamidaVideoControlsState extends State with TickerPro }, decoration: const BoxDecoration(), borderRadius: 6.0, - bgColor: isSelected ? CurrentColor.inst.color.withAlpha(100) : null, + bgColor: isSelected ? CurrentColor.inst.miniplayerColor.withAlpha(100) : null, margin: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), padding: const EdgeInsets.all(6.0), child: Row( @@ -1451,7 +1451,7 @@ class NamidaVideoControlsState extends State with TickerPro ), ), Obx( - () => Player.inst.isBuffering || Player.inst.isLoading + () => Player.inst.shouldShowLoadingIndicator ? ThreeArchedCircle( color: itemsColor, size: 52.0, diff --git a/lib/youtube/functions/download_sheet.dart b/lib/youtube/functions/download_sheet.dart index cb294ac7..8dc76f30 100644 --- a/lib/youtube/functions/download_sheet.dart +++ b/lib/youtube/functions/download_sheet.dart @@ -291,7 +291,7 @@ Future showDownloadVideoBottomSheet({ width: context.width * 0.2, height: context.width * 0.2 * 9 / 16, onImageReady: (imageFile) { - videoThumbnail.value = imageFile; + if (imageFile != null) videoThumbnail.value = imageFile; }, ), const SizedBox(width: 12.0), diff --git a/lib/youtube/pages/yt_playlist_download_subpage.dart b/lib/youtube/pages/yt_playlist_download_subpage.dart index b578c507..e09cc287 100644 --- a/lib/youtube/pages/yt_playlist_download_subpage.dart +++ b/lib/youtube/pages/yt_playlist_download_subpage.dart @@ -486,49 +486,45 @@ class _YTPlaylistDownloadPageState extends State { ), const SizedBox(width: 8.0), Obx( - () => AnimatedOpacity( - duration: const Duration(milliseconds: 300), - opacity: _selectedList.isEmpty ? 1 : 1.0, - child: FloatingActionButton.extended( - heroTag: 'download_fab', - backgroundColor: (_selectedList.isEmpty ? context.theme.disabledColor : CurrentColor.inst.color).withOpacity(1.0), - isExtended: true, - icon: Icon( - Broken.import_2, - size: 28.0, + () => FloatingActionButton.extended( + heroTag: 'download_fab', + backgroundColor: (_selectedList.isEmpty ? context.theme.disabledColor : CurrentColor.inst.color).withOpacity(1.0), + isExtended: true, + icon: Icon( + Broken.import_2, + size: 28.0, + color: Colors.white.withOpacity(0.7), + ), + label: Text( + lang.DOWNLOAD, + style: context.textTheme.displayMedium?.copyWith( color: Colors.white.withOpacity(0.7), ), - label: Text( - lang.DOWNLOAD, - style: context.textTheme.displayMedium?.copyWith( - color: Colors.white.withOpacity(0.7), - ), - ), - onPressed: () async { - if (_selectedList.isEmpty) return; - if (!await requestManageStoragePermission()) return; - NamidaNavigator.inst.popPage(); - YoutubeController.inst.downloadYoutubeVideos( - groupName: widget.playlistName, - itemsConfig: _selectedList.map((id) => _configMap[id] ?? _getDummyDownloadConfig(id)).toList(), - useCachedVersionsIfAvailable: true, - autoExtractTitleAndArtist: autoExtractTitleAndArtist, - keepCachedVersionsIfDownloaded: keepCachedVersionsIfDownloaded, - downloadFilesWriteUploadDate: downloadFilesWriteUploadDate, - addAudioToLocalLibrary: true, - deleteOldFile: overrideOldFiles, - audioOnly: downloadAudioOnly.value, - preferredQualities: () { - final list = []; - for (final q in kStockVideoQualities) { - list.add(q); - if (q == preferredQuality.value) break; - } - return list; - }(), - ); - }, ), + onPressed: () async { + if (_selectedList.isEmpty) return; + if (!await requestManageStoragePermission()) return; + NamidaNavigator.inst.popPage(); + YoutubeController.inst.downloadYoutubeVideos( + groupName: widget.playlistName, + itemsConfig: _selectedList.map((id) => _configMap[id] ?? _getDummyDownloadConfig(id)).toList(), + useCachedVersionsIfAvailable: true, + autoExtractTitleAndArtist: autoExtractTitleAndArtist, + keepCachedVersionsIfDownloaded: keepCachedVersionsIfDownloaded, + downloadFilesWriteUploadDate: downloadFilesWriteUploadDate, + addAudioToLocalLibrary: true, + deleteOldFile: overrideOldFiles, + audioOnly: downloadAudioOnly.value, + preferredQualities: () { + final list = []; + for (final q in kStockVideoQualities) { + list.add(q); + if (q == preferredQuality.value) break; + } + return list; + }(), + ); + }, ), ), ], diff --git a/lib/youtube/widgets/yt_thumbnail.dart b/lib/youtube/widgets/yt_thumbnail.dart index 72b8f9d2..21953c51 100644 --- a/lib/youtube/widgets/yt_thumbnail.dart +++ b/lib/youtube/widgets/yt_thumbnail.dart @@ -124,14 +124,21 @@ class _YoutubeThumbnailState extends State with LoadingItemsDe if (res == null) { await Future.delayed(Duration.zero); if (!await canStartLoadingItems()) return; - res = await ThumbnailManager.inst.getYoutubeThumbnailAndCache( - id: widget.videoId, - channelUrlOrID: finalChAvatarUrl, - hqChannelImage: fetchHQChImg, - isImportantInCache: widget.isImportantInCache, - // -- get lower res first - beforeFetchingFromInternet: () async { - if (widget.videoId == null) return; + if (widget.videoId != null) { + // -- for video: + // --- isImportantInCache -> fetch to file + // --- !isImportantInCache -> fetch lowres bytes only + if (widget.isImportantInCache) { + res = await ThumbnailManager.inst.getYoutubeThumbnailAndCache( + id: widget.videoId, + channelUrlOrID: finalChAvatarUrl, + hqChannelImage: fetchHQChImg, + isImportantInCache: true, + bytesIfWontWriteToFile: (bytes) { + if (mounted) setState(() => imageBytes = bytes); + }, + ); + } else { final lowerRes = await ThumbnailManager.inst.getYoutubeThumbnailAsBytes( youtubeId: widget.videoId, lowerResYTID: true, @@ -140,11 +147,19 @@ class _YoutubeThumbnailState extends State with LoadingItemsDe if (lowerRes != null && lowerRes.isNotEmpty) { if (mounted) setState(() => imageBytes = lowerRes); } - }, - bytesIfWontWriteToFile: (bytes) { - if (mounted) setState(() => imageBytes = bytes); - }, - ); + } + } else { + // for channels/playlists -> default + res = await ThumbnailManager.inst.getYoutubeThumbnailAndCache( + id: widget.videoId, + channelUrlOrID: finalChAvatarUrl, + hqChannelImage: fetchHQChImg, + isImportantInCache: widget.isImportantInCache, + bytesIfWontWriteToFile: (bytes) { + if (mounted) setState(() => imageBytes = bytes); + }, + ); + } } widget.onImageReady?.call(res); diff --git a/lib/youtube/youtube_miniplayer.dart b/lib/youtube/youtube_miniplayer.dart index 3769c9f1..32357e77 100644 --- a/lib/youtube/youtube_miniplayer.dart +++ b/lib/youtube/youtube_miniplayer.dart @@ -137,6 +137,8 @@ class YoutubeMiniPlayer extends StatelessWidget { YoutubeController.inst.downloadedFilesMap; // for refreshing. final downloadedFileExists = YoutubeController.inst.doesIDHasFileDownloaded(currentId) != null; + final defaultIconColor = context.defaultIconColor(CurrentColor.inst.miniplayerColor); + return NamidaYTMiniplayer( key: MiniPlayerController.inst.ytMiniplayerKey, duration: const Duration(milliseconds: 1000), @@ -636,15 +638,17 @@ class YoutubeMiniPlayer extends StatelessWidget { forceRequest: ConnectivityController.inst.hasConnection, ), child: YoutubeController.inst.isCurrentCommentsFromCache - ? const StackedIcon( + ? StackedIcon( baseIcon: Broken.refresh, secondaryIcon: Broken.global, iconSize: 20.0, secondaryIconSize: 12.0, + baseIconColor: defaultIconColor, + secondaryIconColor: defaultIconColor, ) : Icon( Broken.refresh, - color: context.defaultIconColor(), + color: defaultIconColor, size: 20.0, ), ) @@ -766,7 +770,7 @@ class YoutubeMiniPlayer extends StatelessWidget { ) : Icon( Broken.refresh, - color: context.defaultIconColor(), + color: defaultIconColor, ), ) ], @@ -963,7 +967,7 @@ class YoutubeMiniPlayer extends StatelessWidget { opacity: 0.3, child: ThreeArchedCircle( key: Key("${currentId}_button_loading_child"), - color: context.defaultIconColor(), + color: defaultIconColor, size: 36.0, ), ), @@ -979,12 +983,12 @@ class YoutubeMiniPlayer extends StatelessWidget { child: Player.inst.isPlaying ? Icon( Broken.pause, - color: context.defaultIconColor(), + color: defaultIconColor, key: const Key('pause'), ) : Icon( Broken.play, - color: context.defaultIconColor(), + color: defaultIconColor, key: const Key('play'), ), ), @@ -997,7 +1001,7 @@ class YoutubeMiniPlayer extends StatelessWidget { NamidaIconButton( horizontalPadding: 0.0, icon: Broken.next, - iconColor: context.defaultIconColor(), + iconColor: defaultIconColor, onPressed: () { Player.inst.next(); }, diff --git a/lib/youtube/yt_utils.dart b/lib/youtube/yt_utils.dart index 0bc8a453..5f362842 100644 --- a/lib/youtube/yt_utils.dart +++ b/lib/youtube/yt_utils.dart @@ -672,7 +672,7 @@ class YTUtils { snackyy( message: lang.COPIED_TO_CLIPBOARD, top: false, - leftBarIndicatorColor: CurrentColor.inst.color, + leftBarIndicatorColor: CurrentColor.inst.miniplayerColor, ); } }