From a34c18b262275c14164a11b82956ef072dc5fe0a Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Tue, 8 Apr 2025 16:10:55 +0800 Subject: [PATCH] feat: webdav Closes #432 Signed-off-by: bggRGjQaUbCoE --- lib/pages/setting/view.dart | 9 +++ lib/pages/webdav/view.dart | 134 +++++++++++++++++++++++++++++++++++ lib/pages/webdav/webdav.dart | 88 +++++++++++++++++++++++ lib/router/app_pages.dart | 3 + lib/utils/storage.dart | 18 +++++ pubspec.lock | 8 +++ pubspec.yaml | 1 + 7 files changed, 261 insertions(+) create mode 100644 lib/pages/webdav/view.dart create mode 100644 lib/pages/webdav/webdav.dart diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index 7e9dff2af..e21dcaed2 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -7,12 +7,14 @@ import 'package:PiliPlus/pages/setting/privacy_setting.dart'; import 'package:PiliPlus/pages/setting/recommend_setting.dart'; import 'package:PiliPlus/pages/setting/style_setting.dart'; import 'package:PiliPlus/pages/setting/video_setting.dart'; +import 'package:PiliPlus/pages/webdav/view.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'widgets/multi_select_dialog.dart'; @@ -84,6 +86,11 @@ class _SettingPageState extends State { subtitle: '震动、搜索、收藏、ai、评论、动态、代理、更新检查等', icon: Icons.extension_outlined, ), + _SettingsModel( + name: 'webdavSetting', + title: 'WebDAV 设置', + icon: MdiIcons.databaseCogOutline, + ), _SettingsModel( name: 'about', title: '关于', @@ -104,6 +111,7 @@ class _SettingPageState extends State { 'playSetting' => '播放器设置', 'styleSetting' => '外观设置', 'extraSetting' => '其它设置', + 'webdavSetting' => 'WebDAV 设置', 'about' => '关于', _ => '设置', }), @@ -127,6 +135,7 @@ class _SettingPageState extends State { 'playSetting' => PlaySetting(showAppBar: false), 'styleSetting' => StyleSetting(showAppBar: false), 'extraSetting' => ExtraSetting(showAppBar: false), + 'webdavSetting' => WebDavSettingPage(showAppBar: false), 'about' => AboutPage(showAppBar: false), _ => const SizedBox.shrink(), }, diff --git a/lib/pages/webdav/view.dart b/lib/pages/webdav/view.dart new file mode 100644 index 000000000..c71c190c5 --- /dev/null +++ b/lib/pages/webdav/view.dart @@ -0,0 +1,134 @@ +import 'package:PiliPlus/pages/webdav/webdav.dart'; +import 'package:PiliPlus/utils/storage.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +class WebDavSettingPage extends StatefulWidget { + const WebDavSettingPage({ + super.key, + this.showAppBar, + }); + + final bool? showAppBar; + + @override + State createState() => _WebDavSettingPageState(); +} + +class _WebDavSettingPageState extends State { + final _uriCtr = TextEditingController(text: GStorage.webdavUri); + final _usernameCtr = TextEditingController(text: GStorage.webdavUsername); + final _passwordCtr = TextEditingController(text: GStorage.webdavPassword); + final _directoryCtr = TextEditingController(text: GStorage.webdavDirectory); + + @override + void dispose() { + _uriCtr.dispose(); + _usernameCtr.dispose(); + _passwordCtr.dispose(); + _directoryCtr.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + EdgeInsets padding = MediaQuery.paddingOf(context); + return Scaffold( + appBar: widget.showAppBar == false + ? null + : AppBar(title: const Text('WebDAV 设置')), + body: ListView( + padding: padding.copyWith( + top: 20, + left: padding.left + 20, + right: padding.right + 20, + bottom: padding.bottom + 90, + ), + children: [ + TextField( + controller: _uriCtr, + decoration: const InputDecoration( + labelText: '地址', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _usernameCtr, + decoration: const InputDecoration( + labelText: '用户', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _passwordCtr, + decoration: const InputDecoration( + labelText: '密码', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _directoryCtr, + decoration: const InputDecoration( + labelText: '路径', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: WebDav().backup, + child: const Text('备份设置'), + ), + ), + const SizedBox(width: 20), + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + onPressed: WebDav().restore, + child: const Text('恢复设置'), + ), + ), + ], + ), + ], + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.save), + onPressed: () async { + if (_uriCtr.text.isEmpty) { + SmartDialog.showToast('地址不能为空'); + return; + } + await GStorage.setting.put(SettingBoxKey.webdavUri, _uriCtr.text); + await GStorage.setting + .put(SettingBoxKey.webdavUsername, _usernameCtr.text); + await GStorage.setting + .put(SettingBoxKey.webdavPassword, _passwordCtr.text); + await GStorage.setting + .put(SettingBoxKey.webdavDirectory, _directoryCtr.text); + try { + final res = await WebDav().init(); + SmartDialog.showToast('配置${res ? '成功' : '失败'}'); + } catch (e) { + SmartDialog.showToast('配置失败: ${e.toString()}'); + return; + } + }, + ), + ); + } +} diff --git a/lib/pages/webdav/webdav.dart b/lib/pages/webdav/webdav.dart new file mode 100644 index 000000000..fcf6a54a7 --- /dev/null +++ b/lib/pages/webdav/webdav.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:PiliPlus/utils/extension.dart'; +import 'package:PiliPlus/utils/storage.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:webdav_client/webdav_client.dart' as webdav; + +class WebDav { + late String _webDavUri; + late String _webDavUsername; + late String _webDavPassword; + late String _webdavDirectory; + + webdav.Client? _client; + + WebDav._internal(); + static final WebDav _instance = WebDav._internal(); + factory WebDav() => _instance; + + Future init() async { + _webDavUri = GStorage.webdavUri; + _webDavUsername = GStorage.webdavUsername; + _webDavPassword = GStorage.webdavPassword; + _webdavDirectory = GStorage.webdavDirectory; + if (_webdavDirectory.endsWith('/').not) { + _webdavDirectory += '/'; + } + _webdavDirectory += 'PiliPlus'; + + try { + _client = null; + final client = webdav.newClient( + _webDavUri, + user: _webDavUsername, + password: _webDavPassword, + ) + ..setHeaders({'accept-charset': 'utf-8'}) + ..setConnectTimeout(4000) + ..setReceiveTimeout(4000) + ..setSendTimeout(4000); + + await client.mkdirAll(_webdavDirectory); + + _client = client; + return true; + } catch (_) { + return false; + } + } + + Future backup() async { + if (_client == null) { + if (await init() == false) { + SmartDialog.showToast('备份失败,请检查配置'); + return; + } + } + try { + final path = '$_webdavDirectory/piliplus_settings.json'; + final file = File(path); + if (await file.exists()) { + await file.delete(); + } + String data = await GStorage.exportAllSettings(); + await _client!.write(path, utf8.encode(data)); + SmartDialog.showToast('备份成功'); + } catch (e) { + SmartDialog.showToast('备份失败: $e'); + } + } + + Future restore() async { + if (_client == null) { + if (await init() == false) { + SmartDialog.showToast('恢复失败,请检查配置'); + return; + } + } + try { + final path = '$_webdavDirectory/piliplus_settings.json'; + final data = await _client!.read(path); + await GStorage.importAllSettings(utf8.decode(data)); + SmartDialog.showToast('恢复成功'); + } catch (e) { + SmartDialog.showToast('恢复失败: $e'); + } + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index b8e5b240f..ba2a1452d 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -7,6 +7,7 @@ import 'package:PiliPlus/pages/setting/sponsor_block_page.dart'; import 'package:PiliPlus/pages/setting/view.dart'; import 'package:PiliPlus/pages/video/detail/introduction/widgets/create_fav_page.dart'; import 'package:PiliPlus/pages/video/detail/view_v.dart'; +import 'package:PiliPlus/pages/webdav/view.dart'; import 'package:PiliPlus/pages/webview/webview_page.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -175,6 +176,8 @@ class Routes { name: '/navbarSetting', page: () => const NavigationBarSetPage()), CustomGetPage( name: '/settingsSearch', page: () => const SettingsSearchPage()), + CustomGetPage( + name: '/webdavSetting', page: () => const WebDavSettingPage()), ]; } diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 82e30a40f..0949371ab 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -451,6 +451,18 @@ class GStorage { static bool get navSearchStreamDebounce => GStorage.setting .get(SettingBoxKey.navSearchStreamDebounce, defaultValue: false); + static String get webdavUri => + GStorage.setting.get(SettingBoxKey.webdavUri, defaultValue: ''); + + static String get webdavUsername => + GStorage.setting.get(SettingBoxKey.webdavUsername, defaultValue: ''); + + static String get webdavPassword => + GStorage.setting.get(SettingBoxKey.webdavPassword, defaultValue: ''); + + static String get webdavDirectory => + GStorage.setting.get(SettingBoxKey.webdavDirectory, defaultValue: '/'); + static List get dynamicDetailRatio => List.from(setting .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); @@ -737,6 +749,12 @@ class SettingBoxKey { recordSearchHistory = 'recordSearchHistory', navSearchStreamDebounce = 'navSearchStreamDebounce', + // WebDAV + webdavUri = 'webdavUri', + webdavUsername = 'webdavUsername', + webdavPassword = 'webdavPassword', + webdavDirectory = 'webdavDirectory', + // Sponsor Block enableSponsorBlock = 'enableSponsorBlock', blockSettings = 'blockSettings', diff --git a/pubspec.lock b/pubspec.lock index 2d7f68fda..5964b82cc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2022,6 +2022,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + webdav_client: + dependency: "direct main" + description: + name: webdav_client + sha256: "682fffc50b61dc0e8f46717171db03bf9caaa17347be41c0c91e297553bf86b2" + url: "https://pub.dev" + source: hosted + version: "1.2.2" webview_cookie_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 95b758220..e56484ae0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -189,6 +189,7 @@ dependencies: fl_chart: ^0.69.2 synchronized: ^3.3.0 document_file_save_plus: ^2.0.0 + webdav_client: ^1.2.2 dependency_overrides: screen_brightness: ^2.0.1