Skip to content

Commit 7af9f2e

Browse files
committed
feat: add notification system for download progress and completion
- Implement NotificationManager with local notifications support - Add progress notifications for active downloads with progress bars - Show completion notifications when downloads finish - Integrate notification system with DownloadManager - Add download manager tab to main navigation with active task counter - Remove download manager from popup menus and settings - Update dependencies to include flutter_local_notifications
1 parent 381a1d9 commit 7af9f2e

File tree

9 files changed

+492
-166
lines changed

9 files changed

+492
-166
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="24dp"
4+
android:height="24dp"
5+
android:viewportWidth="24"
6+
android:viewportHeight="24">
7+
<path
8+
android:fillColor="#FF000000"
9+
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
10+
</vector>

lib/main.dart

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import 'package:openlist_flutter/pages/openlist/openlist.dart';
33
import 'package:openlist_flutter/pages/app_update_dialog.dart';
44
import 'package:openlist_flutter/pages/settings/settings.dart';
55
import 'package:openlist_flutter/pages/web/web.dart';
6+
import 'package:openlist_flutter/pages/download_manager_page.dart';
7+
import 'package:openlist_flutter/utils/download_manager.dart';
8+
import 'package:openlist_flutter/utils/notification_manager.dart';
69
import 'package:fade_indexed_stack/fade_indexed_stack.dart';
710
import 'package:flutter/foundation.dart';
811
import 'package:flutter/material.dart';
@@ -15,6 +18,10 @@ import 'contant/native_bridge.dart';
1518

1619
void main() async {
1720
WidgetsFlutterBinding.ensureInitialized();
21+
22+
// 初始化通知管理器
23+
await NotificationManager.initialize();
24+
1825
// Android
1926
if (!kIsWeb &&
2027
kDebugMode &&
@@ -86,6 +93,7 @@ class MyHomePage extends StatelessWidget {
8693
children: [
8794
WebScreen(key: webGlobalKey),
8895
const OpenListScreen(),
96+
const DownloadManagerPage(),
8997
const SettingsScreen()
9098
],
9199
),
@@ -105,6 +113,41 @@ class MyHomePage extends StatelessWidget {
105113
),
106114
label: S.current.appName,
107115
),
116+
NavigationDestination(
117+
icon: Obx(() {
118+
int activeCount = DownloadManager.activeTasks.length;
119+
return Stack(
120+
children: [
121+
const Icon(Icons.download),
122+
if (activeCount > 0)
123+
Positioned(
124+
right: 0,
125+
top: 0,
126+
child: Container(
127+
padding: const EdgeInsets.all(2),
128+
decoration: BoxDecoration(
129+
color: Colors.red,
130+
borderRadius: BorderRadius.circular(10),
131+
),
132+
constraints: const BoxConstraints(
133+
minWidth: 16,
134+
minHeight: 16,
135+
),
136+
child: Text(
137+
'$activeCount',
138+
style: const TextStyle(
139+
color: Colors.white,
140+
fontSize: 10,
141+
),
142+
textAlign: TextAlign.center,
143+
),
144+
),
145+
),
146+
],
147+
);
148+
}),
149+
label: '下载管理',
150+
),
108151
NavigationDestination(
109152
icon: const Icon(Icons.settings),
110153
label: S.current.settings,
@@ -145,4 +188,4 @@ class _MainController extends GetxController {
145188

146189
super.onInit();
147190
}
148-
}
191+
}

