v1.5.0: UI rework, multi-progress tracking, performance optimizations

This commit is contained in:
zarzet
2026-01-02 02:54:50 +07:00
parent db1439e08f
commit d227d57545
38 changed files with 2801 additions and 833 deletions
+185
View File
@@ -0,0 +1,185 @@
import 'dart:io';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
class NotificationService {
static final NotificationService _instance = NotificationService._internal();
factory NotificationService() => _instance;
NotificationService._internal();
final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin();
bool _isInitialized = false;
static const int downloadProgressId = 1;
static const String channelId = 'download_progress';
static const String channelName = 'Download Progress';
static const String channelDescription = 'Shows download progress for tracks';
Future<void> initialize() async {
if (_isInitialized) return;
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const iosSettings = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: false,
);
const initSettings = InitializationSettings(
android: androidSettings,
iOS: iosSettings,
);
await _notifications.initialize(initSettings);
// Create notification channel for Android
if (Platform.isAndroid) {
await _notifications
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.createNotificationChannel(
const AndroidNotificationChannel(
channelId,
channelName,
description: channelDescription,
importance: Importance.low,
showBadge: false,
playSound: false,
enableVibration: false,
),
);
}
_isInitialized = true;
}
Future<void> showDownloadProgress({
required String trackName,
required String artistName,
required int progress,
required int total,
}) async {
if (!_isInitialized) await initialize();
final percentage = total > 0 ? (progress * 100 ~/ total) : 0;
final androidDetails = AndroidNotificationDetails(
channelId,
channelName,
channelDescription: channelDescription,
importance: Importance.low,
priority: Priority.low,
showProgress: true,
maxProgress: 100,
progress: percentage,
ongoing: true,
autoCancel: false,
playSound: false,
enableVibration: false,
onlyAlertOnce: true,
icon: '@mipmap/ic_launcher',
);
const iosDetails = DarwinNotificationDetails(
presentAlert: false,
presentBadge: false,
presentSound: false,
);
final details = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _notifications.show(
downloadProgressId,
'Downloading $trackName',
'$artistName$percentage%',
details,
);
}
Future<void> showDownloadComplete({
required String trackName,
required String artistName,
int? completedCount,
int? totalCount,
}) async {
if (!_isInitialized) await initialize();
final title = completedCount != null && totalCount != null
? 'Download Complete ($completedCount/$totalCount)'
: 'Download Complete';
const androidDetails = AndroidNotificationDetails(
channelId,
channelName,
channelDescription: channelDescription,
importance: Importance.defaultImportance,
priority: Priority.defaultPriority,
autoCancel: true,
playSound: false,
icon: '@mipmap/ic_launcher',
);
const iosDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: false,
);
const details = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _notifications.show(
downloadProgressId,
title,
'$trackName - $artistName',
details,
);
}
Future<void> showQueueComplete({
required int completedCount,
required int failedCount,
}) async {
if (!_isInitialized) await initialize();
final title = failedCount > 0
? 'Downloads Finished ($completedCount done, $failedCount failed)'
: 'All Downloads Complete';
const androidDetails = AndroidNotificationDetails(
channelId,
channelName,
channelDescription: channelDescription,
importance: Importance.defaultImportance,
priority: Priority.defaultPriority,
autoCancel: true,
playSound: true,
icon: '@mipmap/ic_launcher',
);
const iosDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
const details = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);
await _notifications.show(
downloadProgressId,
title,
'$completedCount tracks downloaded successfully',
details,
);
}
Future<void> cancelDownloadNotification() async {
await _notifications.cancel(downloadProgressId);
}
}
+26 -1
View File
@@ -54,6 +54,7 @@ class PlatformBridge {
int discNumber = 1,
int totalTracks = 1,
String? releaseDate,
String? itemId,
}) async {
final request = jsonEncode({
'isrc': isrc,
@@ -73,6 +74,7 @@ class PlatformBridge {
'disc_number': discNumber,
'total_tracks': totalTracks,
'release_date': releaseDate ?? '',
'item_id': itemId ?? '',
});
final result = await _channel.invokeMethod('downloadTrack', request);
@@ -98,6 +100,7 @@ class PlatformBridge {
int totalTracks = 1,
String? releaseDate,
String preferredService = 'tidal',
String? itemId,
}) async {
final request = jsonEncode({
'isrc': isrc,
@@ -117,18 +120,40 @@ class PlatformBridge {
'disc_number': discNumber,
'total_tracks': totalTracks,
'release_date': releaseDate ?? '',
'item_id': itemId ?? '',
});
final result = await _channel.invokeMethod('downloadWithFallback', request);
return jsonDecode(result as String) as Map<String, dynamic>;
}
/// Get download progress
/// Get download progress (legacy single download)
static Future<Map<String, dynamic>> getDownloadProgress() async {
final result = await _channel.invokeMethod('getDownloadProgress');
return jsonDecode(result as String) as Map<String, dynamic>;
}
/// Get progress for all active downloads (concurrent mode)
static Future<Map<String, dynamic>> getAllDownloadProgress() async {
final result = await _channel.invokeMethod('getAllDownloadProgress');
return jsonDecode(result as String) as Map<String, dynamic>;
}
/// Initialize progress tracking for a download item
static Future<void> initItemProgress(String itemId) async {
await _channel.invokeMethod('initItemProgress', {'item_id': itemId});
}
/// Finish progress tracking for a download item
static Future<void> finishItemProgress(String itemId) async {
await _channel.invokeMethod('finishItemProgress', {'item_id': itemId});
}
/// Clear progress tracking for a download item
static Future<void> clearItemProgress(String itemId) async {
await _channel.invokeMethod('clearItemProgress', {'item_id': itemId});
}
/// Set download directory
static Future<void> setDownloadDirectory(String path) async {
await _channel.invokeMethod('setDownloadDirectory', {'path': path});