Skip to content

Commit

Permalink
feat: notifications for windows with option to control download notif…
Browse files Browse the repository at this point in the history
…ications

ref: #14
  • Loading branch information
MSOB7YY committed Jan 9, 2025
1 parent e34580f commit 667e15a
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 64 deletions.
4 changes: 2 additions & 2 deletions lib/controller/json_to_history_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ class JsonToHistoryParser {
final startTime = DateTime.now();
_notificationTimer?.cancel();
_notificationTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
NotificationService.importHistoryNotification(parsedHistoryJson.value, totalJsonToParse.value, startTime);
NotificationManager.instance.importHistoryNotification(parsedHistoryJson.value, totalJsonToParse.value, startTime);
});

final datesAdded = <int>[];
Expand Down Expand Up @@ -481,7 +481,7 @@ class JsonToHistoryParser {
isParsing.value = false;

_notificationTimer?.cancel();
NotificationService.doneImportingHistoryNotification(parsedHistoryJson.value, addedHistoryJsonToPlaylist.value);
NotificationManager.instance.doneImportingHistoryNotification(parsedHistoryJson.value, addedHistoryJsonToPlaylist.value);

_latestMissingMap.value = allMissingEntries;
_latestMissingMapAddedStatus.clear();
Expand Down
88 changes: 74 additions & 14 deletions lib/controller/notification_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@ import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

import 'package:namida/controller/json_to_history_parser.dart';
import 'package:namida/controller/platform/base.dart';
import 'package:namida/controller/settings_controller.dart';
import 'package:namida/core/constants.dart';
import 'package:namida/core/enums.dart';
import 'package:namida/core/extensions.dart';
import 'package:namida/youtube/class/download_task_base.dart';

class NotificationService {
static final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
class NotificationManager {
static final instance = NotificationManager._platform();
const NotificationManager._();

static Future<void> cancelAll() async {
try {
return _flutterLocalNotificationsPlugin.cancelAll();
} catch (_) {}
static NotificationManager _platform() {
return NamidaPlatformBuilder.init(
android: () => const NotificationManager._(),
windows: () => const _NotificationManagerSuppressed._(),
);
}

static final _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

static const _historyImportID = 1;
static const _historyImportPayload = 'history_import';
static const _historyImportChannelName = 'History Import';
Expand All @@ -26,13 +34,21 @@ class NotificationService {
static const _youtubeDownloadChannelDescription = 'Downlaod content from youtube';

static Future<bool?> init() {
return _flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
final didInit = _flutterLocalNotificationsPlugin.initialize(
InitializationSettings(
android: AndroidInitializationSettings('ic_stat_musicnote'),
windows: WindowsInitializationSettings(
appName: 'Namida',
appUserModelId: 'namidaco.namida.notification',
guid: '51435cfe-f7be-4a73-82c1-50d53a8e7ae6',
iconPath: AppPaths.NAMIDA_LOGO_MONET,
),
),
onDidReceiveBackgroundNotificationResponse: _onDidReceiveLocalNotification,
onDidReceiveNotificationResponse: _onDidReceiveLocalNotification,
);
_flutterLocalNotificationsPlugin.cancelAll();
return didInit;
}

static void mediaNotification({
Expand Down Expand Up @@ -84,7 +100,7 @@ class NotificationService {
);
}

static void downloadYoutubeNotification({
void downloadYoutubeNotification({
required DownloadTaskFilename filenameWrapper,
required String title,
required String Function(String progressText) subtitle,
Expand All @@ -111,11 +127,11 @@ class NotificationService {
);
}

static Future<void> removeDownloadingYoutubeNotification({required DownloadTaskFilename filenameWrapper}) async {
Future<void> removeDownloadingYoutubeNotification({required DownloadTaskFilename filenameWrapper}) async {
await _flutterLocalNotificationsPlugin.cancel(_youtubeDownloadID, tag: filenameWrapper.key);
}

static void doneDownloadingYoutubeNotification({
void doneDownloadingYoutubeNotification({
required DownloadTaskFilename filenameWrapper,
required String videoTitle,
required String subtitle,
Expand All @@ -139,7 +155,7 @@ class NotificationService {
);
}

static void importHistoryNotification(int parsed, int total, DateTime displayTime) {
void importHistoryNotification(int parsed, int total, DateTime displayTime) {
_createProgressNotification(
id: _historyImportID,
progress: parsed,
Expand All @@ -154,7 +170,7 @@ class NotificationService {
);
}

static void doneImportingHistoryNotification(int totalParsed, int totalAdded) {
void doneImportingHistoryNotification(int totalParsed, int totalAdded) {
_createNotification(
id: _historyImportID,
title: 'Done importing history',
Expand All @@ -174,7 +190,7 @@ class NotificationService {
}
}

static void _createNotification({
void _createNotification({
required int id,
required String title,
required String body,
Expand Down Expand Up @@ -270,3 +286,47 @@ class NotificationService {
);
}
}

class _NotificationManagerSuppressed extends NotificationManager {
const _NotificationManagerSuppressed._() : super._();

DownloadNotifications get _downloadNotifications => settings.youtube.downloadNotifications.value;

@override
void downloadYoutubeNotification({
required DownloadTaskFilename filenameWrapper,
required String title,
required String Function(String progressText) subtitle,
String? imagePath,
required int progress,
required int total,
required DateTime displayTime,
required bool isRunning,
}) {
return;
}

@override
void doneDownloadingYoutubeNotification({
required DownloadTaskFilename filenameWrapper,
required String videoTitle,
required String subtitle,
required bool failed,
String? imagePath,
}) async {
if (_downloadNotifications == DownloadNotifications.disableAll) return;
if (_downloadNotifications == DownloadNotifications.showFailedOnly && !failed) return;
super.doneDownloadingYoutubeNotification(
filenameWrapper: filenameWrapper,
videoTitle: videoTitle,
subtitle: subtitle,
failed: failed,
imagePath: imagePath,
);
}

@override
void importHistoryNotification(int parsed, int total, DateTime displayTime) {
return;
}
}
7 changes: 7 additions & 0 deletions lib/controller/settings.youtube.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class _YoutubeSettings with SettingsFileWriter {
final downloadFilenameBuilder = _defaultFilenameBuilder.obs;
final initialDefaultMetadataTags = <String, String>{};

// -- currently used for windows
final downloadNotifications = DownloadNotifications.showFailedOnly.obs;

bool markVideoWatched = true;
InnertubeClients? innertubeClient;
bool whiteVideoBGInLightMode = false;
Expand All @@ -55,6 +58,7 @@ class _YoutubeSettings with SettingsFileWriter {
YTSeekActionMode? tapToSeek,
YTSeekActionMode? dragToSeek,
String? downloadFilenameBuilder,
DownloadNotifications? downloadNotifications,
bool? markVideoWatched,
InnertubeClients? innertubeClient,
bool setDefaultInnertubeClient = false,
Expand All @@ -81,6 +85,7 @@ class _YoutubeSettings with SettingsFileWriter {
if (tapToSeek != null) this.tapToSeek.value = tapToSeek;
if (dragToSeek != null) this.dragToSeek.value = dragToSeek;
if (downloadFilenameBuilder != null) this.downloadFilenameBuilder.value = downloadFilenameBuilder;
if (downloadNotifications != null) this.downloadNotifications.value = downloadNotifications;

if (markVideoWatched != null) this.markVideoWatched = markVideoWatched;
if (innertubeClient != null || setDefaultInnertubeClient) this.innertubeClient = innertubeClient;
Expand Down Expand Up @@ -144,6 +149,7 @@ class _YoutubeSettings with SettingsFileWriter {
ytVisibleShorts.value = (json['ytVisibleShorts'] as Map?)?.map((key, value) => MapEntry(YTVisibleShortPlaces.values.getEnum(key)!, value)) ?? ytVisibleShorts.value;
ytVisibleMixes.value = (json['ytVisibleMixes'] as Map?)?.map((key, value) => MapEntry(YTVisibleMixesPlaces.values.getEnum(key)!, value)) ?? ytVisibleMixes.value;
downloadFilenameBuilder.value = json['downloadFilenameBuilder'] ?? downloadFilenameBuilder.value;
downloadNotifications.value = DownloadNotifications.values.getEnum(json['downloadNotifications']) ?? downloadNotifications.value;

final initialDefaultMetadataTagsInStorage = (json['initialDefaultMetadataTags'] as Map?);
if (initialDefaultMetadataTagsInStorage != null) {
Expand Down Expand Up @@ -184,6 +190,7 @@ class _YoutubeSettings with SettingsFileWriter {
'ytVisibleShorts': ytVisibleShorts.map((key, value) => MapEntry(key.name, value)),
'ytVisibleMixes': ytVisibleMixes.map((key, value) => MapEntry(key.name, value)),
'downloadFilenameBuilder': downloadFilenameBuilder.value,
'downloadNotifications': downloadNotifications.value.name,
'initialDefaultMetadataTags': initialDefaultMetadataTags,
'markVideoWatched': markVideoWatched,
'innertubeClient': innertubeClient?.name,
Expand Down
3 changes: 3 additions & 0 deletions lib/core/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ extension PathTypeUtils on String {
class NamidaFeaturesVisibility {
static final _platform = defaultTargetPlatform;
static final _isAndroid = _platform == TargetPlatform.android;
static final _isWindows = _platform == TargetPlatform.windows;

static final wallpaperColors = _isAndroid && NamidaDeviceInfo.sdkVersion >= 31;
static final displayArtworkOnLockscreen = _isAndroid && NamidaDeviceInfo.sdkVersion < 33;
Expand All @@ -534,4 +535,6 @@ class NamidaFeaturesVisibility {

static final onAudioQueryAvailable = _isAndroid;
static final recieveSharingIntents = _isAndroid;

static final showDownloadNotifications = _isWindows;
}
6 changes: 6 additions & 0 deletions lib/core/enums.dart
Original file line number Diff line number Diff line change
Expand Up @@ -443,3 +443,9 @@ enum YTSortType {
latestPlayed,
firstListen,
}

enum DownloadNotifications {
disableAll,
showAll,
showFailedOnly,
}
9 changes: 9 additions & 0 deletions lib/core/namida_converter_ext.dart
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,10 @@ extension PlaylistPrivacyUtils on PlaylistPrivacy {
String toText() => _NamidaConverters.inst.getTitle(this);
}

extension DownloadNotificationsUtils on DownloadNotifications {
String toText() => _NamidaConverters.inst.getTitle(this);
}

extension RouteUtils on NamidaRoute {
List<Selectable> tracksListInside() {
final iter = tracksInside();
Expand Down Expand Up @@ -1397,6 +1401,11 @@ class _NamidaConverters {
PlaylistPrivacy.unlisted: lang.UNLISTED,
PlaylistPrivacy.private: lang.PRIVATE,
},
DownloadNotifications: {
DownloadNotifications.disableAll: lang.DISABLE_ALL,
DownloadNotifications.showAll: lang.SHOW_ALL,
DownloadNotifications.showFailedOnly: lang.SHOW_FAILED_ONLY,
},
};

// ====================================================
Expand Down
3 changes: 3 additions & 0 deletions lib/core/translations/keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ abstract class LanguageKeys {
String get DIM_INTENSITY => _getKey('DIM_INTENSITY');
String get DIM_MINIPLAYER_AFTER_SECONDS => _getKey('DIM_MINIPLAYER_AFTER_SECONDS');
String get DIRECTORY_DOESNT_EXIST => _getKey('DIRECTORY_DOESNT_EXIST');
String get DISABLE_ALL => _getKey('DISABLE_ALL');
String get DISABLE_REORDERING => _getKey('DISABLE_REORDERING');
String get DISABLE_SEARCH_CLEANUP => _getKey('DISABLE_SEARCH_CLEANUP');
String get DISC_NUMBER_TOTAL => _getKey('DISC_NUMBER_TOTAL');
Expand Down Expand Up @@ -601,7 +602,9 @@ abstract class LanguageKeys {
String get SHOULD_DUCK_NOTE => _getKey('SHOULD_DUCK_NOTE');
String get SHOULD_PAUSE => _getKey('SHOULD_PAUSE');
String get SHOULD_PAUSE_NOTE => _getKey('SHOULD_PAUSE_NOTE');
String get SHOW_ALL => _getKey('SHOW_ALL');
String get SHOW_CHANNEL_WATERMARK_IN_FULLSCREEN => _getKey('SHOW_CHANNEL_WATERMARK_IN_FULLSCREEN');
String get SHOW_FAILED_ONLY => _getKey('SHOW_FAILED_ONLY');
String get SHOW_HIDE_UNKNOWN_FIELDS => _getKey('SHOW_HIDE_UNKNOWN_FIELDS');
String get SHOW_MIX_PLAYLISTS_IN => _getKey('SHOW_MIX_PLAYLISTS_IN');
String get SHOW_MORE => _getKey('SHOW_MORE');
Expand Down
3 changes: 1 addition & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,7 @@ void mainInitialization() async {
NamidaNavigator.inst.setDefaultSystemUIOverlayStyle();

ScrollSearchController.inst.initialize();
NotificationService.init();
NotificationService.cancelAll();
NotificationManager.init();
FlutterVolumeController.updateShowSystemUI(false);

runApp(Namida(shouldShowOnBoarding: shouldShowOnBoarding));
Expand Down
3 changes: 3 additions & 0 deletions lib/ui/widgets/custom_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ class CustomListTile extends StatelessWidget {
? Text(
trailingText!,
style: context.textTheme.displayMedium?.copyWith(color: context.theme.colorScheme.onSurface.withAlpha(200)),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
)
: trailing,
),
Expand Down
Loading

0 comments on commit 667e15a

Please sign in to comment.