mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
data exporting & importing
This commit is contained in:
@@ -152,7 +152,10 @@
|
||||
"Name": "名称",
|
||||
"Date": "日期",
|
||||
"Date Desc": "日期降序",
|
||||
"Start": "开始"
|
||||
"Start": "开始",
|
||||
"Export App Data": "导出应用数据",
|
||||
"Import App Data": "导入应用数据",
|
||||
"Export": "导出"
|
||||
},
|
||||
"zh_TW": {
|
||||
"Home": "首頁",
|
||||
@@ -307,6 +310,9 @@
|
||||
"Name": "名稱",
|
||||
"Date": "日期",
|
||||
"Date Desc": "日期降序",
|
||||
"Start": "開始"
|
||||
"Start": "開始",
|
||||
"Export App Data": "匯出應用數據",
|
||||
"Import App Data": "匯入應用數據",
|
||||
"Export": "匯出"
|
||||
}
|
||||
}
|
@@ -156,7 +156,7 @@ class _ButtonState extends State<Button> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var padding = widget.padding ??
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 6);
|
||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 4);
|
||||
var width = widget.width;
|
||||
if (width != null) {
|
||||
width = width - padding.horizontal;
|
||||
@@ -172,7 +172,7 @@ class _ButtonState extends State<Button> {
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
),
|
||||
child: isLoading
|
||||
? CircularProgressIndicator(
|
||||
@@ -210,11 +210,11 @@ class _ButtonState extends State<Button> {
|
||||
decoration: BoxDecoration(
|
||||
color: buttonColor,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: (isHover && !isLoading && widget.type == ButtonType.filled)
|
||||
boxShadow: (isHover && !isLoading && (widget.type == ButtonType.filled || widget.type == ButtonType.normal))
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
blurRadius: 2,
|
||||
offset: const Offset(0, 1),
|
||||
)
|
||||
]
|
||||
@@ -252,6 +252,14 @@ class _ButtonState extends State<Button> {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
if (widget.type == ButtonType.normal) {
|
||||
var color = widget.color ?? context.colorScheme.surfaceContainer;
|
||||
if (isHover) {
|
||||
return color.withOpacity(0.9);
|
||||
} else {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
if (isHover) {
|
||||
return context.colorScheme.outline.withOpacity(0.2);
|
||||
}
|
||||
|
@@ -597,4 +597,8 @@ class LocalFavoritesManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void close() {
|
||||
_db.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -172,6 +172,8 @@ class HistoryManager with ChangeNotifier {
|
||||
max_page int
|
||||
);
|
||||
""");
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// add history. if exists, update time.
|
||||
@@ -275,4 +277,8 @@ class HistoryManager with ChangeNotifier {
|
||||
""");
|
||||
return res.first[0] as int;
|
||||
}
|
||||
|
||||
void close() {
|
||||
_db.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -86,6 +86,36 @@ class _AppSettingsState extends State<AppSettings> {
|
||||
},
|
||||
actionTitle: 'Set'.tl,
|
||||
).toSliver(),
|
||||
_CallbackSetting(
|
||||
title: "Export App Data".tl,
|
||||
callback: () async {
|
||||
var controller = showLoadingDialog(context);
|
||||
var file = await exportAppData();
|
||||
await saveFile(filename: "data.venera", file: file);
|
||||
controller.close();
|
||||
},
|
||||
actionTitle: 'Export'.tl,
|
||||
).toSliver(),
|
||||
_CallbackSetting(
|
||||
title: "Import App Data".tl,
|
||||
callback: () async {
|
||||
var controller = showLoadingDialog(context);
|
||||
var file = await selectFile(ext: ['venera']);
|
||||
if(file != null) {
|
||||
var cacheFile = File(FilePath.join(App.cachePath, "temp.venera"));
|
||||
await file.saveTo(cacheFile.path);
|
||||
try {
|
||||
await importAppData(cacheFile);
|
||||
}
|
||||
catch(e, s) {
|
||||
Log.error("Import data", e.toString(), s);
|
||||
context.showMessage(message: "Failed to import data".tl);
|
||||
}
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
actionTitle: 'Import'.tl,
|
||||
).toSliver(),
|
||||
_SettingPartTitle(
|
||||
title: "Log".tl,
|
||||
icon: Icons.error_outline,
|
||||
|
@@ -21,6 +21,9 @@ class _AppearanceSettingsState extends State<AppearanceSettings> {
|
||||
"light": "Light".tl,
|
||||
"dark": "Dark".tl,
|
||||
},
|
||||
onChanged: () async {
|
||||
App.forceRebuild();
|
||||
},
|
||||
).toSliver(),
|
||||
SelectSetting(
|
||||
title: "Theme Color".tl,
|
||||
|
@@ -434,7 +434,7 @@ class _CallbackSetting extends StatelessWidget {
|
||||
return ListTile(
|
||||
title: Text(title),
|
||||
subtitle: subtitle == null ? null : Text(subtitle!),
|
||||
trailing: FilledButton(
|
||||
trailing: Button.normal(
|
||||
onPressed: callback,
|
||||
child: Text(actionTitle),
|
||||
).fixHeight(28),
|
||||
|
@@ -14,6 +14,7 @@ import 'package:venera/foundation/consts.dart';
|
||||
import 'package:venera/foundation/local.dart';
|
||||
import 'package:venera/foundation/log.dart';
|
||||
import 'package:venera/network/app_dio.dart';
|
||||
import 'package:venera/utils/data.dart';
|
||||
import 'package:venera/utils/io.dart';
|
||||
import 'package:venera/utils/translations.dart';
|
||||
import 'package:yaml/yaml.dart';
|
||||
|
71
lib/utils/data.dart
Normal file
71
lib/utils/data.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:venera/foundation/app.dart';
|
||||
import 'package:venera/foundation/appdata.dart';
|
||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||
import 'package:venera/foundation/favorites.dart';
|
||||
import 'package:venera/foundation/history.dart';
|
||||
import 'package:zip_flutter/zip_flutter.dart';
|
||||
|
||||
import 'io.dart';
|
||||
|
||||
Future<File> exportAppData() async {
|
||||
var time = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
var cacheFilePath = FilePath.join(App.cachePath, '$time.venera');
|
||||
var cacheFile = File(cacheFilePath);
|
||||
var dataPath = App.dataPath;
|
||||
if(await cacheFile.exists()) {
|
||||
await cacheFile.delete();
|
||||
}
|
||||
await Isolate.run(() {
|
||||
var zipFile = ZipFile.open(cacheFilePath);
|
||||
var historyFile = FilePath.join(dataPath, "history.db");
|
||||
var localFavoriteFile = FilePath.join(dataPath, "local_favorite.db");
|
||||
var appdata = FilePath.join(dataPath, "appdata.json");
|
||||
zipFile.addFile("history.db", historyFile);
|
||||
zipFile.addFile("local_favorite.db", localFavoriteFile);
|
||||
zipFile.addFile("appdata.json", appdata);
|
||||
for(var file in Directory(FilePath.join(dataPath, "comic_source")).listSync()) {
|
||||
if(file is File) {
|
||||
zipFile.addFile("comic_source/${file.name}", file.path);
|
||||
}
|
||||
}
|
||||
zipFile.close();
|
||||
});
|
||||
return cacheFile;
|
||||
}
|
||||
|
||||
Future<void> importAppData(File file) async {
|
||||
var cacheDirPath = FilePath.join(App.cachePath, 'temp_data');
|
||||
var cacheDir = Directory(cacheDirPath);
|
||||
await Isolate.run(() {
|
||||
ZipFile.openAndExtract(file.path, cacheDirPath);
|
||||
});
|
||||
var historyFile = cacheDir.joinFile("history.db");
|
||||
var localFavoriteFile = cacheDir.joinFile("local_favorite.db");
|
||||
var appdataFile = cacheDir.joinFile("appdata.json");
|
||||
if(await historyFile.exists()) {
|
||||
HistoryManager().close();
|
||||
await historyFile.copy(FilePath.join(App.dataPath, "history.db"));
|
||||
HistoryManager().init();
|
||||
}
|
||||
if(await localFavoriteFile.exists()) {
|
||||
LocalFavoritesManager().close();
|
||||
await localFavoriteFile.copy(FilePath.join(App.dataPath, "local_favorite.db"));
|
||||
LocalFavoritesManager().init();
|
||||
}
|
||||
if(await appdataFile.exists()) {
|
||||
await appdataFile.copy(FilePath.join(App.dataPath, "appdata.json"));
|
||||
appdata.init();
|
||||
}
|
||||
var comicSourceDir = FilePath.join(cacheDirPath, "comic_source");
|
||||
if(Directory(comicSourceDir).existsSync()) {
|
||||
for(var file in Directory(comicSourceDir).listSync()) {
|
||||
if(file is File) {
|
||||
var targetFile = FilePath.join(App.dataPath, "comic_source", file.name);
|
||||
await file.copy(targetFile);
|
||||
}
|
||||
}
|
||||
await ComicSource.reload();
|
||||
}
|
||||
}
|
@@ -169,7 +169,8 @@ Future<file_selector.XFile?> selectFile({required List<String> ext}) async {
|
||||
acceptedTypeGroups: <file_selector.XTypeGroup>[typeGroup],
|
||||
);
|
||||
if (file == null) return null;
|
||||
if (!ext.contains(file?.path.split(".").last)) {
|
||||
if (!ext.contains(file.path.split(".").last)) {
|
||||
App.rootContext.showMessage(message: "Invalid file type");
|
||||
return null;
|
||||
}
|
||||
return file;
|
||||
|
@@ -53,6 +53,7 @@ Source: "{#RootPath}\build\windows\x64\runner\Release\share_plus_plugin.dll"; De
|
||||
Source: "{#RootPath}\build\windows\x64\runner\Release\url_launcher_windows_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#RootPath}\build\windows\x64\runner\Release\screen_retriever_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#RootPath}\build\windows\x64\runner\Release\window_manager_plugin.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#RootPath}\build\windows\x64\runner\Release\zip_flutter.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#RootPath}\build\windows\x64\runner\Release\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||
|
||||
|
Reference in New Issue
Block a user