feat: task detail view (#633)

This commit is contained in:
Levi authored and GitHub committed 2024-08-08 18:34:00 +08:00
1 parent 3fd3dd49d2
commit 4d60c47d39
22 files changed
+280 -105

No files matched your search

+1 -1
View File
@@ -380,7 +380,7 @@ jobs:
go install golang.org/x/mobile/cmd/gomobile@latest
go get golang.org/x/mobile/bind
gomobile init
gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
gomobile bind -tags nosqlite -ldflags="-w -s -X github.com/GopeedLab/gopeed/pkg/base.Version=$VERSION" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg=com.gopeed github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
echo $APK_KEYSTORE | base64 -di > android/app/upload-keystore.jks
flutter build apk
+1 -1
View File
@@ -174,7 +174,7 @@ command:
- android
```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```
+1 -1
View File
@@ -162,7 +162,7 @@ gomobile init
- android
```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```
+1 -1
View File
@@ -170,7 +170,7 @@ command:
- android
```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```
+1 -1
View File
@@ -174,7 +174,7 @@ gomobile init
- android
```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```
+1 -1
View File
@@ -170,7 +170,7 @@ gomobile init
- android
```bash
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 19 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
gomobile bind -tags nosqlite -ldflags="-w -s" -o ui/flutter/android/app/libs/libgopeed.aar -target=android -androidapi 21 -javapkg="com.gopeed" github.com/GopeedLab/gopeed/bind/mobile
cd ui/flutter
flutter build apk
```
+2 -2
View File
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 33
compileSdkVersion 34
ndkVersion flutter.ndkVersion
compileOptions {
@@ -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 21
minSdkVersion 32
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -7,7 +7,8 @@
<application
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="Gopeed">
android:label="Gopeed"
android:extractNativeLibs="true">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
@@ -195,7 +195,8 @@ class AppController extends GetxController with WindowListener, TrayListener {
} else if (Util.isMacos()) {
await trayManager.setIcon('assets/tray_icon/icon_mac.png',
isTemplate: true);
} else if (Platform.environment.containsKey('FLATPAK_ID') || Platform.environment.containsKey('SNAP')) {
} else if (Platform.environment.containsKey('FLATPAK_ID') ||
Platform.environment.containsKey('SNAP')) {
await trayManager.setIcon('com.gopeed.Gopeed');
} else {
await trayManager.setIcon('assets/tray_icon/icon.png');
@@ -269,16 +270,12 @@ class AppController extends GetxController with WindowListener, TrayListener {
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',
)),
channelId: 'gopeed_service',
channelName: 'Gopeed Background Service',
channelImportance: NotificationChannelImportance.LOW,
showWhen: true,
priority: NotificationPriority.LOW,
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: true,
playSound: false,
@@ -298,6 +295,11 @@ class AppController extends GetxController with WindowListener, TrayListener {
FlutterForegroundTask.startService(
notificationTitle: "serviceTitle".tr,
notificationText: "serviceText".tr,
notificationIcon: const NotificationIconData(
resType: ResourceType.mipmap,
resPrefix: ResourcePrefix.ic,
name: 'launcher',
),
);
}
}
@@ -3,8 +3,10 @@ import 'dart:io';
import 'package:badges/badges.dart' as badges;
import 'package:flutter/material.dart';
import 'package:path/path.dart' as path;
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:gopeed/app/views/copy_button.dart';
import 'package:intl/intl.dart';
import 'package:launch_at_startup/launch_at_startup.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -842,6 +844,32 @@ class SettingView extends GetView<SettingController> {
);
});
// advanced config log items start
buildLogsDir() {
return ListTile(
title: Text("日志目录"),
subtitle: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: TextField(
controller: TextEditingController(text: logsDir()),
enabled: false,
readOnly: true,
),
),
Util.isDesktop()
? IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () {
launchUrl(Uri.file(logsDir()));
},
)
: CopyButton(logsDir()),
],
));
}
return Obx(() {
return GestureDetector(
onTap: () {
@@ -953,6 +981,13 @@ class SettingView extends GetView<SettingController> {
: null,
]),
)),
const Text('开发者'),
Card(
child: Column(
children: _addDivider([
buildLogsDir(),
]),
)),
]),
))
],
@@ -1,5 +1,10 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:gopeed/api/model/task.dart';
class TaskController extends GetxController {
final tabIndex = 0.obs;
final scaffoldKey = GlobalKey<ScaffoldState>();
final selectTask = Rx<Task?>(null);
final copyUrlDone = false.obs;
}
@@ -104,7 +104,7 @@ class TaskFilesView extends GetView<TaskFilesController> {
}()
: [
IconButton(
icon: const Icon(Icons.play_circle),
icon: const Icon(Icons.open_in_new),
onPressed: () async {
await OpenFilex.open(filePath);
}),
@@ -1,6 +1,13 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path/path.dart' as path;
import '../../../../api/model/task.dart';
import '../../../../util/file_explorer.dart';
import '../../../../util/util.dart';
import '../../../routes/app_pages.dart';
import '../../../views/copy_button.dart';
import '../controllers/task_controller.dart';
import '../controllers/task_downloaded_controller.dart';
import '../controllers/task_downloading_controller.dart';
@@ -12,9 +19,12 @@ class TaskView extends GetView<TaskController> {
@override
Widget build(BuildContext context) {
final selectTask = controller.selectTask;
return DefaultTabController(
length: 2,
child: Scaffold(
key: controller.scaffoldKey,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: AppBar(
@@ -54,7 +64,118 @@ class TaskView extends GetView<TaskController> {
TaskDownloadedView(),
],
),
endDrawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: Obx(() => ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: [
SizedBox(
height: MediaQuery.of(context).padding.top + 65,
child: DrawerHeader(
child: Text(
'taskDetail'.tr,
style: Theme.of(context).textTheme.titleLarge,
)),
),
ListTile(
title: Text('taskName'.tr),
subtitle:
buildTooltipSubtitle(selectTask.value?.showName)),
ListTile(
title: Text('taskUrl'.tr),
subtitle:
buildTooltipSubtitle(selectTask.value?.meta.req.url),
trailing: CopyButton(selectTask.value?.meta.req.url),
),
ListTile(
title: Text('downloadPath'.tr),
subtitle:
buildTooltipSubtitle(selectTask.value?.explorerUrl),
trailing: IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () {
selectTask.value?.explorer();
},
),
),
],
)),
),
),
);
}
Widget buildTooltipSubtitle(String? text) {
final showText = text ?? "";
return Tooltip(
message: showText,
child: Text(
showText,
overflow: TextOverflow.ellipsis,
),
);
}
}
extension TaskEnhance on Task {
String get showName {
if (meta.opts.name.isNotEmpty) {
return meta.opts.name;
}
if (meta.res == null) {
final u = Uri.parse(meta.req.url);
if (u.scheme.startsWith("http")) {
return u.path.isNotEmpty
? u.path.substring(u.path.lastIndexOf("/") + 1)
: u.host;
} else {
final params = u.queryParameters;
if (params.containsKey("dn")) {
return params["dn"]!;
} else {
return params["xt"]!.split(":").last;
}
}
}
if (meta.res!.name.isNotEmpty) {
return meta.res!.name;
}
return meta.res!.files[0].name;
}
bool get isFolder {
return meta.res?.name.isNotEmpty ?? false;
}
String get explorerUrl {
if (isFolder) {
return path.join(Util.safeDir(meta.opts.path),
Util.safeDir(meta.res!.files[0].path), showName);
} else {
return path.join(Util.safeDir(meta.opts.path), Util.safeDir(showName));
}
}
Future<void> explorer() async {
if (Util.isDesktop()) {
await FileExplorer.openAndSelectFile(explorerUrl);
} else {
Get.rootDelegate.toNamed(Routes.TASK_FILES, parameters: {'id': id});
}
}
Future<void> open() async {
if (status != Status.done) {
return;
}
if (isFolder) {
await explorer();
} else {
await OpenFilex.open(explorerUrl);
}
}
}
@@ -1,18 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path/path.dart' as path;
import 'package:styled_widget/styled_widget.dart';
import '../../api/api.dart';
import '../../api/model/meta.dart';
import '../../api/model/task.dart';
import '../../util/file_explorer.dart';
import '../../util/file_icon.dart';
import '../../util/icons.dart';
import '../../util/message.dart';
import '../../util/util.dart';
import '../modules/app/controllers/app_controller.dart';
import '../modules/task/controllers/task_controller.dart';
import '../modules/task/views/task_view.dart';
import '../routes/app_pages.dart';
class BuildTaskListView extends GetView {
@@ -60,17 +58,7 @@ class BuildTaskListView extends GetView {
}
bool isFolderTask() {
return task.meta.res?.name.isNotEmpty ?? false;
}
String buildExplorerUrl(Task task) {
if (!isFolderTask()) {
return path.join(Util.safeDir(task.meta.opts.path),
Util.safeDir(task.meta.res!.files[0].path), fileName(task.meta));
} else {
return path.join(Util.safeDir(task.meta.opts.path),
Util.safeDir(fileName(task.meta)));
}
return task.isFolder;
}
Future<void> showDeleteDialog(String id) {
@@ -119,22 +107,13 @@ class BuildTaskListView extends GetView {
));
}
toTaskFilesView() {
if (Util.isDesktop()) {
FileExplorer.openAndSelectFile(buildExplorerUrl(task));
} else {
Get.rootDelegate
.toNamed(Routes.TASK_FILES, parameters: {'id': task.id});
}
}
List<Widget> buildActions() {
final list = <Widget>[];
if (isDone()) {
list.add(IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () {
toTaskFilesView();
task.explorer();
},
));
} else {
@@ -188,27 +167,26 @@ class BuildTaskListView extends GetView {
(total > 0 ? " / ${Util.fmtByte(total)}" : "");
}
final taskController = Get.find<TaskController>();
return Card(
elevation: 4.0,
child: InkWell(
onTap: () {},
onTap: () {
taskController.scaffoldKey.currentState?.openEndDrawer();
taskController.selectTask.value = task;
},
onDoubleTap: () {
if (isDone()) {
if (isFolderTask()) {
toTaskFilesView();
} else {
OpenFilex.open(buildExplorerUrl(task));
}
}
task.open();
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: Text(fileName(task.meta)),
title: Text(task.showName),
leading: isFolderTask()
? const Icon(FaIcons.folder)
: Icon(FaIcons.allIcons[findIcon(fileName(task.meta))])),
: Icon(FaIcons.allIcons[findIcon(task.showName)])),
Row(
children: [
Expanded(
@@ -239,29 +217,4 @@ class BuildTaskListView extends GetView {
),
)).padding(horizontal: 14, top: 8);
}
String fileName(Meta meta) {
if (meta.opts.name.isNotEmpty) {
return meta.opts.name;
}
if (meta.res == null) {
final u = Uri.parse(meta.req.url);
if (u.scheme.startsWith("http")) {
return u.path.isNotEmpty
? u.path.substring(u.path.lastIndexOf("/") + 1)
: u.host;
} else {
final params = u.queryParameters;
if (params.containsKey("dn")) {
return params["dn"]!;
} else {
return params["xt"]!.split(":").last;
}
}
}
if (meta.res!.name.isNotEmpty) {
return meta.res!.name;
}
return meta.res!.files[0].name;
}
}
+44
View File
@@ -0,0 +1,44 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../util/message.dart';
class CopyButton extends StatefulWidget {
final String? url;
const CopyButton(this.url, {Key? key}) : super(key: key);
@override
State<CopyButton> createState() => _CopyButtonState();
}
class _CopyButtonState extends State<CopyButton> {
bool success = false;
copy() {
final url = widget.url;
if (url != null) {
try {
Clipboard.setData(ClipboardData(text: url));
setState(() {
success = true;
});
Future.delayed(const Duration(milliseconds: 300), () {
setState(() {
success = false;
});
});
} catch (e) {
showErrorMessage(e);
}
}
}
@override
Widget build(BuildContext context) {
return IconButton(
icon: success ? const Icon(Icons.check_circle) : const Icon(Icons.copy),
onPressed: copy,
);
}
}
+4
View File
@@ -99,5 +99,9 @@ const enUS = {
'seedKeep': 'Keep seeding',
'seedRatio': 'Seed ratio',
'seedTime': 'Seed time (minutes)',
'taskDetail': 'Task Detail',
'taskName': 'Task Name',
'taskUrl': 'Task URL',
'downloadPath': 'Download Path',
},
};
+4
View File
@@ -96,5 +96,9 @@ const zhCN = {
'seedKeep': '持续做种',
'seedRatio': '做种分享率',
'seedTime': '做种时间(分钟)',
'taskDetail': '任务详情',
'taskName': '任务名称',
'taskUrl': '任务链接',
'downloadPath': '下载路径',
}
};
+10
View File
@@ -96,5 +96,15 @@ const zhTW = {
'seedKeep': '持續做種',
'seedRatio': '做種分享率',
'seedTime': '做種時間(分鐘)',
/*
'taskDetail': '任务详情',
'taskName': '任务名称',
'taskUrl': '任务链接',
'downloadPath': '下载路径',
*/
'taskDetail': '任務詳情',
'taskName': '任務名稱',
'taskUrl': '任務連結',
'downloadPath': '下載路徑',
}
};
+3 -11
View File
@@ -1,5 +1,7 @@
import 'dart:io';
import 'package:url_launcher/url_launcher_string.dart';
class FileExplorer {
static Future<void> openAndSelectFile(String filePath) async {
if (await FileSystemEntity.isFile(filePath)) {
@@ -10,17 +12,7 @@ class FileExplorer {
}
static Future<void> _openDirectory(String directoryPath) async {
if (Platform.isWindows) {
Process.run('explorer.exe', [directoryPath]);
} else if (Platform.isMacOS) {
Process.run('open', [directoryPath]);
} else if (Platform.isLinux) {
if (await _isUbuntuOrDebian()) {
Process.run('xdg-open', [directoryPath]);
} else if (await _isCentOS()) {
Process.run('nautilus', [directoryPath]);
}
}
await launchUrlString("file://$directoryPath");
}
static Future<void> _openFile(String filePath) async {
+6 -2
View File
@@ -15,11 +15,15 @@ initLogger() {
);
}
String logsDir() {
return path.join(Util.getStorageDir(), 'logs');
}
_buildOutput() {
// if is debug mode, don't log to file
if (!kDebugMode && Util.isDesktop()) {
final logDirPath = path.join(Util.getStorageDir(), 'logs');
var logDir = Directory(logDirPath);
final logDirPath = logsDir();
var logDir = Directory(logsDir());
if (!logDir.existsSync()) {
logDir.createSync();
}
+10 -10
View File
@@ -322,10 +322,10 @@ packages:
dependency: "direct main"
description:
name: flutter_foreground_task
sha256: "6cf10a27f5e344cd2ecad0752d3a5f4ec32846d82fda8753b3fe2480ebb832a3"
sha256: "294280d8055af5628a8085bb765ba442561eaab3d5bcdf2eea4f20614f18618f"
url: "https://pub.dev"
source: hosted
version: "6.5.0"
version: "8.2.0"
flutter_form_builder:
dependency: "direct main"
description:
@@ -681,10 +681,10 @@ packages:
dependency: transitive
description:
name: path_provider_foundation
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
version: "2.3.2"
path_provider_linux:
dependency: transitive
description:
@@ -841,10 +841,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "671e7a931f55a08aa45be2a13fe7247f2a41237897df434b30d2012388191833"
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
@@ -1054,10 +1054,10 @@ packages:
dependency: transitive
description:
name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
version: "6.2.4"
url_launcher_linux:
dependency: transitive
description:
@@ -1195,5 +1195,5 @@ packages:
source: hosted
version: "2.2.1"
sdks:
dart: ">=3.2.3 <4.0.0"
flutter: ">=3.16.6"
dart: ">=3.2.0 <4.0.0"
flutter: ">=3.16.0"
+1 -1
View File
@@ -59,7 +59,7 @@ dependencies:
share_plus: ^7.1.0
flutter_form_builder: ^9.1.1
form_builder_validators: ^9.0.0
flutter_foreground_task: ^6.1.2
flutter_foreground_task: ^8.2.0
open_filex: ^4.3.4
tray_manager: ^0.2.3
lecle_downloads_path_provider: ^0.0.2+8