lib/pages/openlist/openlist.dart

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import 'package:openlist_flutter/generated_api.dart';
22
import 'package:openlist_flutter/pages/openlist/about_dialog.dart';
33
import 'package:openlist_flutter/pages/openlist/pwd_edit_dialog.dart';
44
import 'package:openlist_flutter/pages/app_update_dialog.dart';
5-
import 'package:openlist_flutter/pages/download_manager_page.dart';
65
import 'package:openlist_flutter/widgets/switch_floating_action_button.dart';
76
import 'package:flutter/material.dart';
87
import 'package:get/get.dart';
@@ -50,19 +49,6 @@ class OpenListScreen extends StatelessWidget {
5049
return [
5150
PopupMenuItem(
5251
value: 1,
53-
onTap: () {
54-
Get.to(() => const DownloadManagerPage());
55-
},
56-
child: const Row(
57-
children: [
58-
Icon(Icons.download),
59-
SizedBox(width: 8),
60-
Text('下载管理'),
61-
],
62-
),
63-
),
64-
PopupMenuItem(
65-
value: 2,
6652
onTap: () async {
6753
AppUpdateDialog.checkUpdateAndShowDialog(context, (b) {
6854
if (!b) {
@@ -75,7 +61,7 @@ class OpenListScreen extends StatelessWidget {
7561
child: Text(S.of(context).checkForUpdates),
7662
),
7763
PopupMenuItem(
78-
value: 3,
64+
value: 2,
7965
onTap: () {
8066
showDialog(context: context, builder: ((context){
8167
return const AppAboutDialog();
@@ -143,4 +129,4 @@ class OpenListController extends GetxController {
143129

144130
super.onInit();
145131
}
146-
}
132+
}

lib/pages/settings/settings.dart

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'package:openlist_flutter/contant/native_bridge.dart';
22
import 'package:openlist_flutter/generated_api.dart';
33
import 'package:openlist_flutter/pages/settings/preference_widgets.dart';
4-
import 'package:openlist_flutter/pages/download_manager_page.dart';
54
import 'package:file_picker/file_picker.dart';
65
import 'package:flutter/material.dart';
76
import 'package:get/get.dart';
@@ -146,14 +145,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
146145
},
147146
),
148147
DividerPreference(title: S.of(context).uiSettings),
149-
BasicPreference(
150-
title: '下载管理',
151-
subtitle: '查看和管理已下载的文件',
152-
leading: const Icon(Icons.download),
153-
onTap: () {
154-
Get.to(() => const DownloadManagerPage());
155-
},
156-
),
157148
SwitchPreference(
158149
icon: const Icon(Icons.pan_tool_alt_outlined),
159150
title: S.of(context).silentJumpApp,
@@ -259,4 +250,4 @@ class _SettingsController extends GetxController {
259250
_notificationGranted.value = true;
260251
}
261252
}
262-
}
253+
}

lib/utils/download_manager.dart

Lines changed: 21 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:permission_handler/permission_handler.dart';
66
import 'package:get/get.dart' as getx;
77
import 'package:flutter/material.dart';
88
import 'package:open_filex/open_filex.dart';
9+
import 'notification_manager.dart';
910

1011
/// 下载任务状态
1112
enum DownloadStatus {
@@ -90,123 +91,14 @@ class DownloadManager {
9091
/// 获取所有下载任务
9192
static List<DownloadTask> get allTasks => [..._activeTasks.values, ..._completedTasks];
9293

93-
/// 直接下载文件到系统下载目录
94-
static Future<bool> downloadFile({
95-
required String url,
96-
String? filename,
97-
Function(int received, int total)? onProgress,
98-
bool showStartNotification = true,
99-
bool showCompleteNotification = true,
100-
}) async {
101-
try {
102-
// 请求存储权限
103-
if (Platform.isAndroid) {
104-
// Android 11+ 需要管理外部存储权限
105-
var storageStatus = await Permission.storage.status;
106-
var manageStorageStatus = await Permission.manageExternalStorage.status;
107-
108-
if (!storageStatus.isGranted) {
109-
storageStatus = await Permission.storage.request();
110-
}
111-
112-
if (!manageStorageStatus.isGranted) {
113-
manageStorageStatus = await Permission.manageExternalStorage.request();
114-
}
115-
116-
// 如果都没有权限,提示用户
117-
if (!storageStatus.isGranted && !manageStorageStatus.isGranted) {
118-
getx.Get.showSnackbar(getx.GetSnackBar(
119-
message: '需要存储权限才能下载文件,请在设置中手动授权',
120-
duration: const Duration(seconds: 5),
121-
mainButton: TextButton(
122-
onPressed: () {
123-
openAppSettings();
124-
},
125-
child: const Text('去设置'),
126-
),
127-
));
128-
return false;
129-
}
130-
}
131-
132-
// 获取下载目录并创建OpenList专用文件夹
133-
Directory? downloadDir = await _getOpenListDownloadDirectory();
134-
135-
if (downloadDir == null) {
136-
getx.Get.showSnackbar(const getx.GetSnackBar(
137-
message: '无法获取下载目录',
138-
duration: Duration(seconds: 3),
139-
));
140-
return false;
141-
}
142-
143-
// 确定文件名
144-
String finalFilename = filename ?? _getFilenameFromUrl(url);
145-
String filePath = '${downloadDir.path}/$finalFilename';
146-
147-
// 检查文件是否已存在,如果存在则添加序号
148-
filePath = _getUniqueFilePath(filePath);
149-
150-
log('开始下载文件: $url');
151-
log('保存路径: $filePath');
152-
153-
// 显示下载开始提示
154-
if (showStartNotification) {
155-
getx.Get.showSnackbar(getx.GetSnackBar(
156-
message: '开始下载: $finalFilename',
157-
duration: const Duration(seconds: 2),
158-
));
159-
}
160-
161-
// 执行下载
162-
await _dio.download(
163-
url,
164-
filePath,
165-
onReceiveProgress: (received, total) {
166-
if (onProgress != null) {
167-
onProgress(received, total);
168-
}
169-
170-
// 显示下载进度
171-
if (total > 0) {
172-
double progress = received / total;
173-
log('下载进度: ${(progress * 100).toStringAsFixed(1)}%');
174-
}
175-
},
176-
);
177-
178-
// 下载完成提示
179-
if (showCompleteNotification) {
180-
getx.Get.showSnackbar(getx.GetSnackBar(
181-
message: '下载完成: $finalFilename',
182-
duration: const Duration(seconds: 3),
183-
mainButton: TextButton(
184-
onPressed: () {
185-
_openFile(filePath);
186-
},
187-
child: const Text('打开'),
188-
),
189-
));
190-
}
191-
192-
log('文件下载完成: $filePath');
193-
return true;
194-
195-
} catch (e) {
196-
log('下载失败: $e');
197-
getx.Get.showSnackbar(getx.GetSnackBar(
198-
message: '下载失败: ${e.toString()}',
199-
duration: const Duration(seconds: 3),
200-
));
201-
return false;
202-
}
203-
}
204-
20594
/// 带进度条的下载(后台下载,不阻塞UI)
20695
static Future<bool> downloadFileWithProgress({
20796
required String url,
20897
String? filename,
20998
}) async {
99+
// 初始化通知管理器
100+
await NotificationManager.initialize();
101+
210102
// 生成任务ID
211103
String taskId = DateTime.now().millisecondsSinceEpoch.toString();
212104

@@ -251,6 +143,9 @@ class DownloadManager {
251143
// 更新任务状态
252144
task.status = DownloadStatus.downloading;
253145

146+
// 显示初始通知
147+
await NotificationManager.showDownloadProgressNotification();
148+
254149
// 执行下载
255150
await _dio.download(
256151
url,
@@ -265,6 +160,9 @@ class DownloadManager {
265160
task.progress = received / total;
266161
}
267162

163+
// 更新通知进度
164+
NotificationManager.showDownloadProgressNotification();
165+
268166
log('下载进度: ${(task.progress * 100).toStringAsFixed(1)}%');
269167
},
270168
);
@@ -278,6 +176,9 @@ class DownloadManager {
278176
_activeTasks.remove(taskId);
279177
_completedTasks.insert(0, task); // 插入到开头,最新的在前面
280178

179+
// 显示单个文件完成通知
180+
await NotificationManager.showSingleFileCompleteNotification(task);
181+
281182
// 显示完成提示
282183
getx.Get.showSnackbar(getx.GetSnackBar(
283184
message: '下载完成: $finalFilename',
@@ -318,6 +219,13 @@ class DownloadManager {
318219
_activeTasks.remove(taskId);
319220
_completedTasks.insert(0, task);
320221

222+
// 更新通知状态
223+
if (_activeTasks.isEmpty) {
224+
await NotificationManager.cancelDownloadNotification();
225+
} else {
226+
await NotificationManager.showDownloadProgressNotification();
227+
}
228+
321229
return false;
322230
}
323231
}
@@ -421,7 +329,7 @@ class DownloadManager {
421329
log('解析文件名失败: $e');
422330
}
423331

424-
// 如果无法从URL提取��件名,使用时间戳
332+
// 如果无法从URL提取文件名,使用时间戳
425333
return 'download_${DateTime.now().millisecondsSinceEpoch}';
426334
}
427335

0 commit comments

Comments
 (0)