mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-20 16:18:39 +00:00
Compare commits
6 Commits
aee4424dbf
...
ffbbd8e702
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffbbd8e702 | ||
|
|
a1815c4cc7 | ||
|
|
b9e543f26b | ||
|
|
0788a4de2d | ||
|
|
b0c6e2f5cd | ||
|
|
9489d8a7ca |
278
lib/common/widgets/dialog/export_import.dart
Normal file
278
lib/common/widgets/dialog/export_import.dart
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import 'dart:async' show FutureOr;
|
||||||
|
import 'dart:convert' show utf8, jsonDecode;
|
||||||
|
import 'dart:io' show File;
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/constants.dart' show StyleString;
|
||||||
|
import 'package:PiliPlus/utils/extension/context_ext.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' show Clipboard;
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get_core/src/get_main.dart';
|
||||||
|
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||||
|
import 'package:intl/intl.dart' show DateFormat;
|
||||||
|
import 'package:re_highlight/languages/json.dart';
|
||||||
|
import 'package:re_highlight/re_highlight.dart';
|
||||||
|
import 'package:re_highlight/styles/base16/github.dart';
|
||||||
|
import 'package:re_highlight/styles/github-dark.dart';
|
||||||
|
|
||||||
|
void exportToClipBoard({
|
||||||
|
required ValueGetter<String> onExport,
|
||||||
|
}) {
|
||||||
|
Utils.copyText(onExport());
|
||||||
|
}
|
||||||
|
|
||||||
|
void exportToLocalFile({
|
||||||
|
required ValueGetter<String> onExport,
|
||||||
|
required ValueGetter<String> localFileName,
|
||||||
|
}) {
|
||||||
|
final res = utf8.encode(onExport());
|
||||||
|
Utils.saveBytes2File(
|
||||||
|
name:
|
||||||
|
'piliplus_${localFileName()}_'
|
||||||
|
'${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json',
|
||||||
|
bytes: res,
|
||||||
|
allowedExtensions: const ['json'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> importFromClipBoard<T>(
|
||||||
|
BuildContext context, {
|
||||||
|
required String title,
|
||||||
|
required ValueGetter<String> onExport,
|
||||||
|
required FutureOr<void> Function(T json) onImport,
|
||||||
|
bool showConfirmDialog = true,
|
||||||
|
}) async {
|
||||||
|
final 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 = Utils.jsonEncoder.convert(json);
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast('解析json失败:$e');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool? executeImport;
|
||||||
|
if (showConfirmDialog) {
|
||||||
|
final highlight = Highlight()..registerLanguage('json', langJson);
|
||||||
|
final result = highlight.highlight(
|
||||||
|
code: formatText,
|
||||||
|
language: 'json',
|
||||||
|
);
|
||||||
|
late TextSpanRenderer renderer;
|
||||||
|
bool? isDarkMode;
|
||||||
|
executeImport = await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
final isDark = context.isDarkMode;
|
||||||
|
if (isDark != isDarkMode) {
|
||||||
|
isDarkMode = isDark;
|
||||||
|
renderer = TextSpanRenderer(
|
||||||
|
const TextStyle(),
|
||||||
|
isDark ? githubDarkTheme : githubTheme,
|
||||||
|
);
|
||||||
|
result.render(renderer);
|
||||||
|
}
|
||||||
|
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: () => Get.back(result: true),
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
executeImport = true;
|
||||||
|
}
|
||||||
|
if (executeImport ?? false) {
|
||||||
|
try {
|
||||||
|
await onImport(json);
|
||||||
|
SmartDialog.showToast('导入成功');
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast('导入失败:$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> importFromLocalFile<T>({
|
||||||
|
required FutureOr<void> Function(T json) onImport,
|
||||||
|
}) async {
|
||||||
|
final result = await FilePicker.pickFiles();
|
||||||
|
if (result != null) {
|
||||||
|
final path = result.files.first.path;
|
||||||
|
if (path != null) {
|
||||||
|
final data = await File(path).readAsString();
|
||||||
|
late final T json;
|
||||||
|
try {
|
||||||
|
json = jsonDecode(data);
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast('解析json失败:$e');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await onImport(json);
|
||||||
|
SmartDialog.showToast('导入成功');
|
||||||
|
} catch (e) {
|
||||||
|
SmartDialog.showToast('导入失败:$e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void importFromInput<T>(
|
||||||
|
BuildContext context, {
|
||||||
|
required String title,
|
||||||
|
required FutureOr<void> Function(T json) onImport,
|
||||||
|
}) {
|
||||||
|
final key = GlobalKey<FormFieldState<String>>();
|
||||||
|
late T json;
|
||||||
|
String? forceErrorText;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text('输入$title'),
|
||||||
|
constraints: StyleString.dialogFixedConstraints,
|
||||||
|
content: TextFormField(
|
||||||
|
key: key,
|
||||||
|
minLines: 4,
|
||||||
|
maxLines: 12,
|
||||||
|
autofocus: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
errorMaxLines: 3,
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (forceErrorText != null) return forceErrorText;
|
||||||
|
try {
|
||||||
|
json = jsonDecode(value!) as T;
|
||||||
|
return null;
|
||||||
|
} catch (e) {
|
||||||
|
if (e is FormatException) {}
|
||||||
|
return '解析json失败:$e';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
if (key.currentState?.validate() == true) {
|
||||||
|
try {
|
||||||
|
await onImport(json);
|
||||||
|
Get.back();
|
||||||
|
SmartDialog.showToast('导入成功');
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
forceErrorText = '导入失败:$e';
|
||||||
|
}
|
||||||
|
key.currentState?.validate();
|
||||||
|
forceErrorText = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确定'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showImportExportDialog<T>(
|
||||||
|
BuildContext context, {
|
||||||
|
required String title,
|
||||||
|
String? label,
|
||||||
|
required ValueGetter<String> onExport,
|
||||||
|
required FutureOr<void> Function(T json) onImport,
|
||||||
|
required ValueGetter<String> localFileName,
|
||||||
|
}) => showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
const style = TextStyle(fontSize: 15);
|
||||||
|
return SimpleDialog(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
title: Text('导入/导出$title'),
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('导出至剪贴板', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
exportToClipBoard(onExport: onExport);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('导出文件至本地', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
exportToLocalFile(onExport: onExport, localFileName: localFileName);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 1,
|
||||||
|
color: ColorScheme.of(context).outline.withValues(alpha: 0.1),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('输入', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
importFromInput<T>(context, title: title, onImport: onImport);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('从剪贴板导入', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
importFromClipBoard<T>(
|
||||||
|
context,
|
||||||
|
title: title,
|
||||||
|
onExport: onExport,
|
||||||
|
onImport: onImport,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('从本地文件导入', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
importFromLocalFile<T>(onImport: onImport);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -104,6 +104,8 @@ class DynamicItemModel {
|
|||||||
String? type;
|
String? type;
|
||||||
bool? visible;
|
bool? visible;
|
||||||
|
|
||||||
|
late bool linkFolded = false;
|
||||||
|
|
||||||
// opus
|
// opus
|
||||||
Fallback? fallback;
|
Fallback? fallback;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:PiliPlus/build_config.dart';
|
import 'package:PiliPlus/build_config.dart';
|
||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
|
||||||
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
|
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
|
||||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||||
import 'package:PiliPlus/services/logger.dart';
|
import 'package:PiliPlus/services/logger.dart';
|
||||||
@@ -21,15 +21,9 @@ import 'package:PiliPlus/utils/storage.dart';
|
|||||||
import 'package:PiliPlus/utils/update.dart';
|
import 'package:PiliPlus/utils/update.dart';
|
||||||
import 'package:PiliPlus/utils/utils.dart';
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart' hide ListTile;
|
import 'package:flutter/material.dart' hide ListTile;
|
||||||
import 'package:flutter/services.dart' show Clipboard, ClipboardData;
|
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||||
import 'package:re_highlight/languages/json.dart';
|
|
||||||
import 'package:re_highlight/re_highlight.dart';
|
|
||||||
import 'package:re_highlight/styles/github-dark.dart';
|
|
||||||
import 'package:re_highlight/styles/github.dart';
|
|
||||||
|
|
||||||
class AboutPage extends StatefulWidget {
|
class AboutPage extends StatefulWidget {
|
||||||
const AboutPage({super.key, this.showAppBar = true});
|
const AboutPage({super.key, this.showAppBar = true});
|
||||||
@@ -249,8 +243,10 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
|||||||
onTap: () => showImportExportDialog<Map>(
|
onTap: () => showImportExportDialog<Map>(
|
||||||
context,
|
context,
|
||||||
title: '登录信息',
|
title: '登录信息',
|
||||||
toJson: () => Utils.jsonEncoder.convert(Accounts.account.toMap()),
|
localFileName: () => 'account',
|
||||||
fromJson: (json) async {
|
onExport: () =>
|
||||||
|
Utils.jsonEncoder.convert(Accounts.account.toMap()),
|
||||||
|
onImport: (json) async {
|
||||||
final res = json.map(
|
final res = json.map(
|
||||||
(key, value) => MapEntry(key, LoginAccount.fromJson(value)),
|
(key, value) => MapEntry(key, LoginAccount.fromJson(value)),
|
||||||
);
|
);
|
||||||
@@ -260,7 +256,6 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
|||||||
if (Accounts.main.isLogin) {
|
if (Accounts.main.isLogin) {
|
||||||
await LoginUtils.onLoginMain();
|
await LoginUtils.onLoginMain();
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -268,12 +263,13 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
|||||||
title: const Text('导入/导出设置'),
|
title: const Text('导入/导出设置'),
|
||||||
dense: false,
|
dense: false,
|
||||||
leading: const Icon(Icons.import_export_outlined),
|
leading: const Icon(Icons.import_export_outlined),
|
||||||
onTap: () => showImportExportDialog(
|
onTap: () => showImportExportDialog<Map<String, dynamic>>(
|
||||||
context,
|
context,
|
||||||
title: '设置',
|
title: '设置',
|
||||||
|
localFileName: () => 'setting_${context.platformName}',
|
||||||
label: GStorage.setting.name,
|
label: GStorage.setting.name,
|
||||||
toJson: GStorage.exportAllSettings,
|
onExport: GStorage.exportAllSettings,
|
||||||
fromJson: GStorage.importAllJsonSettings,
|
onImport: GStorage.importAllJsonSettings,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -302,15 +298,7 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
|||||||
dense: true,
|
dense: true,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Get.back();
|
Get.back();
|
||||||
await Future.wait([
|
await GStorage.clear();
|
||||||
GStorage.userInfo.clear(),
|
|
||||||
GStorage.setting.clear(),
|
|
||||||
GStorage.localCache.clear(),
|
|
||||||
GStorage.video.clear(),
|
|
||||||
GStorage.historyWord.clear(),
|
|
||||||
Accounts.clear(),
|
|
||||||
GStorage.watchProgress.clear(),
|
|
||||||
]);
|
|
||||||
SmartDialog.showToast('重置成功');
|
SmartDialog.showToast('重置成功');
|
||||||
},
|
},
|
||||||
title: const Text('重置所有数据(含登录信息)', style: style),
|
title: const Text('重置所有数据(含登录信息)', style: style),
|
||||||
@@ -325,190 +313,3 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showImportExportDialog<T>(
|
|
||||||
BuildContext context, {
|
|
||||||
required String title,
|
|
||||||
String? label,
|
|
||||||
required ValueGetter<String> toJson,
|
|
||||||
required FutureOr<bool> 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: () {
|
|
||||||
Get.back();
|
|
||||||
final res = utf8.encode(toJson());
|
|
||||||
final name =
|
|
||||||
'piliplus_${label}_${context.isTablet ? 'pad' : 'phone'}_'
|
|
||||||
'${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json';
|
|
||||||
Utils.saveBytes2File(
|
|
||||||
name: name,
|
|
||||||
bytes: res,
|
|
||||||
allowedExtensions: const ['json'],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
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 = Utils.jsonEncoder.convert(json);
|
|
||||||
} catch (e) {
|
|
||||||
SmartDialog.showToast('解析json失败:$e');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final highlight = Highlight()..registerLanguage('json', langJson);
|
|
||||||
final result = highlight.highlight(
|
|
||||||
code: formatText,
|
|
||||||
language: 'json',
|
|
||||||
);
|
|
||||||
late TextSpanRenderer renderer;
|
|
||||||
bool? isDarkMode;
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
final isDark = context.isDarkMode;
|
|
||||||
if (isDark != isDarkMode) {
|
|
||||||
isDarkMode = isDark;
|
|
||||||
renderer = TextSpanRenderer(
|
|
||||||
const TextStyle(),
|
|
||||||
isDark ? githubDarkTheme : githubTheme,
|
|
||||||
);
|
|
||||||
result.render(renderer);
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
if (await fromJson(json)) {
|
|
||||||
SmartDialog.showToast('导入成功');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
SmartDialog.showToast('导入失败:$e');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('确定'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
dense: true,
|
|
||||||
title: Text('输入$title', style: style),
|
|
||||||
onTap: () {
|
|
||||||
Get.back();
|
|
||||||
final key = GlobalKey<FormFieldState<String>>();
|
|
||||||
late T json;
|
|
||||||
String? forceErrorText;
|
|
||||||
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: Text('输入$title'),
|
|
||||||
constraints: StyleString.dialogFixedConstraints,
|
|
||||||
content: TextFormField(
|
|
||||||
key: key,
|
|
||||||
minLines: 4,
|
|
||||||
maxLines: 12,
|
|
||||||
autofocus: true,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
errorMaxLines: 3,
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (forceErrorText != null) return forceErrorText;
|
|
||||||
try {
|
|
||||||
json = jsonDecode(value!) as T;
|
|
||||||
return null;
|
|
||||||
} catch (e) {
|
|
||||||
if (e is FormatException) {}
|
|
||||||
return '解析json失败:$e';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: Get.back,
|
|
||||||
child: Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
if (key.currentState?.validate() == true) {
|
|
||||||
try {
|
|
||||||
if (await fromJson(json)) {
|
|
||||||
Get.back();
|
|
||||||
SmartDialog.showToast('导入成功');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
forceErrorText = '导入失败:$e';
|
|
||||||
}
|
|
||||||
key.currentState?.validate();
|
|
||||||
forceErrorText = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: const Text('确定'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ import 'package:PiliPlus/models/dynamics/result.dart';
|
|||||||
import 'package:PiliPlus/pages/dynamics/widgets/vote.dart';
|
import 'package:PiliPlus/pages/dynamics/widgets/vote.dart';
|
||||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||||
import 'package:PiliPlus/utils/num_utils.dart';
|
import 'package:PiliPlus/utils/num_utils.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
Widget addWidget(
|
Widget? addWidget(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
required int floor,
|
required int floor,
|
||||||
required ThemeData theme,
|
required ThemeData theme,
|
||||||
@@ -27,7 +26,7 @@ Widget addWidget(
|
|||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
// 转发的投稿
|
// 转发的投稿
|
||||||
case 'ADDITIONAL_TYPE_UGC':
|
case 'ADDITIONAL_TYPE_UGC' when (additional.ugc != null):
|
||||||
final ugc = additional.ugc!;
|
final ugc = additional.ugc!;
|
||||||
child = InkWell(
|
child = InkWell(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
@@ -72,7 +71,7 @@ Widget addWidget(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'ADDITIONAL_TYPE_RESERVE':
|
case 'ADDITIONAL_TYPE_RESERVE' when (additional.reserve != null):
|
||||||
final reserve = additional.reserve!;
|
final reserve = additional.reserve!;
|
||||||
if (reserve.state != -1 && reserve.title != null) {
|
if (reserve.state != -1 && reserve.title != null) {
|
||||||
child = InkWell(
|
child = InkWell(
|
||||||
@@ -213,7 +212,8 @@ Widget addWidget(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'ADDITIONAL_TYPE_UPOWER_LOTTERY':
|
case 'ADDITIONAL_TYPE_UPOWER_LOTTERY'
|
||||||
|
when (additional.upowerLottery != null):
|
||||||
final content = additional.upowerLottery!;
|
final content = additional.upowerLottery!;
|
||||||
child = InkWell(
|
child = InkWell(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
@@ -308,7 +308,7 @@ Widget addWidget(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 商品
|
// 商品
|
||||||
case 'ADDITIONAL_TYPE_GOODS':
|
case 'ADDITIONAL_TYPE_GOODS' when (additional.goods != null):
|
||||||
final content = additional.goods!;
|
final content = additional.goods!;
|
||||||
if (content.items?.isNotEmpty == true) {
|
if (content.items?.isNotEmpty == true) {
|
||||||
child = Column(
|
child = Column(
|
||||||
@@ -395,7 +395,7 @@ Widget addWidget(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'ADDITIONAL_TYPE_VOTE':
|
case 'ADDITIONAL_TYPE_VOTE' when (additional.vote != null):
|
||||||
final vote = additional.vote!;
|
final vote = additional.vote!;
|
||||||
child = InkWell(
|
child = InkWell(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
@@ -483,7 +483,7 @@ Widget addWidget(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'ADDITIONAL_TYPE_COMMON':
|
case 'ADDITIONAL_TYPE_COMMON' when (additional.common != null):
|
||||||
final content = additional.common!;
|
final content = additional.common!;
|
||||||
child = InkWell(
|
child = InkWell(
|
||||||
borderRadius: borderRadius,
|
borderRadius: borderRadius,
|
||||||
@@ -557,7 +557,7 @@ Widget addWidget(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case 'ADDITIONAL_TYPE_MATCH':
|
case 'ADDITIONAL_TYPE_MATCH' when (additional.match != null):
|
||||||
final content = additional.match!;
|
final content = additional.match!;
|
||||||
Widget teamItem(TTeam team, Alignment alignment, EdgeInsets padding) {
|
Widget teamItem(TTeam team, Alignment alignment, EdgeInsets padding) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
@@ -687,13 +687,7 @@ Widget addWidget(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (kDebugMode) {
|
return null;
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
child: Text('additional panel\ntype: $type'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ List<Widget> dynContent(
|
|||||||
floor: floor,
|
floor: floor,
|
||||||
),
|
),
|
||||||
if (moduleDynamic?.additional case final additional?)
|
if (moduleDynamic?.additional case final additional?)
|
||||||
addWidget(
|
?addWidget(
|
||||||
theme: theme,
|
theme: theme,
|
||||||
context,
|
context,
|
||||||
idStr: item.idStr,
|
idStr: item.idStr,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
const _linkFoldedText = '网页链接';
|
||||||
|
|
||||||
// 富文本
|
// 富文本
|
||||||
TextSpan? richNode(
|
TextSpan? richNode(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
@@ -63,6 +65,9 @@ TextSpan? richNode(
|
|||||||
for (final i in richTextNodes) {
|
for (final i in richTextNodes) {
|
||||||
switch (i.type) {
|
switch (i.type) {
|
||||||
case 'RICH_TEXT_NODE_TYPE_TEXT':
|
case 'RICH_TEXT_NODE_TYPE_TEXT':
|
||||||
|
if (i.origText == _linkFoldedText) {
|
||||||
|
item.linkFolded = true;
|
||||||
|
}
|
||||||
spanChildren.add(
|
spanChildren.add(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: i.origText,
|
text: i.origText,
|
||||||
@@ -102,6 +107,10 @@ TextSpan? richNode(
|
|||||||
break;
|
break;
|
||||||
// 网页链接
|
// 网页链接
|
||||||
case 'RICH_TEXT_NODE_TYPE_WEB':
|
case 'RICH_TEXT_NODE_TYPE_WEB':
|
||||||
|
final hasLink = i.jumpUrl?.isNotEmpty ?? false;
|
||||||
|
if (!hasLink) {
|
||||||
|
item.linkFolded = true;
|
||||||
|
}
|
||||||
spanChildren
|
spanChildren
|
||||||
..add(
|
..add(
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
@@ -117,10 +126,10 @@ TextSpan? richNode(
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: i.text,
|
text: i.text,
|
||||||
style: style,
|
style: style,
|
||||||
recognizer: i.origText == null
|
recognizer: hasLink
|
||||||
? null
|
? (NoDeadlineTapGestureRecognizer()
|
||||||
: (NoDeadlineTapGestureRecognizer()
|
..onTap = () => PageUtils.handleWebview(i.jumpUrl!))
|
||||||
..onTap = () => PageUtils.handleWebview(i.origText!)),
|
: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
|
||||||
|
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
|
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
|
||||||
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart';
|
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
|
||||||
|
show ReplyInfo;
|
||||||
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
|
||||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||||
import 'package:PiliPlus/utils/id_utils.dart';
|
import 'package:PiliPlus/utils/id_utils.dart';
|
||||||
@@ -9,9 +11,13 @@ import 'package:PiliPlus/utils/page_utils.dart';
|
|||||||
import 'package:PiliPlus/utils/reply_utils.dart';
|
import 'package:PiliPlus/utils/reply_utils.dart';
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:PiliPlus/utils/waterfall.dart';
|
import 'package:PiliPlus/utils/waterfall.dart';
|
||||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get_core/src/get_main.dart';
|
||||||
|
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||||
|
import 'package:get/get_rx/get_rx.dart';
|
||||||
import 'package:waterfall_flow/waterfall_flow.dart';
|
import 'package:waterfall_flow/waterfall_flow.dart';
|
||||||
|
|
||||||
class MyReply extends StatefulWidget {
|
class MyReply extends StatefulWidget {
|
||||||
@@ -22,13 +28,19 @@ class MyReply extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyReplyState extends State<MyReply> with DynMixin {
|
class _MyReplyState extends State<MyReply> with DynMixin {
|
||||||
late final List<ReplyInfo> _replies;
|
final List<ReplyInfo> _replies = <ReplyInfo>[];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_replies = GStorage.reply!.values.map(ReplyInfo.fromBuffer).toList()
|
_initReply();
|
||||||
..sort((a, b) => b.ctime.compareTo(a.ctime)); // rpid not aligned
|
}
|
||||||
|
|
||||||
|
void _initReply() {
|
||||||
|
_replies.assignAll(
|
||||||
|
GStorage.reply!.values.map(ReplyInfo.fromBuffer).toList()
|
||||||
|
..sort((a, b) => b.ctime.compareTo(a.ctime)), // rpid not aligned
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -36,24 +48,33 @@ class _MyReplyState extends State<MyReply> with DynMixin {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('我的评论'),
|
title: const Text('我的评论'),
|
||||||
actions: kDebugMode
|
actions: [
|
||||||
? [
|
if (kDebugMode)
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: 'Clear',
|
tooltip: 'Clear',
|
||||||
onPressed: () => showConfirmDialog(
|
onPressed: () => showConfirmDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: 'Clear Local Storage?',
|
title: 'Clear Local Storage?',
|
||||||
onConfirm: () {
|
onConfirm: () {
|
||||||
GStorage.reply!.clear();
|
GStorage.reply!.clear();
|
||||||
_replies.clear();
|
_replies.clear();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
icon: const Icon(Icons.clear_all),
|
icon: const Icon(Icons.clear_all),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
IconButton(
|
||||||
]
|
tooltip: '导出',
|
||||||
: null,
|
onPressed: _showExportDialog,
|
||||||
|
icon: const Icon(Icons.file_upload_outlined),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
tooltip: '导入',
|
||||||
|
onPressed: _showImportDialog,
|
||||||
|
icon: const Icon(Icons.file_download_outlined),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
@@ -121,4 +142,89 @@ class _MyReplyState extends State<MyReply> with DynMixin {
|
|||||||
isManual: true,
|
isManual: true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _onExport() {
|
||||||
|
return Utils.jsonEncoder.convert(
|
||||||
|
_replies.map((e) => e.toProto3Json()).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showExportDialog() {
|
||||||
|
const style = TextStyle(fontSize: 14);
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SimpleDialog(
|
||||||
|
clipBehavior: .hardEdge,
|
||||||
|
contentPadding: const .symmetric(vertical: 12),
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('导出至剪贴板', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
exportToClipBoard(onExport: _onExport);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('导出文件至本地', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
exportToLocalFile(
|
||||||
|
onExport: _onExport,
|
||||||
|
localFileName: () => 'reply',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onImport(List<dynamic> list) async {
|
||||||
|
await GStorage.reply!.putAll({
|
||||||
|
for (var e in list)
|
||||||
|
e['id'].toString(): (ReplyInfo.create()..mergeFromProto3Json(e))
|
||||||
|
.writeToBuffer(),
|
||||||
|
});
|
||||||
|
if (mounted) {
|
||||||
|
_initReply();
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showImportDialog() {
|
||||||
|
const style = TextStyle(fontSize: 14);
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SimpleDialog(
|
||||||
|
clipBehavior: .hardEdge,
|
||||||
|
contentPadding: const .symmetric(vertical: 12),
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('从剪贴板导入', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
importFromClipBoard<List<dynamic>>(
|
||||||
|
context,
|
||||||
|
title: '评论',
|
||||||
|
onExport: _onExport,
|
||||||
|
onImport: _onImport,
|
||||||
|
showConfirmDialog: false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: true,
|
||||||
|
title: const Text('从本地文件导入', style: style),
|
||||||
|
onTap: () {
|
||||||
|
Get.back();
|
||||||
|
importFromLocalFile<List<dynamic>>(onImport: _onImport);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
|
||||||
import 'package:PiliPlus/common/widgets/disabled_icon.dart';
|
import 'package:PiliPlus/common/widgets/disabled_icon.dart';
|
||||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||||
import 'package:PiliPlus/common/widgets/sliver_wrap.dart';
|
import 'package:PiliPlus/common/widgets/sliver_wrap.dart';
|
||||||
import 'package:PiliPlus/http/loading_state.dart';
|
import 'package:PiliPlus/http/loading_state.dart';
|
||||||
import 'package:PiliPlus/models_new/search/search_rcmd/data.dart';
|
import 'package:PiliPlus/models_new/search/search_rcmd/data.dart';
|
||||||
import 'package:PiliPlus/pages/about/view.dart' show showImportExportDialog;
|
|
||||||
import 'package:PiliPlus/pages/search/controller.dart';
|
import 'package:PiliPlus/pages/search/controller.dart';
|
||||||
import 'package:PiliPlus/pages/search/widgets/hot_keyword.dart';
|
import 'package:PiliPlus/pages/search/widgets/hot_keyword.dart';
|
||||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||||
@@ -424,12 +424,12 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
onPressed: () => showImportExportDialog<List>(
|
onPressed: () => showImportExportDialog<List>(
|
||||||
context,
|
context,
|
||||||
title: '历史记录',
|
title: '历史记录',
|
||||||
toJson: () => jsonEncode(_searchController.historyList),
|
localFileName: () => 'search',
|
||||||
fromJson: (json) {
|
onExport: () => jsonEncode(_searchController.historyList),
|
||||||
|
onImport: (json) {
|
||||||
final list = List<String>.from(json);
|
final list = List<String>.from(json);
|
||||||
_searchController.historyList.value = list;
|
_searchController.historyList.value = list;
|
||||||
GStorage.historyWord.put('cacheList', list);
|
GStorage.historyWord.put('cacheList', list);
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import 'dart:convert';
|
|||||||
import 'package:PiliPlus/common/constants.dart';
|
import 'package:PiliPlus/common/constants.dart';
|
||||||
import 'package:PiliPlus/common/widgets/pair.dart';
|
import 'package:PiliPlus/common/widgets/pair.dart';
|
||||||
import 'package:PiliPlus/utils/extension/context_ext.dart';
|
import 'package:PiliPlus/utils/extension/context_ext.dart';
|
||||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
|
||||||
import 'package:PiliPlus/utils/storage.dart';
|
import 'package:PiliPlus/utils/storage.dart';
|
||||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get_core/src/get_main.dart';
|
||||||
|
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||||
import 'package:webdav_client/webdav_client.dart' as webdav;
|
import 'package:webdav_client/webdav_client.dart' as webdav;
|
||||||
|
|
||||||
class WebDav {
|
class WebDav {
|
||||||
@@ -53,12 +53,7 @@ class WebDav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _getFileName() {
|
String _getFileName() {
|
||||||
final type = PlatformUtils.isDesktop
|
return 'piliplus_settings_${Get.context!.platformName}.json';
|
||||||
? 'desktop'
|
|
||||||
: Get.context!.isTablet
|
|
||||||
? 'pad'
|
|
||||||
: 'phone';
|
|
||||||
return 'piliplus_settings_$type.json';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> backup() async {
|
Future<void> backup() async {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// from Getx
|
/// from Getx
|
||||||
@@ -71,4 +72,10 @@ extension ContextExtensions on BuildContext {
|
|||||||
|
|
||||||
/// True if the current device is Tablet
|
/// True if the current device is Tablet
|
||||||
bool get isTablet => isSmallTablet || isLargeTablet;
|
bool get isTablet => isSmallTablet || isLargeTablet;
|
||||||
|
|
||||||
|
String get platformName => PlatformUtils.isDesktop
|
||||||
|
? 'desktop'
|
||||||
|
: isTablet
|
||||||
|
? 'pad'
|
||||||
|
: 'phone';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,6 +221,10 @@ abstract final class PageUtils {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
if (item.linkFolded) {
|
||||||
|
pushDynFromId(id: item.idStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
toDupNamed(
|
toDupNamed(
|
||||||
'/dynamicDetail',
|
'/dynamicDetail',
|
||||||
arguments: {
|
arguments: {
|
||||||
|
|||||||
@@ -87,12 +87,13 @@ abstract final class GStorage {
|
|||||||
static Future<void> importAllSettings(String data) =>
|
static Future<void> importAllSettings(String data) =>
|
||||||
importAllJsonSettings(jsonDecode(data));
|
importAllJsonSettings(jsonDecode(data));
|
||||||
|
|
||||||
static Future<bool> importAllJsonSettings(Map<String, dynamic> map) async {
|
static Future<List<void>> importAllJsonSettings(
|
||||||
await Future.wait([
|
Map<String, dynamic> map,
|
||||||
|
) {
|
||||||
|
return Future.wait([
|
||||||
setting.clear().then((_) => setting.putAll(map[setting.name])),
|
setting.clear().then((_) => setting.putAll(map[setting.name])),
|
||||||
video.clear().then((_) => video.putAll(map[video.name])),
|
video.clear().then((_) => video.putAll(map[video.name])),
|
||||||
]);
|
]);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void regAdapter() {
|
static void regAdapter() {
|
||||||
@@ -107,8 +108,8 @@ abstract final class GStorage {
|
|||||||
..registerAdapter(RuleFilterAdapter());
|
..registerAdapter(RuleFilterAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> compact() async {
|
static Future<List<void>> compact() {
|
||||||
await Future.wait([
|
return Future.wait([
|
||||||
userInfo.compact(),
|
userInfo.compact(),
|
||||||
historyWord.compact(),
|
historyWord.compact(),
|
||||||
localCache.compact(),
|
localCache.compact(),
|
||||||
@@ -120,8 +121,8 @@ abstract final class GStorage {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> close() async {
|
static Future<List<void>> close() {
|
||||||
await Future.wait([
|
return Future.wait([
|
||||||
userInfo.close(),
|
userInfo.close(),
|
||||||
historyWord.close(),
|
historyWord.close(),
|
||||||
localCache.close(),
|
localCache.close(),
|
||||||
@@ -133,6 +134,19 @@ abstract final class GStorage {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<List<void>> clear() {
|
||||||
|
return Future.wait([
|
||||||
|
userInfo.clear(),
|
||||||
|
historyWord.clear(),
|
||||||
|
localCache.clear(),
|
||||||
|
setting.clear(),
|
||||||
|
video.clear(),
|
||||||
|
Accounts.clear(),
|
||||||
|
watchProgress.clear(),
|
||||||
|
?reply?.clear(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
static int _intStrKeyComparator(dynamic k1, dynamic k2) {
|
static int _intStrKeyComparator(dynamic k1, dynamic k2) {
|
||||||
if (k1 is int) {
|
if (k1 is int) {
|
||||||
if (k2 is int) {
|
if (k2 is int) {
|
||||||
|
|||||||
12
pubspec.lock
12
pubspec.lock
@@ -578,10 +578,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fl_chart
|
name: fl_chart
|
||||||
sha256: "7ca9a40f4eb85949190e54087be8b4d6ac09dc4c54238d782a34cf1f7c011de9"
|
sha256: b938f77d042cbcd822936a7a359a7235bad8bd72070de1f827efc2cc297ac888
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.2.0"
|
||||||
flex_seed_scheme:
|
flex_seed_scheme:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -742,10 +742,10 @@ packages:
|
|||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: c08f651c60f1451feba2ec4895caf12771661809
|
resolved-ref: b87bda5672e1c8494853bb44bbf08515ef748bca
|
||||||
url: "https://github.com/bggRGjQaUbCoE/flutter_smart_dialog.git"
|
url: "https://github.com/bggRGjQaUbCoE/flutter_smart_dialog.git"
|
||||||
source: git
|
source: git
|
||||||
version: "5.0.1"
|
version: "5.1.0"
|
||||||
flutter_sortable_wrap:
|
flutter_sortable_wrap:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1943,10 +1943,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: wakelock_plus
|
name: wakelock_plus
|
||||||
sha256: e4e125b7c1a2f0e491e5452afdc0e25ab77b2d2775a7caa231fcc1c1f2162c47
|
sha256: "8b12256f616346910c519a35606fb69b1fe0737c06b6a447c6df43888b097f39"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.1"
|
||||||
wakelock_plus_platform_interface:
|
wakelock_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
# update when release
|
# update when release
|
||||||
version: 2.0.0+1
|
version: 2.0.1+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.10.0"
|
sdk: ">=3.10.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user