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