feat: InportExportDialog (#1048)

This commit is contained in:
My-Responsitories
2025-08-18 21:25:00 +08:00
committed by GitHub
parent 08c3789321
commit ed57697fdc
4 changed files with 188 additions and 198 deletions

View File

@@ -1,9 +1,9 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/services/loggeer.dart';
import 'package:PiliPlus/utils/accounts.dart';
@@ -24,6 +24,9 @@ import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:re_highlight/languages/json.dart';
import 'package:re_highlight/re_highlight.dart';
import 'package:re_highlight/styles/github.dart';
import 'package:share_plus/share_plus.dart';
class AboutPage extends StatefulWidget {
@@ -222,199 +225,33 @@ Commit Hash: ${BuildConfig.commitHash}''',
ListTile(
title: const Text('导入/导出登录信息'),
leading: const Icon(Icons.import_export_outlined),
onTap: () => showDialog(
context: context,
builder: (context) => SimpleDialog(
title: const Text('导入/导出登录信息'),
clipBehavior: Clip.hardEdge,
children: [
ListTile(
dense: true,
title: const Text('导出', style: style),
onTap: () {
Get.back();
String res = jsonEncode(Accounts.account.toMap());
Utils.copyText(res);
},
),
ListTile(
dense: true,
title: const Text('导入', style: style),
onTap: () async {
Get.back();
ClipboardData? data = await Clipboard.getData(
'text/plain',
);
if (data?.text?.isNotEmpty != true) {
SmartDialog.showToast('剪贴板无数据');
return;
}
if (!context.mounted) return;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('是否导入以下登录信息?'),
content: SingleChildScrollView(
child: Text(data!.text!),
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: outline),
),
),
TextButton(
onPressed: () {
Get.back();
try {
final res = (jsonDecode(data.text!) as Map)
.map(
(key, value) => MapEntry(
key,
LoginAccount.fromJson(value),
),
);
Accounts.account
.putAll(res)
.whenComplete(Accounts.refresh)
.whenComplete(() {
MineController.anonymity.value =
!Accounts.get(
AccountType.heartbeat,
).isLogin;
if (Accounts.main.isLogin) {
return LoginUtils.onLoginMain();
}
});
} catch (e) {
SmartDialog.showToast('导入失败:$e');
}
},
child: const Text('确定'),
),
],
);
},
);
},
),
],
),
onTap: () => showInportExportDialog<Map>(
context,
title: '登录信息',
toJson: () => jsonEncode(Accounts.account.toMap()),
fromJson: (json) async {
final res = json.map(
(key, value) => MapEntry(key, LoginAccount.fromJson(value)),
);
await Accounts.account.putAll(res);
await Accounts.refresh();
MineController.anonymity.value = !Accounts.heartbeat.isLogin;
if (Accounts.main.isLogin) {
await LoginUtils.onLoginMain();
}
},
),
),
ListTile(
title: const Text('导入/导出设置'),
dense: false,
leading: const Icon(Icons.import_export_outlined),
onTap: () => showDialog(
context: context,
builder: (context) {
return SimpleDialog(
clipBehavior: Clip.hardEdge,
title: const Text('导入/导出设置'),
children: [
ListTile(
dense: true,
title: const Text('导出文件至本地', style: style),
onTap: () async {
Get.back();
final res = utf8.encode(GStorage.exportAllSettings());
final name =
'piliplus_settings_${context.isTablet ? 'pad' : 'phone'}_'
'${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json';
try {
DocumentFileSavePlusPlatform.instance
.saveMultipleFiles(
dataList: [res],
fileNameList: [name],
mimeTypeList: [Headers.jsonContentType],
);
if (Platform.isAndroid) {
SmartDialog.showToast('已保存');
}
} catch (e) {
SharePlus.instance.share(
ShareParams(
files: [
XFile.fromData(
res,
name: name,
mimeType: Headers.jsonContentType,
),
],
sharePositionOrigin:
await Utils.sharePositionOrigin,
),
);
}
},
),
ListTile(
dense: true,
title: const Text('导出设置至剪贴板', style: style),
onTap: () {
Get.back();
String data = GStorage.exportAllSettings();
Utils.copyText(data);
},
),
ListTile(
dense: true,
title: const Text('从剪贴板导入设置', style: style),
onTap: () async {
Get.back();
ClipboardData? data = await Clipboard.getData(
'text/plain',
);
if (data == null ||
data.text == null ||
data.text!.isEmpty) {
SmartDialog.showToast('剪贴板无数据');
return;
}
if (!context.mounted) return;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('是否导入如下设置?'),
content: SingleChildScrollView(
child: Text(data.text!),
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: outline),
),
),
TextButton(
onPressed: () async {
Get.back();
try {
await GStorage.importAllSettings(
data.text!,
);
SmartDialog.showToast('导入成功');
} catch (e) {
SmartDialog.showToast('导入失败:$e');
}
},
child: const Text('确定'),
),
],
);
},
);
},
),
],
);
},
onTap: () => showInportExportDialog(
context,
title: '设置',
label: GStorage.setting.name,
toJson: GStorage.exportAllSettings,
fromJson: GStorage.importAllJsonSettings,
),
),
ListTile(
@@ -466,3 +303,128 @@ Commit Hash: ${BuildConfig.commitHash}''',
);
}
}
Future<void> showInportExportDialog<T>(
BuildContext context, {
required String title,
String? label,
required String Function() toJson,
required FutureOr<void> Function(T json) fromJson,
}) => showDialog(
context: context,
builder: (context) {
const style = TextStyle(fontSize: 15);
return SimpleDialog(
clipBehavior: Clip.hardEdge,
title: Text('导入/导出$title'),
children: [
if (label != null)
ListTile(
dense: true,
title: const Text('导出文件至本地', style: style),
onTap: () async {
Get.back();
final res = utf8.encode(toJson());
final name =
'piliplus_${label}_${context.isTablet ? 'pad' : 'phone'}_'
'${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json';
try {
DocumentFileSavePlusPlatform.instance.saveMultipleFiles(
dataList: [res],
fileNameList: [name],
mimeTypeList: const [Headers.jsonContentType],
);
if (Platform.isAndroid) {
SmartDialog.showToast('已保存');
}
} catch (e) {
SharePlus.instance.share(
ShareParams(
files: [
XFile.fromData(
res,
name: name,
mimeType: Headers.jsonContentType,
),
],
sharePositionOrigin: await Utils.sharePositionOrigin,
),
);
}
},
),
ListTile(
dense: true,
title: Text('导出$title至剪贴板', style: style),
onTap: () {
Get.back();
Utils.copyText(toJson());
},
),
ListTile(
dense: true,
title: Text('从剪贴板导入$title', style: style),
onTap: () async {
Get.back();
ClipboardData? data = await Clipboard.getData(
'text/plain',
);
if (data?.text?.isNotEmpty != true) {
SmartDialog.showToast('剪贴板无数据');
return;
}
if (!context.mounted) return;
final text = data!.text!;
late final T json;
late final String formatText;
try {
json = jsonDecode(text);
formatText = const JsonEncoder.withIndent(' ').convert(json);
} catch (e) {
SmartDialog.showToast('解析json失败$e');
return;
}
final renderer = TextSpanRenderer(const TextStyle(), githubTheme);
Highlight()
..registerLanguage('json', langJson)
..highlight(code: formatText, language: 'json').render(renderer);
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('是否导入如下$title'),
content: SingleChildScrollView(
child: Text.rich(renderer.span!),
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () async {
Get.back();
try {
await fromJson(json);
SmartDialog.showToast('导入成功');
} catch (e) {
SmartDialog.showToast('导入失败:$e');
}
},
child: const Text('确定'),
),
],
);
},
);
},
),
],
);
},
);

