Skip to content

Commit

Permalink
feat: heatmap view for listens
Browse files Browse the repository at this point in the history
  • Loading branch information
MSOB7YY committed Nov 1, 2023
1 parent 39d13c4 commit 8fe2959
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 97 deletions.
7 changes: 7 additions & 0 deletions lib/controller/settings_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class SettingsController {
final RxString defaultFolderStartupLocation = kStoragePaths.first.obs;
final RxBool enableFoldersHierarchy = true.obs;
final RxBool displayArtistBeforeTitle = true.obs;
final RxBool heatmapListensView = false.obs;
final RxList<String> backupItemslist = [
AppPaths.TRACKS,
AppPaths.TRACKS_STATS,
Expand Down Expand Up @@ -361,6 +362,7 @@ class SettingsController {
defaultFolderStartupLocation.value = json['defaultFolderStartupLocation'] ?? defaultFolderStartupLocation.value;
enableFoldersHierarchy.value = json['enableFoldersHierarchy'] ?? enableFoldersHierarchy.value;
displayArtistBeforeTitle.value = json['displayArtistBeforeTitle'] ?? displayArtistBeforeTitle.value;
heatmapListensView.value = json['heatmapListensView'] ?? heatmapListensView.value;
backupItemslist.value = List<String>.from(json['backupItemslist'] ?? backupItemslist);
enableVideoPlayback.value = json['enableVideoPlayback'] ?? enableVideoPlayback.value;
enableLyrics.value = json['enableLyrics'] ?? enableLyrics.value;
Expand Down Expand Up @@ -565,6 +567,7 @@ class SettingsController {
'defaultFolderStartupLocation': defaultFolderStartupLocation.value,
'enableFoldersHierarchy': enableFoldersHierarchy.value,
'displayArtistBeforeTitle': displayArtistBeforeTitle.value,
'heatmapListensView': heatmapListensView.value,
'backupItemslist': backupItemslist.toList(),
'enableVideoPlayback': enableVideoPlayback.value,
'enableLyrics': enableLyrics.value,
Expand Down Expand Up @@ -731,6 +734,7 @@ class SettingsController {
String? defaultFolderStartupLocation,
bool? enableFoldersHierarchy,
bool? displayArtistBeforeTitle,
bool? heatmapListensView,
List<String>? backupItemslist,
bool? enableVideoPlayback,
bool? enableLyrics,
Expand Down Expand Up @@ -1043,6 +1047,9 @@ class SettingsController {
if (displayArtistBeforeTitle != null) {
this.displayArtistBeforeTitle.value = displayArtistBeforeTitle;
}
if (heatmapListensView != null) {
this.heatmapListensView.value = heatmapListensView;
}
if (backupItemslist != null) {
backupItemslist.loop((d, index) {
if (!this.backupItemslist.contains(d)) {
Expand Down
181 changes: 147 additions & 34 deletions lib/ui/dialogs/track_listens_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,64 @@
import 'dart:async';

import 'package:flutter/material.dart';

import 'package:get/get.dart';
import 'package:jiffy/jiffy.dart';
import 'package:paged_vertical_calendar/paged_vertical_calendar.dart';

import 'package:namida/class/track.dart';
import 'package:namida/controller/current_color.dart';
import 'package:namida/controller/history_controller.dart';
import 'package:namida/controller/navigator_controller.dart';
import 'package:namida/controller/settings_controller.dart';
import 'package:namida/core/dimensions.dart';
import 'package:namida/core/extensions.dart';
import 'package:namida/core/functions.dart';
import 'package:namida/core/icon_fonts/broken_icons.dart';
import 'package:namida/core/translations/language.dart';
import 'package:namida/ui/widgets/custom_widgets.dart';

void showTrackListensDialog(Track track, {List<int>? datesOfListen, Color? colorScheme}) async {
datesOfListen ??= HistoryController.inst.topTracksMapListens[track] ?? [];
final color = colorScheme ?? await CurrentColor.inst.getTrackDelightnedColor(track);
void showTrackListensDialog(Track track, {List<int> datesOfListen = const [], Color? colorScheme}) async {
showListensDialog(
datesOfListen: datesOfListen.isNotEmpty ? datesOfListen : HistoryController.inst.topTracksMapListens[track] ?? [],
colorScheme: () async => colorScheme ?? await CurrentColor.inst.getTrackDelightnedColor(track),
onListenTap: (listen) {
final scrollInfo = HistoryController.inst.getListenScrollPosition(
listenMS: listen,
extraItemsOffset: 2,
);
NamidaOnTaps.inst.onHistoryPlaylistTap(
indexToHighlight: scrollInfo.indexOfSmallList,
dayOfHighLight: scrollInfo.dayToHighLight,
initialScrollOffset: (scrollInfo.itemsToScroll * Dimensions.inst.trackTileItemExtent) + (scrollInfo.daysToScroll * kHistoryDayHeaderHeightWithPadding),
);
},
);
}

void showListensDialog({
required List<int> datesOfListen,
required FutureOr<Color?> Function() colorScheme,
required void Function(int listen) onListenTap,
}) async {
if (datesOfListen.isEmpty) return;
datesOfListen.sortByReverse((e) => e);

final color = await colorScheme() ?? CurrentColor.inst.color;

final datesMapByDay = <DateTime, List<DateTime>>{};
final datesMapByMonth = <DateTime, List<DateTime>>{};
for (final d in datesOfListen) {
final date = DateTime.fromMillisecondsSinceEpoch(d);
final dayDate = DateTime(date.year, date.month, date.day);
final monthDate = DateTime(date.year, date.month);
datesMapByDay.addForce(dayDate, date);
datesMapByMonth.addForce(monthDate, date);
}

final firstListen = DateTime.fromMillisecondsSinceEpoch(datesOfListen.last);
final lastListen = DateTime.fromMillisecondsSinceEpoch(datesOfListen.first);

NamidaNavigator.inst.navigateDialog(
colorScheme: color,
lighterDialogColor: false,
Expand All @@ -28,43 +68,116 @@ void showTrackListensDialog(Track track, {List<int>? datesOfListen, Color? color
title: lang.TOTAL_LISTENS,
trailingWidgets: [
Text(
'${datesOfListen!.length}',
'${datesOfListen.length}',
style: Get.textTheme.displaySmall?.copyWith(color: theme.colorScheme.primary, fontWeight: FontWeight.w600),
),
const SizedBox(width: 8.0),
Obx(
() => NamidaIconButton(
icon: settings.heatmapListensView.value ? Broken.row_vertical : Broken.calendar_1,
iconSize: settings.heatmapListensView.value ? 18.0 : 20.0,
onPressed: () => settings.save(heatmapListensView: !settings.heatmapListensView.value),
),
),
],
child: SizedBox(
height: Get.height * 0.5,
width: Get.width,
child: NamidaListView(
padding: EdgeInsets.zero,
itemBuilder: (context, i) {
final t = datesOfListen![i];
return SmallListTile(
key: ValueKey(i),
borderRadius: 14.0,
title: t.dateAndClockFormattedOriginal,
leading: Container(
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 1.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0.multipliedRadius),
color: theme.cardColor,
),
child: Text((datesOfListen.length - i).toString())),
onTap: () async {
final scrollInfo = HistoryController.inst.getListenScrollPosition(
listenMS: t,
extraItemsOffset: 2,
);
NamidaOnTaps.inst.onHistoryPlaylistTap(
indexToHighlight: scrollInfo.indexOfSmallList,
dayOfHighLight: scrollInfo.dayToHighLight,
initialScrollOffset: (scrollInfo.itemsToScroll * Dimensions.inst.trackTileItemExtent) + (scrollInfo.daysToScroll * kHistoryDayHeaderHeightWithPadding),
);
},
);
},
itemCount: datesOfListen.length,
itemExtents: null,
child: Obx(
() => settings.heatmapListensView.value
? Padding(
padding: const EdgeInsets.all(12.0),
child: PagedVerticalCalendar(
minDate: firstListen.subtract(const Duration(days: 8)),
maxDate: lastListen.add(const Duration(days: 8)),
initialDate: lastListen,
invisibleMonthsThreshold: 3,
startWeekWithSunday: true,
onDayPressed: (value) => datesMapByDay[value] == null ? null : () => onListenTap(value.millisecondsSinceEpoch),
monthBuilder: (context, month, year) {
final monthDate = DateTime(year, month);
final monthListens = datesMapByMonth[monthDate]?.length ?? 0;
final monthListensText = monthListens > 0 ? ' ($monthListens)' : '';
final dots = (monthListens / 5).ceil();
final monthsDateJiffy = Jiffy.parseFromDateTime(monthDate);
final monthsAgo = monthsDateJiffy.fromNow();
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.filled(
dots,
Padding(
padding: const EdgeInsets.all(2.0),
child: CircleAvatar(
backgroundColor: color,
maxRadius: 5.0,
minRadius: 2.0,
),
),
),
),
),
Text(
monthsDateJiffy.format(pattern: 'MMMM yyyy'),
style: Get.textTheme.titleLarge,
),
Text(
'$monthsAgo$monthListensText',
style: Get.textTheme.displaySmall,
),
],
),
);
},
dayBuilder: (context, date) {
final isToday = date.toDaysSince1970() == DateTime.now().toDaysSince1970();
final listens = datesMapByDay[date]?.length ?? 0;
return NamidaInkWell(
decoration: BoxDecoration(border: isToday ? Border.all(color: color) : null),
margin: const EdgeInsets.all(2.0),
bgColor: color.withAlpha((listens * 5).clamp(0, 255)), // *5 since 50 listens a days is already a lot
borderRadius: 6.0,
onTap: datesMapByDay[date] == null ? null : () => onListenTap(date.millisecondsSinceEpoch),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("${date.day}", style: Get.textTheme.displaySmall),
if (listens > 0) ...[
const SizedBox(height: 2.0),
Text("$listens", style: Get.textTheme.displaySmall?.copyWith(fontSize: 9.0.multipliedFontScale)),
]
],
),
);
},
))
: NamidaListView(
padding: EdgeInsets.zero,
itemBuilder: (context, i) {
final t = datesOfListen[i];
return SmallListTile(
key: ValueKey(i),
borderRadius: 14.0,
title: t.dateAndClockFormattedOriginal,
subtitle: Jiffy.parseFromMillisecondsSinceEpoch(t).fromNow(),
leading: Container(
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 1.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0.multipliedRadius),
color: theme.cardColor,
),
child: Text((datesOfListen.length - i).toString())),
onTap: () => onListenTap(t),
);
},
itemCount: datesOfListen.length,
itemExtents: null,
),
),
),
),
Expand Down
81 changes: 18 additions & 63 deletions lib/youtube/functions/video_listens_dialog.dart
Original file line number Diff line number Diff line change
@@ -1,73 +1,28 @@
import 'package:flutter/material.dart';

import 'package:get/get.dart';

import 'package:namida/controller/navigator_controller.dart';
import 'package:namida/core/dimensions.dart';
import 'package:namida/core/extensions.dart';
import 'package:namida/core/translations/language.dart';
import 'package:namida/ui/widgets/custom_widgets.dart';
import 'package:namida/ui/dialogs/track_listens_dialog.dart';
import 'package:namida/youtube/controller/youtube_history_controller.dart';
import 'package:namida/youtube/yt_utils.dart';

void showVideoListensDialog(String videoId, {List<int>? datesOfListen, Color? colorScheme}) async {
datesOfListen ??= YoutubeHistoryController.inst.topTracksMapListens[videoId] ?? [];

if (datesOfListen.isEmpty) return;
datesOfListen.sortByReverse((e) => e);

NamidaNavigator.inst.navigateDialog(
colorScheme: colorScheme,
lighterDialogColor: false,
dialogBuilder: (theme) => CustomBlurryDialog(
theme: theme,
normalTitleStyle: true,
title: lang.TOTAL_LISTENS,
trailingWidgets: [
Text(
'${datesOfListen!.length}',
style: Get.textTheme.displaySmall?.copyWith(color: theme.colorScheme.primary, fontWeight: FontWeight.w600),
),
],
child: SizedBox(
height: Get.height * 0.5,
width: Get.width,
child: NamidaListView(
padding: EdgeInsets.zero,
itemBuilder: (context, i) {
final t = datesOfListen![i];
return SmallListTile(
key: ValueKey(i),
borderRadius: 14.0,
title: t.dateAndClockFormattedOriginal,
leading: Container(
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 1.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0.multipliedRadius),
color: theme.cardColor,
),
child: Text((datesOfListen.length - i).toString())),
onTap: () async {
final scrollInfo = YoutubeHistoryController.inst.getListenScrollPosition(
listenMS: t,
extraItemsOffset: 2,
);
void showVideoListensDialog(String videoId, {List<int> datesOfListen = const [], Color? colorScheme}) async {
showListensDialog(
datesOfListen: datesOfListen.isNotEmpty ? datesOfListen : YoutubeHistoryController.inst.topTracksMapListens[videoId] ?? [],
colorScheme: () => colorScheme,
onListenTap: (listen) {
final scrollInfo = YoutubeHistoryController.inst.getListenScrollPosition(
listenMS: listen,
extraItemsOffset: 2,
);

final totalItemsExtent = scrollInfo.itemsToScroll * Dimensions.youtubeCardItemExtent;
final totalDaysExtent = scrollInfo.daysToScroll * kYoutubeHistoryDayHeaderHeightWithPadding;
final totalItemsExtent = scrollInfo.itemsToScroll * Dimensions.youtubeCardItemExtent;
final totalDaysExtent = scrollInfo.daysToScroll * kYoutubeHistoryDayHeaderHeightWithPadding;

YTUtils.onYoutubeHistoryPlaylistTap(
indexToHighlight: scrollInfo.indexOfSmallList,
dayOfHighLight: scrollInfo.dayToHighLight,
initialScrollOffset: totalItemsExtent + totalDaysExtent,
);
},
);
},
itemCount: datesOfListen.length,
itemExtents: null,
),
),
),
YTUtils.onYoutubeHistoryPlaylistTap(
indexToHighlight: scrollInfo.indexOfSmallList,
dayOfHighLight: scrollInfo.dayToHighLight,
initialScrollOffset: totalItemsExtent + totalDaysExtent,
);
},
);
}
24 changes: 24 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.3"
infinite_scroll_pagination:
dependency: transitive
description:
name: infinite_scroll_pagination
sha256: "9517328f4e373f08f57dbb11c5aac5b05554142024d6b60c903f3b73476d52db"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
intl:
dependency: "direct main"
description:
Expand Down Expand Up @@ -743,6 +751,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
paged_vertical_calendar:
dependency: "direct main"
description:
name: paged_vertical_calendar
sha256: bca7385d0b810d9c010616515d75429ea9e89290316f928f6a7bb9af3ae01ac4
url: "https://pub.dev"
source: hosted
version: "1.1.6"
palette_generator:
dependency: "direct main"
description:
Expand Down Expand Up @@ -1007,6 +1023,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
sliver_tools:
dependency: transitive
description:
name: sliver_tools
sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6
url: "https://pub.dev"
source: hosted
version: "0.2.12"
source_span:
dependency: transitive
description:
Expand Down
Loading

0 comments on commit 8fe2959

Please sign in to comment.