diff --git a/ui/flutter/android/app/build.gradle b/ui/flutter/android/app/build.gradle
index 8e1a322d..11472895 100644
--- a/ui/flutter/android/app/build.gradle
+++ b/ui/flutter/android/app/build.gradle
@@ -47,7 +47,7 @@ android {
applicationId "com.gopeed"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
- minSdkVersion 19
+ minSdkVersion 21
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/ui/flutter/android/app/src/main/AndroidManifest.xml b/ui/flutter/android/app/src/main/AndroidManifest.xml
index 15d4a893..e6dfc888 100644
--- a/ui/flutter/android/app/src/main/AndroidManifest.xml
+++ b/ui/flutter/android/app/src/main/AndroidManifest.xml
@@ -72,6 +72,12 @@
+
+
+
+
diff --git a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart
index f0463f91..90da5ec1 100644
--- a/ui/flutter/lib/app/modules/app/controllers/app_controller.dart
+++ b/ui/flutter/lib/app/modules/app/controllers/app_controller.dart
@@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -79,21 +80,18 @@ class AppController extends GetxController with WindowListener, TrayListener {
@override
void onReady() {
super.onReady();
- try {
- _initDeepLinks();
- } catch (e) {
- logger.w("initDeepLinks error", e);
- }
- try {
- _initWindows();
- } catch (e) {
- logger.w("initWindows error", e);
- }
- try {
- _initTray();
- } catch (e) {
- logger.w("initTray error", e);
- }
+
+ _initDeepLinks().onError((error, stackTrace) =>
+ logger.w("initDeepLinks error", error, stackTrace));
+
+ _initWindows().onError((error, stackTrace) =>
+ logger.w("initWindows error", error, stackTrace));
+
+ _initTray().onError(
+ (error, stackTrace) => logger.w("initTray error", error, stackTrace));
+
+ _initForegroundTask().onError((error, stackTrace) =>
+ logger.w("initForegroundTask error", error, stackTrace));
}
@override
@@ -210,6 +208,42 @@ class AppController extends GetxController with WindowListener, TrayListener {
trayManager.addListener(this);
}
+ Future _initForegroundTask() async {
+ FlutterForegroundTask.init(
+ androidNotificationOptions: AndroidNotificationOptions(
+ channelId: 'gopeed_service',
+ channelName: 'Gopeed Background Service',
+ channelImportance: NotificationChannelImportance.LOW,
+ showWhen: true,
+ priority: NotificationPriority.LOW,
+ iconData: const NotificationIconData(
+ resType: ResourceType.mipmap,
+ resPrefix: ResourcePrefix.ic,
+ name: 'launcher',
+ )),
+ iosNotificationOptions: const IOSNotificationOptions(
+ showNotification: true,
+ playSound: false,
+ ),
+ foregroundTaskOptions: const ForegroundTaskOptions(
+ interval: 5000,
+ isOnceEvent: false,
+ autoRunOnBoot: true,
+ allowWakeLock: true,
+ allowWifiLock: true,
+ ),
+ );
+
+ if (await FlutterForegroundTask.isRunningService) {
+ FlutterForegroundTask.restartService();
+ } else {
+ FlutterForegroundTask.startService(
+ notificationTitle: "serviceTitle".tr,
+ notificationText: "serviceText".tr,
+ );
+ }
+ }
+
Future _toCreate(Uri uri) async {
final path = uri.scheme == "magnet"
? uri.toString()
diff --git a/ui/flutter/lib/app/modules/home/views/home_view.dart b/ui/flutter/lib/app/modules/home/views/home_view.dart
index fbd25d48..1f98f7fd 100644
--- a/ui/flutter/lib/app/modules/home/views/home_view.dart
+++ b/ui/flutter/lib/app/modules/home/views/home_view.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:flutter_foreground_task/flutter_foreground_task.dart';
import 'package:get/get.dart';
import '../../../routes/app_pages.dart';
@@ -23,139 +24,141 @@ class HomeView extends GetView {
break;
}
- return Scaffold(
- // extendBody: true,
- body: Row(
- // crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- !ResponsiveBuilder.isNarrow(context)
- ? NavigationRail(
- extended: true,
- labelType: NavigationRailLabelType.none,
- minExtendedWidth: 170,
- groupAlignment: 0,
- // useIndicator: false,
- onDestinationSelected: (int index) {
- controller.currentIndex.value = index;
- switch (index) {
- case 0:
- delegate.offAndToNamed(Routes.TASK);
- break;
- case 1:
- delegate.offAndToNamed(Routes.EXTENSION);
- break;
- case 2:
- delegate.offAndToNamed(Routes.SETTING);
- break;
- }
- },
- destinations: [
- NavigationRailDestination(
- icon: const Icon(Icons.task),
- selectedIcon: const Icon(Icons.task),
- label: Text('task'.tr),
- ),
- NavigationRailDestination(
- icon: const Icon(Icons.extension),
- selectedIcon: const Icon(Icons.extension),
- label: Text('extensions'.tr),
- ),
- NavigationRailDestination(
- icon: const Icon(Icons.settings),
- selectedIcon: const Icon(Icons.settings),
- label: Text('setting'.tr),
- ),
- ],
- selectedIndex: controller.currentIndex.value,
- leading: const Icon(Icons.menu),
- // trailing: const Icon(Icons.info_outline),
- )
- : const SizedBox.shrink(),
- Expanded(
- child: GetRouterOutlet(
- initialRoute: Routes.TASK,
- // anchorRoute: '/',
- // filterPages: (afterAnchor) {
- // logger.w(afterAnchor);
- // logger.w(afterAnchor.take(1));
- // return afterAnchor.take(1);
- // },
- ))
- ]),
- bottomNavigationBar: ResponsiveBuilder.isNarrow(context)
- ? BottomNavigationBar(
- items: [
- BottomNavigationBarItem(
- icon: const Icon(Icons.task),
- label: 'task'.tr,
- ),
- BottomNavigationBarItem(
- icon: const Icon(Icons.extension),
- label: 'extensions'.tr,
- ),
- BottomNavigationBarItem(
- icon: const Icon(Icons.settings),
- label: 'setting'.tr,
- ),
- ],
- currentIndex: controller.currentIndex.value,
- // selectedItemColor: Get.theme.highlightColor,
- onTap: (index) {
- controller.currentIndex.value = index;
- switch (index) {
- case 0:
- delegate.offAndToNamed(Routes.TASK);
- break;
- case 1:
- delegate.offAndToNamed(Routes.EXTENSION);
- break;
- case 2:
- delegate.offAndToNamed(Routes.SETTING);
- break;
- }
- },
- )
- // StylishBottomBar(
- // option: AnimatedBarOptions(
- // iconSize: 32,
- // barAnimation: BarAnimation.blink,
- // iconStyle: IconStyle.Default,
- // opacity: 0.3,
- // ),
- // items: [
- // BottomBarItem(
- // icon: const Icon(Icons.file_download),
- // selectedColor: Get.theme.primaryColor,
- // title: Text('downloading'.tr)),
- // BottomBarItem(
- // icon: const Icon(Icons.done),
- // selectedColor: Get.theme.primaryColor,
- // title: Text('downloaded'.tr)),
- // BottomBarItem(
- // icon: const Icon(Icons.settings),
- // selectedColor: Get.theme.primaryColor,
- // title: Text('setting'.tr)),
- // ],
- // // hasNotch: true,
- // currentIndex: controller.currentIndex.value,
- // onTap: (index) {
- // switch (index) {
- // case 0:
- // delegate.toNamed(Routes.DOWNLOADING);
- // controller.currentIndex.value = 0;
- // break;
- // case 1:
- // delegate.toNamed(Routes.DOWNLOADED);
- // controller.currentIndex.value = 1;
- // break;
- // case 2:
- // delegate.toNamed(Routes.SETTING);
- // controller.currentIndex.value = 2;
- // break;
- // }
- // },
- // )
- : const SizedBox.shrink(),
+ return WithForegroundTask(
+ child: Scaffold(
+ // extendBody: true,
+ body: Row(
+ // crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ !ResponsiveBuilder.isNarrow(context)
+ ? NavigationRail(
+ extended: true,
+ labelType: NavigationRailLabelType.none,
+ minExtendedWidth: 170,
+ groupAlignment: 0,
+ // useIndicator: false,
+ onDestinationSelected: (int index) {
+ controller.currentIndex.value = index;
+ switch (index) {
+ case 0:
+ delegate.offAndToNamed(Routes.TASK);
+ break;
+ case 1:
+ delegate.offAndToNamed(Routes.EXTENSION);
+ break;
+ case 2:
+ delegate.offAndToNamed(Routes.SETTING);
+ break;
+ }
+ },
+ destinations: [
+ NavigationRailDestination(
+ icon: const Icon(Icons.task),
+ selectedIcon: const Icon(Icons.task),
+ label: Text('task'.tr),
+ ),
+ NavigationRailDestination(
+ icon: const Icon(Icons.extension),
+ selectedIcon: const Icon(Icons.extension),
+ label: Text('extensions'.tr),
+ ),
+ NavigationRailDestination(
+ icon: const Icon(Icons.settings),
+ selectedIcon: const Icon(Icons.settings),
+ label: Text('setting'.tr),
+ ),
+ ],
+ selectedIndex: controller.currentIndex.value,
+ leading: const Icon(Icons.menu),
+ // trailing: const Icon(Icons.info_outline),
+ )
+ : const SizedBox.shrink(),
+ Expanded(
+ child: GetRouterOutlet(
+ initialRoute: Routes.TASK,
+ // anchorRoute: '/',
+ // filterPages: (afterAnchor) {
+ // logger.w(afterAnchor);
+ // logger.w(afterAnchor.take(1));
+ // return afterAnchor.take(1);
+ // },
+ ))
+ ]),
+ bottomNavigationBar: ResponsiveBuilder.isNarrow(context)
+ ? BottomNavigationBar(
+ items: [
+ BottomNavigationBarItem(
+ icon: const Icon(Icons.task),
+ label: 'task'.tr,
+ ),
+ BottomNavigationBarItem(
+ icon: const Icon(Icons.extension),
+ label: 'extensions'.tr,
+ ),
+ BottomNavigationBarItem(
+ icon: const Icon(Icons.settings),
+ label: 'setting'.tr,
+ ),
+ ],
+ currentIndex: controller.currentIndex.value,
+ // selectedItemColor: Get.theme.highlightColor,
+ onTap: (index) {
+ controller.currentIndex.value = index;
+ switch (index) {
+ case 0:
+ delegate.offAndToNamed(Routes.TASK);
+ break;
+ case 1:
+ delegate.offAndToNamed(Routes.EXTENSION);
+ break;
+ case 2:
+ delegate.offAndToNamed(Routes.SETTING);
+ break;
+ }
+ },
+ )
+ // StylishBottomBar(
+ // option: AnimatedBarOptions(
+ // iconSize: 32,
+ // barAnimation: BarAnimation.blink,
+ // iconStyle: IconStyle.Default,
+ // opacity: 0.3,
+ // ),
+ // items: [
+ // BottomBarItem(
+ // icon: const Icon(Icons.file_download),
+ // selectedColor: Get.theme.primaryColor,
+ // title: Text('downloading'.tr)),
+ // BottomBarItem(
+ // icon: const Icon(Icons.done),
+ // selectedColor: Get.theme.primaryColor,
+ // title: Text('downloaded'.tr)),
+ // BottomBarItem(
+ // icon: const Icon(Icons.settings),
+ // selectedColor: Get.theme.primaryColor,
+ // title: Text('setting'.tr)),
+ // ],
+ // // hasNotch: true,
+ // currentIndex: controller.currentIndex.value,
+ // onTap: (index) {
+ // switch (index) {
+ // case 0:
+ // delegate.toNamed(Routes.DOWNLOADING);
+ // controller.currentIndex.value = 0;
+ // break;
+ // case 1:
+ // delegate.toNamed(Routes.DOWNLOADED);
+ // controller.currentIndex.value = 1;
+ // break;
+ // case 2:
+ // delegate.toNamed(Routes.SETTING);
+ // controller.currentIndex.value = 2;
+ // break;
+ // }
+ // },
+ // )
+ : const SizedBox.shrink(),
+ ),
);
});
}
diff --git a/ui/flutter/lib/generated/locales.g.dart b/ui/flutter/lib/generated/locales.g.dart
index 7c8e82a7..292f1784 100644
--- a/ui/flutter/lib/generated/locales.g.dart
+++ b/ui/flutter/lib/generated/locales.g.dart
@@ -87,6 +87,8 @@ class LocaleKeys {
static const history = 'history';
static const clearHistory = 'clearHistory';
static const noHistoryFound = 'noHistoryFound';
+ static const serviceTitle = 'serviceTitle';
+ static const serviceText = 'serviceText';
}
class Locales {
@@ -165,6 +167,8 @@ class Locales {
'history': 'History',
'clearHistory': 'Clear History',
'noHistoryFound': 'No History Found',
+ 'serviceTitle': 'Download Service',
+ 'serviceText': 'Running',
};
static const fa_IR = {
'label': 'فارسی',
@@ -434,6 +438,8 @@ class Locales {
'history': '历史记录',
'clearHistory': '清空历史记录',
'noHistoryFound': '暂无历史记录',
+ 'serviceTitle': '下载服务',
+ 'serviceText': '运行中',
};
static const zh_TW = {
'label': '中文 (正體) ',
diff --git a/ui/flutter/pubspec.lock b/ui/flutter/pubspec.lock
index ac275573..aaaf6399 100644
--- a/ui/flutter/pubspec.lock
+++ b/ui/flutter/pubspec.lock
@@ -310,6 +310,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_foreground_task:
+ dependency: "direct main"
+ description:
+ name: flutter_foreground_task
+ sha256: e48d2d810a2d643362e64de41146ed8e95d4dd282bae6abbb32309d9f0bf5d67
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.1.2"
flutter_form_builder:
dependency: "direct main"
description:
diff --git a/ui/flutter/pubspec.yaml b/ui/flutter/pubspec.yaml
index 04faa5c5..1bc26b82 100644
--- a/ui/flutter/pubspec.yaml
+++ b/ui/flutter/pubspec.yaml
@@ -62,6 +62,7 @@ dependencies:
open_file: ^3.3.2
flutter_form_builder: ^9.1.1
form_builder_validators: ^9.0.0
+ flutter_foreground_task: ^6.1.2
dev_dependencies:
flutter_test:
sdk: flutter