View File

@@ -150,7 +150,6 @@ class SSearchController extends GetxController
historyList
..remove(controller.text)
..insert(0, controller.text);
GStorage.historyWord.put('cacheList', historyList);
}
searchFocusNode.unfocus();
@@ -198,7 +197,6 @@ class SSearchController extends GetxController
void onLongSelect(String word) {
historyList.remove(word);
GStorage.historyWord.put('cacheList', historyList);
}
void onClearHistory() {
@@ -207,13 +205,13 @@ class SSearchController extends GetxController
title: '确定清空搜索历史?',
onConfirm: () {
historyList.clear();
GStorage.historyWord.put('cacheList', []);
},
);
}
@override
void onClose() {
GStorage.historyWord.put('cacheList', historyList);
subDispose();
searchFocusNode.dispose();
controller.dispose();

View File

@@ -1,7 +1,10 @@
import 'dart:convert';
import 'package:PiliPlus/common/widgets/disabled_icon.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/search/search_rcmd/data.dart';
import 'package:PiliPlus/pages/about/view.dart' show showInportExportDialog;
import 'package:PiliPlus/pages/search/controller.dart';
import 'package:PiliPlus/pages/search/widgets/hot_keyword.dart';
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
@@ -331,13 +334,14 @@ class _SearchPageState extends State<SearchPage> {
);
},
),
_exportHsitory(theme),
const Spacer(),
SizedBox(
height: 34,
child: TextButton.icon(
style: ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.symmetric(
style: const ButtonStyle(
padding: WidgetStatePropertyAll(
EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
@@ -380,6 +384,30 @@ class _SearchPageState extends State<SearchPage> {
);
}
Widget _exportHsitory(ThemeData theme) => SizedBox(
width: 34,
height: 34,
child: IconButton(
iconSize: 22,
tooltip: '导入/导出历史记录',
icon: Icon(
Icons.import_export_outlined,
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.8),
),
style: IconButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: () => showInportExportDialog<List>(
context,
title: '历史记录',
toJson: () => jsonEncode(_searchController.historyList),
fromJson: (json) {
_searchController.historyList.value = json.cast<String>();
},
),
),
);
Icon historyIcon(ThemeData theme) => Icon(
Icons.history,
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.8),

View File

@@ -14,7 +14,7 @@ import 'package:path_provider/path_provider.dart';
class GStorage {
static late final Box<UserInfoData> userInfo;
static late final Box<dynamic> historyWord;
static late final Box<List<String>> historyWord;
static late final Box<dynamic> localCache;
static late final Box<dynamic> setting;
static late final Box<dynamic> video;
@@ -41,7 +41,7 @@ class GStorage {
// 设置
setting = await Hive.openBox('setting');
// 搜索历史
historyWord = await Hive.openBox(
historyWord = await Hive.openBox<List<String>>(
'historyWord',
compactionStrategy: (int entries, int deletedEntries) {
return deletedEntries > 10;
@@ -60,8 +60,10 @@ class GStorage {
});
}
static Future<void> importAllSettings(String data) async {
final Map<String, dynamic> map = jsonDecode(data);
static Future<void> importAllSettings(String data) =>
importAllJsonSettings(jsonDecode(data));
static Future<void> importAllJsonSettings(Map<String, dynamic> map) async {
await setting.clear();
await video.clear();
await setting.putAll(map[setting.name]);