mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-20 09:06:36 +08:00
feat: InportExportDialog (#1048)
This commit is contained in:
committed by
GitHub
parent
08c3789321
commit
ed57697fdc
@@ -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('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user