mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-09 18:10:29 +00:00
Refactor project in get-cli standard (#80)
* refactor project in get-cli standard * use local trackers when github is not connectable upgrade stylish_bottom_bar to 1.0.0 responsive view for home_view treeview for file_list_vie fluent ui integrating * improve responsive view. treeView add light theme.fix overflow bugs * responsive view,treeView,file select downloaded and downloading pages file extension icons fix some minor bugs * use local trackers when github is not connectable upgrade stylish_bottom_bar to 1.0.0 responsive view for home_view treeview for file_list_vie fluent ui integrating * improve responsive view. treeView add light theme.fix overflow bugs * bottom bar index fix * refactor project in get-cli standard --------- Co-authored-by: wuke2022 <[email protected]>
This commit is contained in:
1 parent
9b1ed15fea
commit
74c24ad860
44 files changed
+1025
-360
No files matched your search
@@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/app_controller.dart';
|
||||
|
||||
class AppBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<AppController>(
|
||||
() => AppController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/model/downloader_config.dart';
|
||||
import '../../../../core/common/start_config.dart';
|
||||
import '../../../../i18n/messages.dart';
|
||||
import '../../../../util/log_util.dart';
|
||||
import '../../../../util/util.dart';
|
||||
|
||||
const _startConfigNetwork = "start.network";
|
||||
const _startConfigAddress = "start.address";
|
||||
const _startConfigApiToken = "start.apiToken";
|
||||
|
||||
const unixSocketPath = 'gopeed.sock';
|
||||
|
||||
const allTrackerSubscribeUrls = [
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_all.txt',
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_http.txt',
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_https.txt',
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_ip.txt',
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_udp.txt',
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_all_ws.txt',
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_best.txt',
|
||||
'https://github.com/ngosang/trackerslist/raw/master/trackers_best_ip.txt',
|
||||
'https://github.com/XIU2/TrackersListCollection/raw/master/all.txt',
|
||||
'https://github.com/XIU2/TrackersListCollection/raw/master/best.txt',
|
||||
'https://github.com/XIU2/TrackersListCollection/raw/master/http.txt',
|
||||
];
|
||||
const allTrackerCdns = [
|
||||
// jsdelivr: https://cdn.jsdelivr.net/gh/ngosang/trackerslist/trackers_all.txt
|
||||
["https://cdn.jsdelivr.net/gh", r".*github.com(/.*)/raw/master(/.*)"],
|
||||
// nuaa: https://hub.nuaa.cf/ngosang/trackerslist/raw/master/trackers_all.txt
|
||||
["https://hub.nuaa.cf", r".*github.com(/.*)"]
|
||||
];
|
||||
final allTrackerSubscribeUrlCdns = Map.fromIterable(allTrackerSubscribeUrls,
|
||||
key: (v) => v as String,
|
||||
value: (v) {
|
||||
final ret = [v as String];
|
||||
for (final cdn in allTrackerCdns) {
|
||||
final reg = RegExp(cdn[1]);
|
||||
final match = reg.firstMatch(v.toString());
|
||||
var matchStr = "";
|
||||
for (var i = 1; i <= match!.groupCount; i++) {
|
||||
matchStr += match.group(i)!;
|
||||
}
|
||||
ret.add("${cdn[0]}$matchStr");
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
class AppController extends GetxController {
|
||||
static StartConfig? _defaultStartConfig;
|
||||
|
||||
final startConfig = StartConfig().obs;
|
||||
final runningPort = 0.obs;
|
||||
final downloaderConfig = DownloaderConfig().obs;
|
||||
|
||||
String runningAddress() {
|
||||
if (startConfig.value.network == 'unix') {
|
||||
return startConfig.value.address;
|
||||
}
|
||||
return '${startConfig.value.address.split(':').first}:$runningPort';
|
||||
}
|
||||
|
||||
Future<StartConfig> _initDefaultStartConfig() async {
|
||||
if (_defaultStartConfig != null) {
|
||||
return _defaultStartConfig!;
|
||||
}
|
||||
_defaultStartConfig = StartConfig();
|
||||
if (!Util.isUnix()) {
|
||||
// not support unix socket, use tcp
|
||||
_defaultStartConfig!.network = "tcp";
|
||||
_defaultStartConfig!.address = "127.0.0.1:0";
|
||||
} else {
|
||||
_defaultStartConfig!.network = "unix";
|
||||
if (Util.isDesktop()) {
|
||||
_defaultStartConfig!.address = unixSocketPath;
|
||||
}
|
||||
if (Util.isMobile()) {
|
||||
_defaultStartConfig!.address =
|
||||
"${(await getTemporaryDirectory()).path}/$unixSocketPath";
|
||||
}
|
||||
}
|
||||
_defaultStartConfig!.apiToken = '';
|
||||
return _defaultStartConfig!;
|
||||
}
|
||||
|
||||
Future<void> loadStartConfig() async {
|
||||
final defaultCfg = await _initDefaultStartConfig();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
startConfig.value.network =
|
||||
prefs.getString(_startConfigNetwork) ?? defaultCfg.network;
|
||||
startConfig.value.address =
|
||||
prefs.getString(_startConfigAddress) ?? defaultCfg.address;
|
||||
startConfig.value.apiToken =
|
||||
prefs.getString(_startConfigApiToken) ?? defaultCfg.apiToken;
|
||||
}
|
||||
|
||||
Future<void> loadDownloaderConfig() async {
|
||||
try {
|
||||
downloaderConfig.value = await getConfig();
|
||||
} catch (e) {
|
||||
logger.w("load downloader config fail", e);
|
||||
downloaderConfig.value = DownloaderConfig();
|
||||
}
|
||||
await _initDownloaderConfig();
|
||||
}
|
||||
|
||||
Future<void> trackerUpdate() async {
|
||||
final btExtConfig = downloaderConfig.value.extra.bt;
|
||||
final result = <String>[];
|
||||
for (var u in btExtConfig.trackerSubscribeUrls) {
|
||||
var allFail = true;
|
||||
for (var cdn in allTrackerSubscribeUrlCdns[u]!) {
|
||||
try {
|
||||
result.addAll(await _fetchTrackers(u));
|
||||
allFail = false;
|
||||
break;
|
||||
} catch (e) {
|
||||
logger.w("subscribe trackers fail, url: $cdn", e);
|
||||
}
|
||||
}
|
||||
if (allFail) {
|
||||
throw Exception('subscribe trackers fail, network error');
|
||||
}
|
||||
}
|
||||
btExtConfig.subscribeTrackers.clear();
|
||||
btExtConfig.subscribeTrackers.addAll(result);
|
||||
downloaderConfig.update((val) {
|
||||
val!.extra.bt.lastTrackerUpdateTime = DateTime.now();
|
||||
});
|
||||
refreshTrackers();
|
||||
|
||||
await saveConfig();
|
||||
}
|
||||
|
||||
refreshTrackers() {
|
||||
final btConfig = downloaderConfig.value.protocolConfig.bt;
|
||||
final btExtConfig = downloaderConfig.value.extra.bt;
|
||||
btConfig.trackers.clear();
|
||||
btConfig.trackers.addAll(btExtConfig.subscribeTrackers);
|
||||
btConfig.trackers.addAll(btExtConfig.customTrackers);
|
||||
// remove duplicate
|
||||
btConfig.trackers.toSet().toList();
|
||||
}
|
||||
|
||||
Future<void> trackerUpdateOnStart() async {
|
||||
final btExtConfig = downloaderConfig.value.extra.bt;
|
||||
final lastUpdateTime = btExtConfig.lastTrackerUpdateTime;
|
||||
// if last update time is null or more than 1 day, update trackers
|
||||
if (lastUpdateTime == null ||
|
||||
lastUpdateTime.difference(DateTime.now()).inDays < 0) {
|
||||
try {
|
||||
await trackerUpdate();
|
||||
} catch (e) {
|
||||
logger.w("tracker update fail", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<String>> _fetchTrackers(String subscribeUrl) async {
|
||||
final resp = await proxyRequest(subscribeUrl);
|
||||
if (resp.statusCode != 200) {
|
||||
throw Exception('Failed to get trackers');
|
||||
}
|
||||
const ls = LineSplitter();
|
||||
return ls.convert(resp.data).where((e) => e.isNotEmpty).toList();
|
||||
}
|
||||
|
||||
_initDownloaderConfig() async {
|
||||
final config = downloaderConfig.value;
|
||||
if (config.protocolConfig.http.connections == 0) {
|
||||
config.protocolConfig.http.connections = 16;
|
||||
}
|
||||
final extra = config.extra;
|
||||
if (extra.themeMode.isEmpty) {
|
||||
extra.themeMode = ThemeMode.system.name;
|
||||
}
|
||||
if (extra.locale.isEmpty) {
|
||||
final systemLocale = getLocaleKey(ui.window.locale);
|
||||
extra.locale = messages.keys.containsKey(systemLocale)
|
||||
? systemLocale
|
||||
: getLocaleKey(fallbackLocale);
|
||||
}
|
||||
if (extra.bt.trackerSubscribeUrls.isEmpty) {
|
||||
// default select all tracker subscribe urls
|
||||
extra.bt.trackerSubscribeUrls.addAll(allTrackerSubscribeUrls);
|
||||
}
|
||||
|
||||
if (config.downloadDir.isEmpty) {
|
||||
if (Util.isDesktop()) {
|
||||
config.downloadDir = (await getDownloadsDirectory())?.path ?? "./";
|
||||
} else if (Util.isAndroid()) {
|
||||
config.downloadDir = (await getExternalStorageDirectory())?.path ??
|
||||
(await getApplicationDocumentsDirectory()).path;
|
||||
return;
|
||||
} else if (Util.isIOS()) {
|
||||
config.downloadDir = (await getApplicationDocumentsDirectory()).path;
|
||||
} else {
|
||||
config.downloadDir = './';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveConfig() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_startConfigNetwork, startConfig.value.network);
|
||||
await prefs.setString(_startConfigAddress, startConfig.value.address);
|
||||
await putConfig(downloaderConfig.value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../generated/locales.g.dart';
|
||||
import '../../../../i18n/messages.dart';
|
||||
import '../../../../theme/theme.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../controllers/app_controller.dart';
|
||||
|
||||
class AppView extends GetView<AppController> {
|
||||
const AppView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final config = controller.downloaderConfig.value;
|
||||
return GetMaterialApp.router(
|
||||
useInheritedMediaQuery: true,
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: GopeedTheme.light,
|
||||
darkTheme: GopeedTheme.dark,
|
||||
themeMode: ThemeMode.values.byName(config.extra.themeMode),
|
||||
// translations: messages,
|
||||
translationsKeys: AppTranslation.translations,
|
||||
locale: toLocale(config.extra.locale),
|
||||
fallbackLocale: fallbackLocale,
|
||||
localizationsDelegates: const [
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: messages.keys.keys.map((e) => toLocale(e)).toList(),
|
||||
getPages: AppPages.routes,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/create_controller.dart';
|
||||
|
||||
class CreateBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<CreateController>(
|
||||
() => CreateController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class CreateController extends GetxController {
|
||||
final files = [].obs;
|
||||
final RxList selectedIndexs = [].obs;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/api/model/resolve_result.dart';
|
||||
import 'package:rounded_loading_button/rounded_loading_button.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/model/create_task.dart';
|
||||
import '../../../../api/model/options.dart';
|
||||
import '../../../../api/model/request.dart';
|
||||
import '../../../../util/util.dart';
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../../../views/views/directory_selector.dart';
|
||||
import '../../../views/views/file_list_view.dart';
|
||||
import '../../app/controllers/app_controller.dart';
|
||||
import '../controllers/create_controller.dart';
|
||||
|
||||
class CreateView extends GetView<CreateController> {
|
||||
final _resolveFormKey = GlobalKey<FormState>();
|
||||
|
||||
final _urlController = TextEditingController();
|
||||
final _confirmController = RoundedLoadingButtonController();
|
||||
|
||||
CreateView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Get.rootDelegate.offNamed(Routes.DOWNLOADING)),
|
||||
// actions: [],
|
||||
title: Text('create.title'.tr),
|
||||
),
|
||||
body: DropTarget(
|
||||
onDragDone: (details) {
|
||||
_urlController.text = details.files[0].path;
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 24.0),
|
||||
child: Form(
|
||||
key: _resolveFormKey,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
autofocus: true,
|
||||
controller: _urlController,
|
||||
minLines: 1,
|
||||
maxLines: 30,
|
||||
decoration: InputDecoration(
|
||||
hintText: _hitText(),
|
||||
hintStyle: const TextStyle(fontSize: 12),
|
||||
labelText: 'create.downloadLink'.tr,
|
||||
icon: const Icon(Icons.link)),
|
||||
validator: (v) {
|
||||
return v!.trim().isNotEmpty
|
||||
? null
|
||||
: 'create.downloadLinkValid'.tr;
|
||||
}),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(15.0),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
width: 150,
|
||||
height: 40,
|
||||
),
|
||||
child: RoundedLoadingButton(
|
||||
color: Get.theme.colorScheme.secondary,
|
||||
onPressed: () async {
|
||||
if (_resolveFormKey.currentState!.validate()) {
|
||||
_confirmController.start();
|
||||
try {
|
||||
final rr = await resolve(Request(
|
||||
url: _urlController.text,
|
||||
));
|
||||
await _showResolveDialog(rr);
|
||||
} catch (e) {
|
||||
Get.snackbar('error'.tr, e.toString());
|
||||
} finally {
|
||||
_confirmController.reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
controller: _confirmController,
|
||||
child: Text('confirm'.tr),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _hitText() {
|
||||
return 'create.downloadLinkHit'.trParams({
|
||||
'append': Util.isDesktop() ? 'create.downloadLinkHitDesktop'.tr : '',
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showResolveDialog(ResolveResult rr) async {
|
||||
controller.files.value = rr.res.files;
|
||||
final appController = Get.find<AppController>();
|
||||
final setting = appController.downloaderConfig.value;
|
||||
|
||||
final createFormKey = GlobalKey<FormState>();
|
||||
final pathController = TextEditingController(text: setting.downloadDir);
|
||||
final downloadController = RoundedLoadingButtonController();
|
||||
|
||||
return showDialog<void>(
|
||||
context: Get.context!,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => AlertDialog(
|
||||
content: Builder(
|
||||
builder: (context) {
|
||||
// Get available height and width of the build area of this widget. Make a choice depending on the size.
|
||||
var height = MediaQuery.of(context).size.height;
|
||||
var width = MediaQuery.of(context).size.width;
|
||||
|
||||
return SizedBox(
|
||||
height: height * 0.5,
|
||||
width: width,
|
||||
child: Form(
|
||||
key: createFormKey,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(child: FileListView()),
|
||||
DirectorySelector(
|
||||
controller: pathController,
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(
|
||||
width: Get.theme.buttonTheme.minWidth,
|
||||
height: Get.theme.buttonTheme.height,
|
||||
),
|
||||
child: ElevatedButton(
|
||||
style:
|
||||
ElevatedButton.styleFrom(shape: const StadiumBorder())
|
||||
.copyWith(
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
Get.theme.colorScheme.background)),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
child: Text('cancel'.tr),
|
||||
),
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(
|
||||
width: Get.theme.buttonTheme.minWidth,
|
||||
height: Get.theme.buttonTheme.height,
|
||||
),
|
||||
child: RoundedLoadingButton(
|
||||
color: Get.theme.colorScheme.secondary,
|
||||
onPressed: () async {
|
||||
try {
|
||||
if (createFormKey.currentState!.validate()) {
|
||||
downloadController.start();
|
||||
|
||||
// if (Util.isAndroid()) {
|
||||
// if (!await Permission.storage.request().isGranted) {
|
||||
// Get.snackbar('error'.tr,
|
||||
// 'create.error.noStoragePermission'.tr);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
await createTask(CreateTask(
|
||||
rid: rr.id,
|
||||
opts: Options(
|
||||
name: '',
|
||||
path: pathController.text,
|
||||
selectFiles:
|
||||
controller.selectedIndexs.cast<int>())));
|
||||
Get.back();
|
||||
Get.rootDelegate.offNamed(Routes.DOWNLOADING);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('error'.tr, e.toString());
|
||||
rethrow;
|
||||
} finally {
|
||||
downloadController.reset();
|
||||
}
|
||||
},
|
||||
controller: downloadController,
|
||||
child: Text('create.download'.tr),
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/downloaded_controller.dart';
|
||||
|
||||
class DownloadedBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<DownloadedController>(
|
||||
() => DownloadedController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/model/task.dart';
|
||||
|
||||
class DownloadedController extends GetxController {
|
||||
final tasks = <Task>[].obs;
|
||||
late final Timer _timer;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await getTasksState();
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async {
|
||||
// debugPrint('timer${DateTime.now()}');
|
||||
await getTasksState();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
_timer.cancel();
|
||||
}
|
||||
|
||||
getTasksState() async {
|
||||
tasks.value = await getTasks([Status.done]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:gopeed/app/views/views/buid_task_list_view.dart';
|
||||
|
||||
import '../controllers/downloaded_controller.dart';
|
||||
|
||||
class DownloadedView extends GetView<DownloadedController> {
|
||||
const DownloadedView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BuildTaskListView(tasks: controller.tasks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/downloading_controller.dart';
|
||||
|
||||
class DownloadingBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<DownloadingController>(
|
||||
() => DownloadingController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../api/api.dart';
|
||||
import '../../../../api/model/task.dart';
|
||||
|
||||
class DownloadingController extends GetxController {
|
||||
final tasks = <Task>[].obs;
|
||||
|
||||
late final Timer _timer;
|
||||
|
||||
@override
|
||||
void onInit() async {
|
||||
super.onInit();
|
||||
await getTasksState();
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async {
|
||||
// debugPrint('timer${DateTime.now()}');
|
||||
await getTasksState();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
_timer.cancel();
|
||||
}
|
||||
|
||||
getTasksState() async {
|
||||
tasks.value = await getTasks(
|
||||
[Status.ready, Status.running, Status.pause, Status.error]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../views/views/buid_task_list_view.dart';
|
||||
import '../controllers/downloading_controller.dart';
|
||||
|
||||
class DownloadingView extends GetView<DownloadingController> {
|
||||
const DownloadingView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BuildTaskListView(tasks: controller.tasks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/home_controller.dart';
|
||||
|
||||
class HomeBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<HomeController>(
|
||||
() => HomeController(),
|
||||
fenix: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class HomeController extends GetxController {
|
||||
var currentIndex = 0.obs;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
||||
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../../../views/views/responsive_builder.dart';
|
||||
import '../controllers/home_controller.dart';
|
||||
|
||||
class HomeView extends GetView<HomeController> {
|
||||
const HomeView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetRouterOutlet.builder(builder: (context, delegate, currentRoute) {
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
body: Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
!ResponsiveBuilder.isNarrow(context)
|
||||
? NavigationRail(
|
||||
extended: true,
|
||||
labelType: NavigationRailLabelType.none,
|
||||
minExtendedWidth: 140,
|
||||
groupAlignment: 0,
|
||||
// useIndicator: false,
|
||||
onDestinationSelected: (int 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;
|
||||
}
|
||||
},
|
||||
destinations: [
|
||||
NavigationRailDestination(
|
||||
icon: const Icon(Icons.format_align_left_outlined),
|
||||
selectedIcon:
|
||||
const Icon(Icons.format_align_left_outlined),
|
||||
label: Text('downloading'.tr),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: const Icon(Icons.format_align_justify_outlined),
|
||||
selectedIcon:
|
||||
const Icon(Icons.format_align_justify_outlined),
|
||||
label: Text('downloaded'.tr),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: const Icon(Icons.settings),
|
||||
selectedIcon: const Icon(Icons.settings),
|
||||
label: Text('setting'.tr),
|
||||
),
|
||||
],
|
||||
selectedIndex: controller.currentIndex.value,
|
||||
leading: const Icon(Icons.logo_dev),
|
||||
// trailing: const Icon(Icons.info_outline),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
Expanded(
|
||||
child: GetRouterOutlet(
|
||||
initialRoute: Routes.DOWNLOADING,
|
||||
// anchorRoute: '/',
|
||||
// filterPages: (afterAnchor) {
|
||||
// logger.w(afterAnchor);
|
||||
// logger.w(afterAnchor.take(1));
|
||||
// return afterAnchor.take(1);
|
||||
// },
|
||||
))
|
||||
]),
|
||||
bottomNavigationBar: ResponsiveBuilder.isNarrow(context)
|
||||
? StylishBottomBar(
|
||||
option: AnimatedBarOptions(
|
||||
iconSize: 32,
|
||||
barAnimation: BarAnimation.blink,
|
||||
iconStyle: IconStyle.Default,
|
||||
opacity: 0.3,
|
||||
),
|
||||
items: [
|
||||
BottomBarItem(
|
||||
icon: const Icon(Icons.format_align_left_outlined),
|
||||
selectedColor: Get.theme.primaryColor,
|
||||
title: Text('downloading'.tr)),
|
||||
BottomBarItem(
|
||||
icon: const Icon(Icons.format_align_justify_outlined),
|
||||
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(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/root_controller.dart';
|
||||
|
||||
class RootBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<RootController>(() => RootController(), fenix: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class RootController extends GetxController {}
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../routes/app_pages.dart';
|
||||
import '../controllers/root_controller.dart';
|
||||
|
||||
class RootView extends GetView<RootController> {
|
||||
const RootView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GetRouterOutlet.builder(
|
||||
builder: (context, delegate, current) {
|
||||
return GetRouterOutlet(
|
||||
initialRoute: Routes.HOME,
|
||||
// anchorRoute: '/',
|
||||
// filterPages: (afterAnchor) {
|
||||
// return afterAnchor.take(1);
|
||||
// },
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../controllers/setting_controller.dart';
|
||||
|
||||
class SettingBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut<SettingController>(
|
||||
() => SettingController(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class SettingController extends GetxController {
|
||||
final tapStatues = <String, bool>{}.obs;
|
||||
|
||||
// set all tap status to false
|
||||
void clearTap() {
|
||||
tapStatues.updateAll((key, value) => false);
|
||||
}
|
||||
|
||||
// set one tap status to true
|
||||
void onTap(String key) {
|
||||
clearTap();
|
||||
tapStatues[key] = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../../i18n/messages.dart';
|
||||
import '../../../../util/package_info.dart';
|
||||
import '../../../../util/util.dart';
|
||||
import '../../../views/views/check_list_view.dart';
|
||||
import '../../../views/views/directory_selector.dart';
|
||||
import '../../../views/views/outlined_button_loading.dart';
|
||||
import '../../app/controllers/app_controller.dart';
|
||||
import '../controllers/setting_controller.dart';
|
||||
|
||||
const _padding = SizedBox(height: 10);
|
||||
final _divider = const Divider().paddingOnly(left: 10, right: 10);
|
||||
|
||||
class SettingView extends GetView<SettingController> {
|
||||
const SettingView({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appController = Get.find<AppController>();
|
||||
final downloaderCfg = appController.downloaderConfig;
|
||||
final startCfg = appController.startConfig;
|
||||
|
||||
Timer? timer;
|
||||
debounceSave({bool needRestart = false}) {
|
||||
var completer = Completer<void>();
|
||||
timer?.cancel();
|
||||
timer = Timer(const Duration(milliseconds: 500), () {
|
||||
appController
|
||||
.saveConfig()
|
||||
.then(completer.complete)
|
||||
.onError(completer.completeError);
|
||||
if (needRestart) {
|
||||
Get.snackbar('tip'.tr, 'effectAfterRestart'.tr);
|
||||
}
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
// download basic config items start
|
||||
final buildDownloadDir = _buildConfigItem(
|
||||
'downloadDir', () => downloaderCfg.value.downloadDir, (Key key) {
|
||||
final downloadDirController =
|
||||
TextEditingController(text: downloaderCfg.value.downloadDir);
|
||||
downloadDirController.addListener(() async {
|
||||
if (downloadDirController.text != downloaderCfg.value.downloadDir) {
|
||||
downloaderCfg.value.downloadDir = downloadDirController.text;
|
||||
if (Util.isDesktop()) {
|
||||
controller.clearTap();
|
||||
}
|
||||
|
||||
await debounceSave();
|
||||
}
|
||||
});
|
||||
return DirectorySelector(
|
||||
controller: downloadDirController,
|
||||
showLabel: false,
|
||||
);
|
||||
});
|
||||
|
||||
// http config items start
|
||||
final httpConfig = downloaderCfg.value.protocolConfig.http;
|
||||
final buildHttpConnections = _buildConfigItem(
|
||||
'connections'.tr, () => httpConfig.connections.toString(), (Key key) {
|
||||
final connectionsController =
|
||||
TextEditingController(text: httpConfig.connections.toString());
|
||||
connectionsController.addListener(() async {
|
||||
if (connectionsController.text.isNotEmpty &&
|
||||
connectionsController.text != httpConfig.connections.toString()) {
|
||||
httpConfig.connections = int.parse(connectionsController.text);
|
||||
|
||||
await debounceSave();
|
||||
}
|
||||
});
|
||||
|
||||
return TextField(
|
||||
key: key,
|
||||
focusNode: FocusNode(),
|
||||
controller: connectionsController,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
_NumericalRangeFormatter(min: 1, max: 256),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
// bt config items start
|
||||
final btExtConfig = downloaderCfg.value.extra.bt;
|
||||
|
||||
final buildBtTrackerSubscribeUrls = _buildConfigItem(
|
||||
'subscribeTracker'.tr,
|
||||
() => 'items'.trParams(
|
||||
{'count': btExtConfig.trackerSubscribeUrls.length.toString()}),
|
||||
(Key key) {
|
||||
final trackerUpdateController = OutlinedButtonLoadingController();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 200,
|
||||
child: CheckListView(
|
||||
items: allTrackerSubscribeUrls,
|
||||
checked: btExtConfig.trackerSubscribeUrls,
|
||||
onChanged: (value) {
|
||||
btExtConfig.trackerSubscribeUrls = value;
|
||||
|
||||
debounceSave();
|
||||
},
|
||||
),
|
||||
),
|
||||
_padding,
|
||||
Row(
|
||||
children: [
|
||||
OutlinedButtonLoading(
|
||||
onPressed: () async {
|
||||
trackerUpdateController.start();
|
||||
try {
|
||||
await appController.trackerUpdate();
|
||||
} catch (e) {
|
||||
Get.snackbar('error'.tr, 'subscribeFail'.tr);
|
||||
} finally {
|
||||
trackerUpdateController.stop();
|
||||
}
|
||||
},
|
||||
controller: trackerUpdateController,
|
||||
child: Text('update'.tr),
|
||||
),
|
||||
Expanded(
|
||||
child: SwitchListTile(
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
value: true,
|
||||
onChanged: (bool value) {},
|
||||
title: Text('updateDaily'.tr)),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text('lastUpdate'.trParams({
|
||||
'time': btExtConfig.lastTrackerUpdateTime != null
|
||||
? DateFormat('yyyy-MM-dd HH:mm:ss')
|
||||
.format(btExtConfig.lastTrackerUpdateTime!)
|
||||
: ''
|
||||
})),
|
||||
],
|
||||
);
|
||||
});
|
||||
final buildBtTrackers = _buildConfigItem(
|
||||
'addTracker'.tr,
|
||||
() => 'items'
|
||||
.trParams({'count': btExtConfig.customTrackers.length.toString()}),
|
||||
(Key key) {
|
||||
final trackersController = TextEditingController(
|
||||
text: btExtConfig.customTrackers.join('\r\n').toString());
|
||||
const ls = LineSplitter();
|
||||
return TextField(
|
||||
key: key,
|
||||
focusNode: FocusNode(),
|
||||
controller: trackersController,
|
||||
keyboardType: TextInputType.multiline,
|
||||
maxLines: 5,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'addTrackerHit'.tr,
|
||||
),
|
||||
onChanged: (value) async {
|
||||
btExtConfig.customTrackers = ls.convert(value);
|
||||
appController.refreshTrackers();
|
||||
|
||||
await debounceSave();
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// ui config items start
|
||||
final buildTheme = _buildConfigItem(
|
||||
'theme',
|
||||
() => _getThemeName(downloaderCfg.value.extra.themeMode),
|
||||
(Key key) => DropdownButton<String>(
|
||||
key: key,
|
||||
value: downloaderCfg.value.extra.themeMode,
|
||||
onChanged: (value) async {
|
||||
downloaderCfg.update((val) {
|
||||
val?.extra.themeMode = value!;
|
||||
});
|
||||
Get.changeThemeMode(ThemeMode.values.byName(value!));
|
||||
controller.clearTap();
|
||||
|
||||
await debounceSave();
|
||||
},
|
||||
items: ThemeMode.values
|
||||
.map((e) => DropdownMenuItem<String>(
|
||||
value: e.name,
|
||||
child: Text(_getThemeName(e.name)),
|
||||
))
|
||||
.toList(),
|
||||
));
|
||||
final buildLocale = _buildConfigItem(
|
||||
'locale',
|
||||
() => messages.keys[downloaderCfg.value.extra.locale]!['label']!,
|
||||
(Key key) => DropdownButton<String>(
|
||||
key: key,
|
||||
value: downloaderCfg.value.extra.locale,
|
||||
isDense: true,
|
||||
onChanged: (value) async {
|
||||
downloaderCfg.update((val) {
|
||||
val!.extra.locale = value!;
|
||||
});
|
||||
Get.updateLocale(toLocale(value!));
|
||||
controller.clearTap();
|
||||
|
||||
await debounceSave();
|
||||
},
|
||||
items: messages.keys.keys
|
||||
.map((e) => DropdownMenuItem<String>(
|
||||
value: e,
|
||||
child: Text(messages.keys[e]!['label']!),
|
||||
))
|
||||
.toList(),
|
||||
));
|
||||
|
||||
// about config items start
|
||||
buildHomepage() => ListTile(
|
||||
title: Text('homepage'.tr),
|
||||
subtitle: const Text('https://github.com/GopeedLab/gopeed'),
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse('https://github.com/GopeedLab/gopeed'),
|
||||
mode: LaunchMode.externalApplication);
|
||||
},
|
||||
);
|
||||
buildVersion() => ListTile(
|
||||
title: Text('version'.tr),
|
||||
subtitle: Text(packageInfo.version),
|
||||
);
|
||||
|
||||
// advanced config items start
|
||||
final buildApiProtocol = _buildConfigItem(
|
||||
'protocol'.tr,
|
||||
() => startCfg.value.network == 'tcp'
|
||||
? 'TCP ${startCfg.value.address}'
|
||||
: 'Unix',
|
||||
(Key key) {
|
||||
final items = <Widget>[
|
||||
SizedBox(
|
||||
width: 150,
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: startCfg.value.network,
|
||||
onChanged: (value) async {
|
||||
startCfg.update((val) {
|
||||
val!.network = value!;
|
||||
});
|
||||
|
||||
await debounceSave(needRestart: true);
|
||||
},
|
||||
items: [
|
||||
!Util.isMobile()
|
||||
? const DropdownMenuItem<String>(
|
||||
value: 'tcp',
|
||||
child: Text('TCP'),
|
||||
)
|
||||
: null,
|
||||
Util.isUnix()
|
||||
? const DropdownMenuItem<String>(
|
||||
value: 'unix',
|
||||
child: Text('Unix'),
|
||||
)
|
||||
: null,
|
||||
].where((e) => e != null).map((e) => e!).toList(),
|
||||
),
|
||||
)
|
||||
];
|
||||
if (Util.isDesktop() && startCfg.value.network == 'tcp') {
|
||||
final arr = startCfg.value.address.split(':');
|
||||
var ip = '127.0.0.1';
|
||||
var port = '0';
|
||||
if (arr.length > 1) {
|
||||
ip = arr[0];
|
||||
port = arr[1];
|
||||
}
|
||||
|
||||
final ipController = TextEditingController(text: ip);
|
||||
final portController = TextEditingController(text: port);
|
||||
updateAddress() async {
|
||||
final newAddress = '${ipController.text}:${portController.text}';
|
||||
if (newAddress != startCfg.value.address) {
|
||||
startCfg.value.address = newAddress;
|
||||
|
||||
await debounceSave(needRestart: true);
|
||||
}
|
||||
}
|
||||
|
||||
ipController.addListener(updateAddress);
|
||||
portController.addListener(updateAddress);
|
||||
items.addAll([
|
||||
const Padding(padding: EdgeInsets.only(left: 20)),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: TextFormField(
|
||||
controller: ipController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'IP',
|
||||
contentPadding: EdgeInsets.all(0.0),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp('[0-9.]')),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(left: 10)),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: TextFormField(
|
||||
controller: portController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'port'.tr,
|
||||
contentPadding: const EdgeInsets.all(0.0),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
_NumericalRangeFormatter(min: 0, max: 65535),
|
||||
],
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return Form(
|
||||
child: Row(
|
||||
children: items,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
final buildApiToken = _buildConfigItem('apiToken'.tr,
|
||||
() => startCfg.value.apiToken.isEmpty ? 'notSet'.tr : 'seted'.tr,
|
||||
(Key key) {
|
||||
final apiTokenController =
|
||||
TextEditingController(text: startCfg.value.apiToken);
|
||||
apiTokenController.addListener(() async {
|
||||
if (apiTokenController.text != startCfg.value.apiToken) {
|
||||
startCfg.value.apiToken = apiTokenController.text;
|
||||
|
||||
await debounceSave(needRestart: true);
|
||||
}
|
||||
});
|
||||
apiTokenController.addListener(() async {});
|
||||
return TextField(
|
||||
key: key,
|
||||
obscureText: true,
|
||||
controller: apiTokenController,
|
||||
focusNode: FocusNode(),
|
||||
);
|
||||
});
|
||||
|
||||
return Obx(() {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
controller.clearTap();
|
||||
},
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(56),
|
||||
child: AppBar(
|
||||
bottom: TabBar(
|
||||
tabs: [
|
||||
Tab(
|
||||
text: 'basic'.tr,
|
||||
),
|
||||
Tab(
|
||||
text: 'advanced'.tr,
|
||||
),
|
||||
],
|
||||
),
|
||||
)),
|
||||
body: TabBarView(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: _addPadding([
|
||||
Text('general'.tr),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([
|
||||
buildDownloadDir(),
|
||||
]),
|
||||
)),
|
||||
const Text('HTTP'),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([
|
||||
buildHttpConnections(),
|
||||
]),
|
||||
)),
|
||||
const Text('BitTorrent'),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([
|
||||
buildBtTrackerSubscribeUrls(),
|
||||
buildBtTrackers(),
|
||||
]),
|
||||
)),
|
||||
Text('ui'.tr),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([
|
||||
buildTheme(),
|
||||
buildLocale(),
|
||||
]),
|
||||
)),
|
||||
Text('about'.tr),
|
||||
Card(
|
||||
child: Column(
|
||||
children: _addDivider([
|
||||
buildHomepage(),
|
||||
buildVersion(),
|
||||
]),
|
||||
)),
|
||||
]),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
Card(
|
||||
child: Column(
|
||||
children: [
|
||||
..._addDivider([
|
||||
buildApiProtocol(),
|
||||
Util.isDesktop() && startCfg.value.network == 'tcp'
|
||||
? buildApiToken()
|
||||
: null,
|
||||
]),
|
||||
],
|
||||
)),
|
||||
],
|
||||
),
|
||||
],
|
||||
).paddingOnly(left: 16, right: 16, top: 16, bottom: 64)),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _tapInputWidget(GlobalKey key) {
|
||||
if (key.currentContext == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.currentContext?.widget is TextField) {
|
||||
final textField = key.currentContext?.widget as TextField;
|
||||
textField.focusNode?.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
/* GestureDetector? detector;
|
||||
void searchForGestureDetector(BuildContext? element) {
|
||||
element?.visitChildElements((element) {
|
||||
if (element.widget is GestureDetector) {
|
||||
detector = element.widget as GestureDetector?;
|
||||
} else {
|
||||
searchForGestureDetector(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
searchForGestureDetector(key.currentContext);
|
||||
detector?.onTap?.call(); */
|
||||
}
|
||||
|
||||
Widget Function() _buildConfigItem(
|
||||
String label, String Function() text, Widget Function(Key key) input) {
|
||||
final tapStatues = controller.tapStatues;
|
||||
final inputKey = GlobalKey();
|
||||
return () => ListTile(
|
||||
title: Text(label.tr),
|
||||
subtitle: tapStatues[label] ?? false ? input(inputKey) : Text(text()),
|
||||
onTap: () {
|
||||
controller.onTap(label);
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
_tapInputWidget(inputKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> _addPadding(List<Widget> widgets) {
|
||||
final result = <Widget>[];
|
||||
for (var i = 0; i < widgets.length; i++) {
|
||||
result.add(widgets[i]);
|
||||
result.add(_padding);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
List<Widget> _addDivider(List<Widget?> widgets) {
|
||||
final result = <Widget>[];
|
||||
final newArr = widgets.where((e) => e != null).map((e) => e!).toList();
|
||||
for (var i = 0; i < newArr.length; i++) {
|
||||
result.add(newArr[i]);
|
||||
if (i != newArr.length - 1) {
|
||||
result.add(_divider);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String _getThemeName(String? themeMode) {
|
||||
switch (ThemeMode.values.byName(themeMode ?? ThemeMode.system.name)) {
|
||||
case ThemeMode.light:
|
||||
return 'themeLight'.tr;
|
||||
case ThemeMode.dark:
|
||||
return 'themeDark'.tr;
|
||||
default:
|
||||
return 'themeSystem'.tr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// class _ConfigItem {
|
||||
// late String label;
|
||||
// late String Function() text;
|
||||
// late Widget Function(Key key) inputItem;
|
||||
//
|
||||
// _ConfigItem(this.label, this.text, this.inputItem);
|
||||
// }
|
||||
|
||||
class _NumericalRangeFormatter extends TextInputFormatter {
|
||||
final int min;
|
||||
final int max;
|
||||
|
||||
_NumericalRangeFormatter({required this.min, required this.max});
|
||||
|
||||
@override
|
||||
TextEditingValue formatEditUpdate(
|
||||
TextEditingValue oldValue,
|
||||
TextEditingValue newValue,
|
||||
) {
|
||||
if (newValue.text.isEmpty) {
|
||||
return newValue;
|
||||
}
|
||||
var intVal = int.tryParse(newValue.text);
|
||||
if (intVal == null) {
|
||||
return oldValue;
|
||||
}
|
||||
if (intVal < min) {
|
||||
return newValue.copyWith(text: min.toString());
|
||||
} else if (intVal > max) {
|
||||
return oldValue.copyWith(text: max.toString());
|
||||
} else {
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user