mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
Convert network folder to local
This commit is contained in:
@@ -212,7 +212,10 @@
|
||||
"Select an image on screen": "选择屏幕上的图片",
|
||||
"Added @count comics to download queue.": "已添加 @count 本漫画到下载队列",
|
||||
"Ignore Certificate Errors": "忽略证书错误",
|
||||
"Authorization Required": "需要身份验证"
|
||||
"Authorization Required": "需要身份验证",
|
||||
"Sync": "同步",
|
||||
"The folder is Linked to @source": "文件夹已关联到 @source",
|
||||
"Source Folder": "源收藏夹"
|
||||
},
|
||||
"zh_TW": {
|
||||
"Home": "首頁",
|
||||
@@ -427,6 +430,9 @@
|
||||
"Select an image on screen": "選擇屏幕上的圖片",
|
||||
"Added @count comics to download queue.": "已添加 @count 本漫畫到下載隊列",
|
||||
"Ignore Certificate Errors": "忽略證書錯誤",
|
||||
"Authorization Required": "需要身份驗證"
|
||||
"Authorization Required": "需要身份驗證",
|
||||
"Sync": "同步",
|
||||
"The folder is Linked to @source": "文件夾已關聯到 @source",
|
||||
"Source Folder": "源收藏夾"
|
||||
}
|
||||
}
|
@@ -51,6 +51,10 @@ class Flyout extends StatefulWidget {
|
||||
|
||||
@override
|
||||
State<Flyout> createState() => FlyoutState();
|
||||
|
||||
static FlyoutState of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<FlyoutState>()!;
|
||||
}
|
||||
}
|
||||
|
||||
class FlyoutState extends State<Flyout> {
|
||||
|
@@ -346,6 +346,32 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
return name;
|
||||
}
|
||||
|
||||
void linkFolderToNetwork(String folder, String source, String networkFolder) {
|
||||
_db.execute("""
|
||||
insert or replace into folder_sync (folder_name, source_key, source_folder)
|
||||
values (?, ?, ?);
|
||||
""", [folder, source, networkFolder]);
|
||||
}
|
||||
|
||||
bool isLinkedToNetworkFolder(String folder, String source, String networkFolder) {
|
||||
var res = _db.select("""
|
||||
select * from folder_sync
|
||||
where folder_name == ? and source_key == ? and source_folder == ?;
|
||||
""", [folder, source, networkFolder]);
|
||||
return res.isNotEmpty;
|
||||
}
|
||||
|
||||
(String?, String?) findLinked(String folder) {
|
||||
var res = _db.select("""
|
||||
select * from folder_sync
|
||||
where folder_name == ?;
|
||||
""", [folder]);
|
||||
if (res.isEmpty) {
|
||||
return (null, null);
|
||||
}
|
||||
return (res.first["source_key"], res.first["source_folder"]);
|
||||
}
|
||||
|
||||
bool comicExists(String folder, String id, ComicType type) {
|
||||
var res = _db.select("""
|
||||
select * from "$folder"
|
||||
@@ -365,20 +391,19 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
return FavoriteItem.fromRow(res.first);
|
||||
}
|
||||
|
||||
/// add comic to a folder
|
||||
///
|
||||
/// This method will download cover to local, to avoid problems like changing url
|
||||
void addComic(String folder, FavoriteItem comic, [int? order]) async {
|
||||
/// add comic to a folder.
|
||||
/// return true if success, false if already exists
|
||||
bool addComic(String folder, FavoriteItem comic, [int? order]) {
|
||||
_modifiedAfterLastCache = true;
|
||||
if (!existsFolder(folder)) {
|
||||
throw Exception("Folder does not exists");
|
||||
}
|
||||
var res = _db.select("""
|
||||
select * from "$folder"
|
||||
where id == '${comic.id}';
|
||||
""");
|
||||
where id == ? and type == ?;
|
||||
""", [comic.id, comic.type.value]);
|
||||
if (res.isNotEmpty) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
final params = [
|
||||
comic.id,
|
||||
@@ -406,6 +431,7 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
""", [...params, minValue(folder) - 1]);
|
||||
}
|
||||
notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// delete a folder
|
||||
@@ -414,6 +440,10 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
_db.execute("""
|
||||
drop table "$name";
|
||||
""");
|
||||
_db.execute("""
|
||||
delete from folder_order
|
||||
where folder_name == ?;
|
||||
""", [name]);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -461,6 +491,16 @@ class LocalFavoritesManager with ChangeNotifier {
|
||||
ALTER TABLE "$before"
|
||||
RENAME TO "$after";
|
||||
""");
|
||||
_db.execute("""
|
||||
update folder_order
|
||||
set folder_name = ?
|
||||
where folder_name == ?;
|
||||
""", [after, before]);
|
||||
_db.execute("""
|
||||
update folder_sync
|
||||
set folder_name = ?
|
||||
where folder_name == ?;
|
||||
""", [after, before]);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
@@ -288,3 +288,178 @@ Future<void> sortFolders() async {
|
||||
|
||||
LocalFavoritesManager().updateOrder(folders);
|
||||
}
|
||||
|
||||
Future<void> importNetworkFolder(
|
||||
String source,
|
||||
String? folder,
|
||||
String? folderID,
|
||||
) async {
|
||||
var comicSource = ComicSource.find(source);
|
||||
if (comicSource == null) {
|
||||
return;
|
||||
}
|
||||
if(folder != null && folder.isEmpty) {
|
||||
folder = null;
|
||||
}
|
||||
var resultName = folder ?? comicSource.name;
|
||||
var exists = LocalFavoritesManager().existsFolder(resultName);
|
||||
if (exists) {
|
||||
if (!LocalFavoritesManager()
|
||||
.isLinkedToNetworkFolder(resultName, source, folderID ?? "")) {
|
||||
App.rootContext.showMessage(message: "Folder already exists".tl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(!exists) {
|
||||
LocalFavoritesManager().createFolder(resultName);
|
||||
LocalFavoritesManager().linkFolderToNetwork(
|
||||
resultName,
|
||||
source,
|
||||
folderID ?? "",
|
||||
);
|
||||
}
|
||||
|
||||
var current = 0;
|
||||
var isFinished = false;
|
||||
String? next;
|
||||
|
||||
Future<void> fetchNext() async {
|
||||
var retry = 3;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
if (comicSource.favoriteData?.loadComic != null) {
|
||||
next ??= '1';
|
||||
var page = int.parse(next!);
|
||||
var res = await comicSource.favoriteData!.loadComic!(page, folderID);
|
||||
var count = 0;
|
||||
for (var c in res.data) {
|
||||
var result = LocalFavoritesManager().addComic(
|
||||
resultName,
|
||||
FavoriteItem(
|
||||
id: c.id,
|
||||
name: c.title,
|
||||
coverPath: c.cover,
|
||||
type: ComicType(source.hashCode),
|
||||
author: c.subtitle ?? '',
|
||||
tags: c.tags ?? [],
|
||||
),
|
||||
);
|
||||
if (result) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
current += count;
|
||||
if (res.data.isEmpty || res.subData == page) {
|
||||
isFinished = true;
|
||||
next = null;
|
||||
} else {
|
||||
next = (page + 1).toString();
|
||||
}
|
||||
} else if (comicSource.favoriteData?.loadNext != null) {
|
||||
var res = await comicSource.favoriteData!.loadNext!(next, folderID);
|
||||
var count = 0;
|
||||
for (var c in res.data) {
|
||||
var result = LocalFavoritesManager().addComic(
|
||||
resultName,
|
||||
FavoriteItem(
|
||||
id: c.id,
|
||||
name: c.title,
|
||||
coverPath: c.cover,
|
||||
type: ComicType(source.hashCode),
|
||||
author: c.subtitle ?? '',
|
||||
tags: c.tags ?? [],
|
||||
),
|
||||
);
|
||||
if (result) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
current += count;
|
||||
if (res.data.isEmpty || res.subData == null) {
|
||||
isFinished = true;
|
||||
next = null;
|
||||
} else {
|
||||
next = res.subData;
|
||||
}
|
||||
} else {
|
||||
throw "Unsupported source";
|
||||
}
|
||||
return;
|
||||
} catch (e) {
|
||||
retry--;
|
||||
if (retry == 0) {
|
||||
rethrow;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isCanceled = false;
|
||||
String? errorMsg;
|
||||
bool isErrored() => errorMsg != null;
|
||||
|
||||
void Function()? updateDialog;
|
||||
|
||||
showDialog(
|
||||
context: App.rootContext,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
updateDialog = () => setState(() {});
|
||||
return ContentDialog(
|
||||
title: isFinished
|
||||
? "Finished".tl
|
||||
: isErrored()
|
||||
? "Error".tl
|
||||
: "Importing".tl,
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
LinearProgressIndicator(
|
||||
value: isFinished ? 1 : null,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text("Imported @c comics".tlParams({
|
||||
"c": current,
|
||||
})),
|
||||
const SizedBox(height: 4),
|
||||
if (isErrored()) Text("Error: $errorMsg"),
|
||||
],
|
||||
).paddingHorizontal(16),
|
||||
actions: [
|
||||
Button.filled(
|
||||
color: (isFinished || isErrored())
|
||||
? null
|
||||
: context.colorScheme.error,
|
||||
onPressed: () {
|
||||
isCanceled = true;
|
||||
context.pop();
|
||||
},
|
||||
child: (isFinished || isErrored())
|
||||
? Text("OK".tl)
|
||||
: Text("Cancel".tl),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
).then((_) {
|
||||
isCanceled = true;
|
||||
});
|
||||
|
||||
while (!isFinished && !isCanceled) {
|
||||
try {
|
||||
await fetchNext();
|
||||
updateDialog?.call();
|
||||
} catch (e) {
|
||||
errorMsg = e.toString();
|
||||
updateDialog?.call();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
|
||||
import 'package:venera/components/components.dart';
|
||||
|
@@ -14,6 +14,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
||||
|
||||
late List<FavoriteItem> comics;
|
||||
|
||||
String? networkSource;
|
||||
String? networkFolder;
|
||||
|
||||
void updateComics() {
|
||||
setState(() {
|
||||
comics = LocalFavoritesManager().getAllComics(widget.folder);
|
||||
@@ -24,6 +27,9 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
||||
void initState() {
|
||||
favPage = context.findAncestorStateOfType<_FavoritesPageState>()!;
|
||||
comics = LocalFavoritesManager().getAllComics(widget.folder);
|
||||
var (a, b) = LocalFavoritesManager().findLinked(widget.folder);
|
||||
networkSource = a;
|
||||
networkFolder = b;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -49,6 +55,51 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
||||
child: Text(favPage.folder ?? "Unselected".tl),
|
||||
),
|
||||
actions: [
|
||||
if (networkSource != null)
|
||||
Tooltip(
|
||||
message: "Sync".tl,
|
||||
child: Flyout(
|
||||
flyoutBuilder: (context) {
|
||||
var sourceName = ComicSource.find(networkSource!)?.name ??
|
||||
networkSource!;
|
||||
var text = "The folder is Linked to @source".tlParams({
|
||||
"source": sourceName,
|
||||
});
|
||||
if(networkFolder != null && networkFolder!.isNotEmpty) {
|
||||
text += "\n${"Source Folder".tl}: $networkFolder";
|
||||
}
|
||||
return FlyoutContent(
|
||||
title: "Sync".tl,
|
||||
content: Text(text),
|
||||
actions: [
|
||||
Button.filled(
|
||||
child: Text("Update".tl),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
importNetworkFolder(
|
||||
networkSource!,
|
||||
widget.folder,
|
||||
networkFolder!,
|
||||
).then(
|
||||
(value) {
|
||||
updateComics();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: Builder(builder: (context) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.sync),
|
||||
onPressed: () {
|
||||
Flyout.of(context).show();
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
MenuButton(
|
||||
entries: [
|
||||
MenuEntry(
|
||||
|
@@ -108,6 +108,17 @@ class _NormalFavoritePageState extends State<_NormalFavoritePage> {
|
||||
onTap: context.width < _kTwoPanelChangeWidth ? showFolders : null,
|
||||
child: Text(widget.data.title),
|
||||
),
|
||||
actions: [
|
||||
MenuButton(entries: [
|
||||
MenuEntry(
|
||||
icon: Icons.sync,
|
||||
text: "Convert to local".tl,
|
||||
onClick: () {
|
||||
importNetworkFolder(widget.data.key, null, null);
|
||||
},
|
||||
)
|
||||
]),
|
||||
],
|
||||
),
|
||||
errorLeading: Appbar(
|
||||
leading: Tooltip(
|
||||
@@ -533,6 +544,17 @@ class _FavoriteFolder extends StatelessWidget {
|
||||
key: comicListKey,
|
||||
leadingSliver: SliverAppbar(
|
||||
title: Text(title),
|
||||
actions: [
|
||||
MenuButton(entries: [
|
||||
MenuEntry(
|
||||
icon: Icons.sync,
|
||||
text: "Convert to local".tl,
|
||||
onClick: () {
|
||||
importNetworkFolder(data.key, title, folderID);
|
||||
},
|
||||
)
|
||||
]),
|
||||
],
|
||||
),
|
||||
errorLeading: Appbar(
|
||||
title: Text(title),
|
||||
|
Reference in New Issue
Block a user