feat: webdav

Closes #432

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-08 16:10:55 +08:00
parent 560b1e40cc
commit a34c18b262
7 changed files with 261 additions and 0 deletions

View File

@@ -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<SettingPage> {
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<SettingPage> {
'playSetting' => '播放器设置',
'styleSetting' => '外观设置',
'extraSetting' => '其它设置',
'webdavSetting' => 'WebDAV 设置',
'about' => '关于',
_ => '设置',
}),
@@ -127,6 +135,7 @@ class _SettingPageState extends State<SettingPage> {
'playSetting' => PlaySetting(showAppBar: false),
'styleSetting' => StyleSetting(showAppBar: false),
'extraSetting' => ExtraSetting(showAppBar: false),
'webdavSetting' => WebDavSettingPage(showAppBar: false),
'about' => AboutPage(showAppBar: false),
_ => const SizedBox.shrink(),
},

134
lib/pages/webdav/view.dart Normal file
View File

@@ -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<WebDavSettingPage> createState() => _WebDavSettingPageState();
}
class _WebDavSettingPageState extends State<WebDavSettingPage> {
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;
}
},
),
);
}
}

View File

@@ -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<bool> 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');
}
}
}

View File

@@ -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()),
];
}

View File

@@ -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<double> get dynamicDetailRatio => List<double>.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',

View File

@@ -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:

View File

@@ -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