diff --git a/lib/controller/history_controller.dart b/lib/controller/history_controller.dart index 6a2a2d6c..c41b4c44 100644 --- a/lib/controller/history_controller.dart +++ b/lib/controller/history_controller.dart @@ -11,11 +11,15 @@ import 'package:namida/core/constants.dart'; import 'package:namida/core/dimensions.dart'; import 'package:namida/core/enums.dart'; import 'package:namida/core/extensions.dart'; +import 'package:namida/ui/widgets/library/track_tile.dart'; class HistoryController with HistoryManager { static HistoryController get inst => _instance; static final HistoryController _instance = HistoryController._internal(); - HistoryController._internal(); + HistoryController._internal() { + onTopItemsMapModified = TrackTileManager.onTrackItemPropChange; + latestUpdatedMostPlayedItem.addListener(() => TrackTileManager.rebuildTrackInfo(latestUpdatedMostPlayedItem.value! /* not null bet */)); + } @override double daysToSectionExtent(List days) { diff --git a/lib/controller/indexer_controller.dart b/lib/controller/indexer_controller.dart index e04339c0..99d2bfbe 100644 --- a/lib/controller/indexer_controller.dart +++ b/lib/controller/indexer_controller.dart @@ -940,6 +940,10 @@ class Indexer { List? moods, int? lastPositionInMs, }) async { + if (rating != null || tags != null || moods != null) { + TrackTileManager.onTrackItemPropChange(); + } + rating ??= track.stats.rating; tags ??= track.stats.tags; moods ??= track.stats.moods; @@ -952,7 +956,6 @@ class Indexer { } Future _saveTrackStatsFileToStorage() async { - TrackTileManager.onTrackItemPropChange(); await File(AppPaths.TRACKS_STATS).writeAsJson(trackStatsMap.values.map((e) => e.toJson()).toList()); } diff --git a/lib/ui/widgets/library/track_tile.dart b/lib/ui/widgets/library/track_tile.dart index 9ddbebaf..81c94c7b 100644 --- a/lib/ui/widgets/library/track_tile.dart +++ b/lib/ui/widgets/library/track_tile.dart @@ -43,6 +43,8 @@ class TrackTilePropertiesProvider extends StatelessWidget { final backgroundColorNotPlaying = context.theme.cardTheme.color ?? Colors.transparent; final selectionColorLayer = context.theme.focusColor; + final listenToTopHistoryItems = settings.trackItem.values.any((element) => element == TrackTileItem.listenCount || element == TrackTileItem.latestListenDate); + return ObxO( rx: settings.forceSquaredTrackThumbnail, builder: (forceSquaredThumbnails) => ObxO( @@ -55,39 +57,43 @@ class TrackTilePropertiesProvider extends StatelessWidget { rx: settings.trackListTileHeight, builder: (trackTileHeight) => ObxO( rx: SelectedTracksController.inst.existingTracksMap, - builder: (selectedTracksMap) => ObxO( - rx: CurrentColor.inst.currentPlayingTrack, - builder: (currentPlayingTrack) => ObxO( - rx: CurrentColor.inst.currentPlayingIndex, - builder: (currentPlayingIndex) => Obx( - () { - int? sleepingIndex; - if (queueSource == QueueSource.playerQueue) { - final sleepconfig = Player.inst.sleepTimerConfig.valueR; - if (sleepconfig.enableSleepAfterItems) sleepingIndex = Player.inst.sleepingItemIndex(sleepconfig.sleepAfterItems, Player.inst.currentIndex.valueR); - } - - final backgroundColorPlaying = comingFromQueue ? CurrentColor.inst.miniplayerColor : CurrentColor.inst.currentColorScheme; - - final properties = TrackTileProperties( - backgroundColorPlaying: backgroundColorPlaying, - backgroundColorNotPlaying: backgroundColorNotPlaying, - selectionColorLayer: selectionColorLayer, - thumbnailSize: thumbnailSize, - trackTileHeight: trackTileHeight, - forceSquaredThumbnails: forceSquaredThumbnails, - sleepingIndex: sleepingIndex, - displayThirdRow: displayThirdRow, - displayFavouriteIconInListTile: displayFavouriteIconInListTile, - comingFromQueue: comingFromQueue, - configs: configs, - canHaveDuplicates: canHaveDuplicates, - currentPlayingTrack: currentPlayingTrack, - currentPlayingIndex: currentPlayingIndex, - isTrackSelected: (trOrTwd) => selectedTracksMap[trOrTwd.track] != null, - ); - return builder(properties); - }, + builder: (selectedTracksMap) => _ObxPrefer( + rx: HistoryController.inst.topTracksMapListens, + enabled: listenToTopHistoryItems, + builder: (_) => ObxO( + rx: CurrentColor.inst.currentPlayingTrack, + builder: (currentPlayingTrack) => ObxO( + rx: CurrentColor.inst.currentPlayingIndex, + builder: (currentPlayingIndex) => Obx( + () { + int? sleepingIndex; + if (queueSource == QueueSource.playerQueue) { + final sleepconfig = Player.inst.sleepTimerConfig.valueR; + if (sleepconfig.enableSleepAfterItems) sleepingIndex = Player.inst.sleepingItemIndex(sleepconfig.sleepAfterItems, Player.inst.currentIndex.valueR); + } + + final backgroundColorPlaying = comingFromQueue ? CurrentColor.inst.miniplayerColor : CurrentColor.inst.currentColorScheme; + + final properties = TrackTileProperties( + backgroundColorPlaying: backgroundColorPlaying, + backgroundColorNotPlaying: backgroundColorNotPlaying, + selectionColorLayer: selectionColorLayer, + thumbnailSize: thumbnailSize, + trackTileHeight: trackTileHeight, + forceSquaredThumbnails: forceSquaredThumbnails, + sleepingIndex: sleepingIndex, + displayThirdRow: displayThirdRow, + displayFavouriteIconInListTile: displayFavouriteIconInListTile, + comingFromQueue: comingFromQueue, + configs: configs, + canHaveDuplicates: canHaveDuplicates, + currentPlayingTrack: currentPlayingTrack, + currentPlayingIndex: currentPlayingIndex, + isTrackSelected: (trOrTwd) => selectedTracksMap[trOrTwd.track] != null, + ); + return builder(properties); + }, + ), ), ), ), @@ -100,6 +106,18 @@ class TrackTilePropertiesProvider extends StatelessWidget { } } +class _ObxPrefer extends StatelessWidget { + final RxBaseCore rx; + final Widget Function(T? value) builder; + final bool enabled; + const _ObxPrefer({required this.rx, required this.builder, required this.enabled, super.key}); + + @override + Widget build(BuildContext context) { + return enabled ? builder(null) : ObxO(rx: rx, builder: builder); + } +} + class TrackTilePropertiesConfigs { final QueueSource queueSource; @@ -221,15 +239,15 @@ class TrackTile extends StatelessWidget { final isInSelectedTracksPreview = queueSource == QueueSource.selectedTracks; final additionalHero = this.additionalHero; final thirdLineText = this.thirdLineText; - final row1Text = TrackTileManager.joinTrackItems(TrackTilePosition.row1Item1, TrackTilePosition.row1Item2, TrackTilePosition.row1Item3, track); - final row2Text = TrackTileManager.joinTrackItems(TrackTilePosition.row2Item1, TrackTilePosition.row2Item2, TrackTilePosition.row2Item3, track); + final row1Text = TrackTileManager._joinTrackItems(_TrackTileRowOrder.first, track); + final row2Text = TrackTileManager._joinTrackItems(_TrackTileRowOrder.second, track); final row3Text = thirdLineText != null && thirdLineText.isNotEmpty ? thirdLineText : properties.displayThirdRow - ? TrackTileManager.joinTrackItems(TrackTilePosition.row3Item1, TrackTilePosition.row3Item2, TrackTilePosition.row3Item3, track) + ? TrackTileManager._joinTrackItems(_TrackTileRowOrder.third, track) : null; - final rightItem1Text = TrackTileManager.getChoosenTrackTileItem(TrackTilePosition.rightItem1, track); - final rightItem2Text = TrackTileManager.getChoosenTrackTileItem(TrackTilePosition.rightItem2, track); + final rightItem1Text = TrackTileManager._joinTrackItems(_TrackTileRowOrder.right1, track); + final rightItem2Text = TrackTileManager._joinTrackItems(_TrackTileRowOrder.right2, track); final willSleepAfterThis = properties.sleepingIndex == index; @@ -507,69 +525,101 @@ class TrackTile extends StatelessWidget { } } +enum _TrackTileRowOrder { + first, + second, + third, + + right1, + right2, +} + class TrackTileManager { - static final _infoMap = >{}; + const TrackTileManager._(); + + static const _rowOrderToPosition = <_TrackTileRowOrder, List>{ + _TrackTileRowOrder.first: [TrackTilePosition.row1Item1, TrackTilePosition.row1Item2, TrackTilePosition.row1Item3], + _TrackTileRowOrder.second: [TrackTilePosition.row2Item1, TrackTilePosition.row2Item2, TrackTilePosition.row2Item3], + _TrackTileRowOrder.third: [TrackTilePosition.row3Item1, TrackTilePosition.row3Item2, TrackTilePosition.row3Item3], + _TrackTileRowOrder.right1: [TrackTilePosition.rightItem1], + _TrackTileRowOrder.right2: [TrackTilePosition.rightItem2], + }; + static const _rowOrderToPositionWithoutThird = <_TrackTileRowOrder, List>{ + _TrackTileRowOrder.first: [TrackTilePosition.row1Item1, TrackTilePosition.row1Item2], + _TrackTileRowOrder.second: [TrackTilePosition.row2Item1, TrackTilePosition.row2Item2], + _TrackTileRowOrder.third: [TrackTilePosition.row3Item1, TrackTilePosition.row3Item2], + _TrackTileRowOrder.right1: [TrackTilePosition.rightItem1], + _TrackTileRowOrder.right2: [TrackTilePosition.rightItem2], + }; + + static final _infoFullMap = ?>{}; static void onTrackItemPropChange() { - _infoMap.clear(); + _infoFullMap.clear(); _separator = _buildSeparator(); } + static void rebuildTrackInfo(Track track) { + _infoFullMap[track] = null; + } + static String _separator = _buildSeparator(); static String _buildSeparator() => ' ${settings.trackTileSeparator.value} '; + static final _buffer = StringBuffer(); // clearing and reusing is more performant + + static String _joinTrackItems( + _TrackTileRowOrder rowOrder, + Track track, + ) { + final row = _infoFullMap[track]?[rowOrder]; + if (row != null) return row; + + final positions = settings.displayThirdItemInEachRow.value ? _rowOrderToPosition[rowOrder] : _rowOrderToPositionWithoutThird[rowOrder]; + final newRowDetails = _joinTrackItemsInternal(positions!, track); + final newRow = newRowDetails.text; + + if (newRowDetails.shouldNotCache) return newRow; + + final innerMap = _infoFullMap[track] ??= {}; + innerMap[rowOrder] = newRow; + + return newRow; + } + + static ({String text, bool shouldNotCache}) _joinTrackItemsInternal(List positions, Track track) { + _buffer.clear(); - static String joinTrackItems(TrackTilePosition? p1, TrackTilePosition? p2, TrackTilePosition? p3, Track track) { - var buffer = StringBuffer(); bool needsSeparator = false; - if (p1 != null) { - var info = getChoosenTrackTileItem(p1, track); - if (info.isNotEmpty) { - buffer.write(info); - needsSeparator = true; - } - } - if (p2 != null) { - var info = getChoosenTrackTileItem(p2, track); - if (info.isNotEmpty) { - if (needsSeparator) buffer.write(_separator); - buffer.write(info); - needsSeparator = true; - } - } - if (p3 != null && settings.displayThirdItemInEachRow.value) { - var info = getChoosenTrackTileItem(p3, track); + bool shouldNotCache = false; + + final length = positions.length; + for (int i = 0; i < length; i++) { + final itemPosition = positions[i]; + final trackItem = settings.trackItem.value[itemPosition]; + if (trackItem == TrackTileItem.latestListenDate) shouldNotCache = true; + + var info = _buildChoosenTrackTileItem(trackItem, track); + if (info.isNotEmpty) { - if (needsSeparator) buffer.write(_separator); - buffer.write(info); + if (needsSeparator) _buffer.write(_separator); + _buffer.write(info); needsSeparator = true; } } - return buffer.toString(); - } - static String getChoosenTrackTileItem(TrackTilePosition itemPosition, Track trackPre) { - final inf = _infoMap[trackPre]?[itemPosition]; - if (inf != null) return inf; + return (text: _buffer.toString(), shouldNotCache: shouldNotCache); + } - String val; + static String _buildChoosenTrackTileItem(TrackTileItem? trackItem, Track trackPre) { + if (trackItem == null || trackItem == TrackTileItem.none) return ''; - final trackItem = settings.trackItem.value[itemPosition]; - if (trackItem == null || trackItem == TrackTileItem.none) { - val = ''; - } else { - final fn = _lookup[trackItem]; - if (fn != null) { - final track = trackPre.toTrackExt(); - val = fn(track); - } else { - val = ''; - } + final fn = _lookup[trackItem]; + if (fn != null) { + final track = trackPre.toTrackExt(); + return fn(track); } - _infoMap[trackPre] ??= {}; - _infoMap[trackPre]![itemPosition] = val; - - return val; + return ''; } static final _lookup = { diff --git a/pubspec.yaml b/pubspec.yaml index 9c279daa..778ad101 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: namida description: A Beautiful and Feature-rich Music Player, With YouTube & Video Support Built in Flutter publish_to: "none" -version: 3.9.2-beta+240815234 +version: 3.9.22-beta+240816204 environment: sdk: ">=3.4.0 <4.0.0"