data exporting & importing

This commit is contained in:
nyne
2024-11-02 20:12:48 +08:00
parent 19a93cbbce
commit c4d867db89
11 changed files with 139 additions and 8 deletions

View File

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

View File

@@ -597,4 +597,8 @@ class LocalFavoritesManager {
}
}
}
void close() {
_db.dispose();
}
}

View File

@@ -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();
}
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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),

View File

@@ -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
View 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();
}
}

View File

@@ -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;