mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 07:47:24 +00:00
@@ -53,6 +53,7 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
<!-- [flutter 3.27.1] Impeller is still worse than skia, disable it -->
|
||||||
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false"/>
|
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false"/>
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
|
@@ -251,7 +251,14 @@
|
|||||||
"Aggregated Search": "聚合搜索",
|
"Aggregated Search": "聚合搜索",
|
||||||
"No search results found": "未找到搜索结果",
|
"No search results found": "未找到搜索结果",
|
||||||
"Added @c comics to download queue." : "已添加 @c 本漫画到下载队列",
|
"Added @c comics to download queue." : "已添加 @c 本漫画到下载队列",
|
||||||
"Download started": "下载已开始"
|
"Download started": "下载已开始",
|
||||||
|
"Click favorite": "点击收藏",
|
||||||
|
"End": "末尾",
|
||||||
|
"None": "无",
|
||||||
|
"View Detail": "查看详情",
|
||||||
|
"Select a directory which contains multiple cbz/zip files." : "选择一个包含多个cbz/zip文件的目录",
|
||||||
|
"Multiple cbz files" : "多个cbz文件",
|
||||||
|
"No valid comics found" : "未找到有效的漫画"
|
||||||
},
|
},
|
||||||
"zh_TW": {
|
"zh_TW": {
|
||||||
"Home": "首頁",
|
"Home": "首頁",
|
||||||
@@ -505,6 +512,13 @@
|
|||||||
"Aggregated Search": "聚合搜索",
|
"Aggregated Search": "聚合搜索",
|
||||||
"No search results found": "未找到搜索結果",
|
"No search results found": "未找到搜索結果",
|
||||||
"Added @c comics to download queue." : "已添加 @c 本漫畫到下載隊列",
|
"Added @c comics to download queue." : "已添加 @c 本漫畫到下載隊列",
|
||||||
"Download started": "下載已開始"
|
"Download started": "下載已開始",
|
||||||
|
"Click favorite": "點擊收藏",
|
||||||
|
"End": "末尾",
|
||||||
|
"None": "無",
|
||||||
|
"View Detail": "查看詳情",
|
||||||
|
"Select a directory which contains multiple cbz/zip files." : "選擇一個包含多個cbz/zip文件的目錄",
|
||||||
|
"Multiple cbz files" : "多個cbz文件",
|
||||||
|
"No valid comics found" : "未找到有效的漫畫"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
# Uncomment this line to define a global platform for your project
|
# Uncomment this line to define a global platform for your project
|
||||||
platform :ios, '14.0'
|
platform :ios, '15.0'
|
||||||
|
|
||||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
@@ -77,7 +77,7 @@ class ComicTile extends StatelessWidget {
|
|||||||
icon: Icons.stars_outlined,
|
icon: Icons.stars_outlined,
|
||||||
text: 'Add to favorites'.tl,
|
text: 'Add to favorites'.tl,
|
||||||
onClick: () {
|
onClick: () {
|
||||||
addFavorite(comic);
|
addFavorite([comic]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
|
@@ -41,13 +41,29 @@ class AnimatedTapRegion extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AnimatedTapRegionState extends State<AnimatedTapRegion> {
|
class _AnimatedTapRegionState extends State<AnimatedTapRegion> {
|
||||||
|
bool isScaled = false;
|
||||||
|
|
||||||
bool isHovered = false;
|
bool isHovered = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
onEnter: (_) => setState(() => isHovered = true),
|
onEnter: (_) {
|
||||||
onExit: (_) => setState(() => isHovered = false),
|
isHovered = true;
|
||||||
|
if (!isScaled) {
|
||||||
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
|
if (isHovered) {
|
||||||
|
setState(() => isScaled = true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onExit: (_) {
|
||||||
|
isHovered = false;
|
||||||
|
if(isScaled) {
|
||||||
|
setState(() => isScaled = false);
|
||||||
|
}
|
||||||
|
},
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: widget.onTap,
|
onTap: widget.onTap,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
@@ -55,7 +71,7 @@ class _AnimatedTapRegionState extends State<AnimatedTapRegion> {
|
|||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: AnimatedScale(
|
child: AnimatedScale(
|
||||||
duration: _fastAnimationDuration,
|
duration: _fastAnimationDuration,
|
||||||
scale: isHovered ? 1.1 : 1,
|
scale: isScaled ? 1.1 : 1,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@@ -22,8 +22,15 @@ class PopUpWidget<T> extends PopupRoute<T> {
|
|||||||
Widget body = PopupIndicatorWidget(
|
Widget body = PopupIndicatorWidget(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: showPopUp
|
decoration: showPopUp
|
||||||
? const BoxDecoration(
|
? BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
|
boxShadow: context.brightness == ui.Brightness.dark ? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.white.withAlpha(50),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
] : null,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
clipBehavior: showPopUp ? Clip.antiAlias : Clip.none,
|
clipBehavior: showPopUp ? Clip.antiAlias : Clip.none,
|
||||||
@@ -86,7 +93,8 @@ class PopupIndicatorWidget extends InheritedWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<T> showPopUpWidget<T>(BuildContext context, Widget widget) async {
|
Future<T> showPopUpWidget<T>(BuildContext context, Widget widget) async {
|
||||||
return await Navigator.of(context, rootNavigator: true).push(PopUpWidget(widget));
|
return await Navigator.of(context, rootNavigator: true)
|
||||||
|
.push(PopUpWidget(widget));
|
||||||
}
|
}
|
||||||
|
|
||||||
class PopUpWidgetScaffold extends StatefulWidget {
|
class PopUpWidgetScaffold extends StatefulWidget {
|
||||||
@@ -127,9 +135,8 @@ class _PopUpWidgetScaffoldState extends State<PopUpWidgetScaffold> {
|
|||||||
message: "Back".tl,
|
message: "Back".tl,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
icon: const Icon(Icons.arrow_back_sharp),
|
icon: const Icon(Icons.arrow_back_sharp),
|
||||||
onPressed: () => context.canPop()
|
onPressed: () =>
|
||||||
? context.pop()
|
context.canPop() ? context.pop() : App.pop(),
|
||||||
: App.pop(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@@ -60,7 +60,15 @@ class SideBarRoute<T> extends PopupRoute<T> {
|
|||||||
borderRadius: showSideBar
|
borderRadius: showSideBar
|
||||||
? const BorderRadius.horizontal(left: Radius.circular(16))
|
? const BorderRadius.horizontal(left: Radius.circular(16))
|
||||||
: null,
|
: null,
|
||||||
color: Theme.of(context).colorScheme.surfaceTint),
|
color: Theme.of(context).colorScheme.surfaceTint,
|
||||||
|
boxShadow: context.brightness == ui.Brightness.dark ? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.white.withAlpha(50),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
] : null,
|
||||||
|
),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
constraints: BoxConstraints(maxWidth: sideBarWidth),
|
constraints: BoxConstraints(maxWidth: sideBarWidth),
|
||||||
height: MediaQuery.of(context).size.height,
|
height: MediaQuery.of(context).size.height,
|
||||||
|
@@ -10,7 +10,7 @@ export "widget_utils.dart";
|
|||||||
export "context.dart";
|
export "context.dart";
|
||||||
|
|
||||||
class _App {
|
class _App {
|
||||||
final version = "1.1.1";
|
final version = "1.1.2";
|
||||||
|
|
||||||
bool get isAndroid => Platform.isAndroid;
|
bool get isAndroid => Platform.isAndroid;
|
||||||
|
|
||||||
|
@@ -122,6 +122,7 @@ class _Settings with ChangeNotifier {
|
|||||||
'enableClockAndBatteryInfoInReader': true,
|
'enableClockAndBatteryInfoInReader': true,
|
||||||
'ignoreCertificateErrors': false,
|
'ignoreCertificateErrors': false,
|
||||||
'authorizationRequired': false,
|
'authorizationRequired': false,
|
||||||
|
'onClickFavorite': 'viewDetail', // viewDetail, read
|
||||||
};
|
};
|
||||||
|
|
||||||
operator [](String key) {
|
operator [](String key) {
|
||||||
|
@@ -145,7 +145,7 @@ class ComicDetails with HistoryMixin {
|
|||||||
|
|
||||||
final int? likesCount;
|
final int? likesCount;
|
||||||
|
|
||||||
final int? commentsCount;
|
final int? commentCount;
|
||||||
|
|
||||||
final String? uploader;
|
final String? uploader;
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ class ComicDetails with HistoryMixin {
|
|||||||
subId = json["subId"],
|
subId = json["subId"],
|
||||||
likesCount = json["likesCount"],
|
likesCount = json["likesCount"],
|
||||||
isLiked = json["isLiked"],
|
isLiked = json["isLiked"],
|
||||||
commentsCount = json["commentsCount"],
|
commentCount = json["commentCount"],
|
||||||
uploader = json["uploader"],
|
uploader = json["uploader"],
|
||||||
uploadTime = json["uploadTime"],
|
uploadTime = json["uploadTime"],
|
||||||
updateTime = json["updateTime"],
|
updateTime = json["updateTime"],
|
||||||
@@ -216,7 +216,7 @@ class ComicDetails with HistoryMixin {
|
|||||||
"subId": subId,
|
"subId": subId,
|
||||||
"isLiked": isLiked,
|
"isLiked": isLiked,
|
||||||
"likesCount": likesCount,
|
"likesCount": likesCount,
|
||||||
"commentsCount": commentsCount,
|
"commentsCount": commentCount,
|
||||||
"uploader": uploader,
|
"uploader": uploader,
|
||||||
"uploadTime": uploadTime,
|
"uploadTime": uploadTime,
|
||||||
"updateTime": updateTime,
|
"updateTime": updateTime,
|
||||||
|
@@ -28,4 +28,12 @@ class ComicType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const local = ComicType(0);
|
static const local = ComicType(0);
|
||||||
|
|
||||||
|
factory ComicType.fromKey(String key) {
|
||||||
|
if(key == "local") {
|
||||||
|
return local;
|
||||||
|
} else {
|
||||||
|
return ComicType(key.hashCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@@ -27,10 +27,8 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
screen.size.height * _normalComicImageRatio,
|
screen.size.height * _normalComicImageRatio,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_effectiveScreenWidth = max(
|
_effectiveScreenWidth =
|
||||||
_effectiveScreenWidth ?? 0,
|
max(_effectiveScreenWidth ?? 0, screen.size.width);
|
||||||
screen.size.width
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_effectiveScreenWidth! < _minComicImageWidth) {
|
if (_effectiveScreenWidth! < _minComicImageWidth) {
|
||||||
@@ -110,7 +108,10 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final buffer = await ImmutableBuffer.fromUint8List(data);
|
final buffer = await ImmutableBuffer.fromUint8List(data);
|
||||||
return await decode(buffer, getTargetSize: _getTargetSize);
|
return await decode(
|
||||||
|
buffer,
|
||||||
|
getTargetSize: enableResize ? _getTargetSize : null,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await CacheManager().delete(this.key);
|
await CacheManager().delete(this.key);
|
||||||
if (data.length < 2 * 1024) {
|
if (data.length < 2 * 1024) {
|
||||||
@@ -151,6 +152,8 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
|
|||||||
String toString() {
|
String toString() {
|
||||||
return "$runtimeType($key)";
|
return "$runtimeType($key)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get enableResize => false;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef FileDecoderCallback = Future<ui.Codec> Function(Uint8List);
|
typedef FileDecoderCallback = Future<ui.Codec> Function(Uint8List);
|
||||||
|
@@ -49,4 +49,7 @@ class ReaderImageProvider
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get key => "$imageKey@$sourceKey@$cid@$eid";
|
String get key => "$imageKey@$sourceKey@$cid@$eid";
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get enableResize => true;
|
||||||
}
|
}
|
||||||
|
@@ -473,7 +473,8 @@ class LocalManager with ChangeNotifier {
|
|||||||
var dir = Directory(FilePath.join(path, c.directory));
|
var dir = Directory(FilePath.join(path, c.directory));
|
||||||
dir.deleteIgnoreError(recursive: true);
|
dir.deleteIgnoreError(recursive: true);
|
||||||
}
|
}
|
||||||
//Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted.
|
// Deleting a local comic means that it's nolonger available, thus both favorite and history should be deleted.
|
||||||
|
if(c.comicType == ComicType.local) {
|
||||||
if(HistoryManager().findSync(c.id, c.comicType) != null) {
|
if(HistoryManager().findSync(c.id, c.comicType) != null) {
|
||||||
HistoryManager().remove(c.id, c.comicType);
|
HistoryManager().remove(c.id, c.comicType);
|
||||||
}
|
}
|
||||||
@@ -481,6 +482,7 @@ class LocalManager with ChangeNotifier {
|
|||||||
for (var f in folders) {
|
for (var f in folders) {
|
||||||
LocalFavoritesManager().deleteComicWithId(f, c.id, c.comicType);
|
LocalFavoritesManager().deleteComicWithId(f, c.id, c.comicType);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
remove(c.id, c.comicType);
|
remove(c.id, c.comicType);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@@ -146,14 +146,19 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
|
|
||||||
String? _cover;
|
String? _cover;
|
||||||
|
|
||||||
|
/// All images to download, key is chapter name
|
||||||
Map<String, List<String>>? _images;
|
Map<String, List<String>>? _images;
|
||||||
|
|
||||||
|
/// Downloaded image count
|
||||||
int _downloadedCount = 0;
|
int _downloadedCount = 0;
|
||||||
|
|
||||||
|
/// Total image count
|
||||||
int _totalCount = 0;
|
int _totalCount = 0;
|
||||||
|
|
||||||
|
/// Current downloading image index
|
||||||
int _index = 0;
|
int _index = 0;
|
||||||
|
|
||||||
|
/// Current downloading chapter, index of [_images]
|
||||||
int _chapter = 0;
|
int _chapter = 0;
|
||||||
|
|
||||||
var tasks = <int, _ImageDownloadWrapper>{};
|
var tasks = <int, _ImageDownloadWrapper>{};
|
||||||
@@ -180,10 +185,10 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
|
|||||||
if (comic!.chapters != null) {
|
if (comic!.chapters != null) {
|
||||||
saveTo = Directory(FilePath.join(
|
saveTo = Directory(FilePath.join(
|
||||||
path!,
|
path!,
|
||||||
comic!.chapters!.keys.elementAt(_chapter),
|
_images!.keys.elementAt(_chapter),
|
||||||
));
|
));
|
||||||
if (!saveTo.existsSync()) {
|
if (!saveTo.existsSync()) {
|
||||||
saveTo.createSync();
|
saveTo.createSync(recursive: true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
saveTo = Directory(path!);
|
saveTo = Directory(path!);
|
||||||
|
@@ -172,7 +172,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
isLiked = comic.isLiked ?? false;
|
isLiked = comic.isLiked ?? false;
|
||||||
isFavorite = comic.isFavorite ?? false;
|
isFavorite = comic.isFavorite ?? false;
|
||||||
if (comic.chapters == null) {
|
if (comic.chapters == null) {
|
||||||
isDownloaded = await LocalManager().isDownloaded(
|
isDownloaded = LocalManager().isDownloaded(
|
||||||
comic.id,
|
comic.id,
|
||||||
comic.comicType,
|
comic.comicType,
|
||||||
0,
|
0,
|
||||||
@@ -292,7 +292,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
if (comicSource.commentsLoader != null)
|
if (comicSource.commentsLoader != null)
|
||||||
_ActionButton(
|
_ActionButton(
|
||||||
icon: const Icon(Icons.comment),
|
icon: const Icon(Icons.comment),
|
||||||
text: (comic.commentsCount ?? 'Comments'.tl).toString(),
|
text: (comic.commentCount ?? 'Comments'.tl).toString(),
|
||||||
onPressed: showComments,
|
onPressed: showComments,
|
||||||
iconColor: context.useTextColor(Colors.green),
|
iconColor: context.useTextColor(Colors.green),
|
||||||
),
|
),
|
||||||
@@ -679,7 +679,7 @@ abstract mixin class _ComicPageActions {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (comic.chapters == null &&
|
if (comic.chapters == null &&
|
||||||
await LocalManager().isDownloaded(comic.id, comic.comicType, 0)) {
|
LocalManager().isDownloaded(comic.id, comic.comicType, 0)) {
|
||||||
App.rootContext.showMessage(message: "The comic is downloaded".tl);
|
App.rootContext.showMessage(message: "The comic is downloaded".tl);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -77,7 +77,7 @@ String? validateFolderName(String newFolderName) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addFavorite(Comic comic) {
|
void addFavorite(List<Comic> comics) {
|
||||||
var folders = LocalFavoritesManager().folderNames;
|
var folders = LocalFavoritesManager().folderNames;
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -105,6 +105,7 @@ void addFavorite(Comic comic) {
|
|||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (selectedFolder != null) {
|
if (selectedFolder != null) {
|
||||||
|
for (var comic in comics) {
|
||||||
LocalFavoritesManager().addComic(
|
LocalFavoritesManager().addComic(
|
||||||
selectedFolder!,
|
selectedFolder!,
|
||||||
FavoriteItem(
|
FavoriteItem(
|
||||||
@@ -118,6 +119,7 @@ void addFavorite(Comic comic) {
|
|||||||
tags: comic.tags ?? [],
|
tags: comic.tags ?? [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
context.pop();
|
context.pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -14,6 +14,7 @@ import 'package:venera/foundation/local.dart';
|
|||||||
import 'package:venera/foundation/res.dart';
|
import 'package:venera/foundation/res.dart';
|
||||||
import 'package:venera/network/download.dart';
|
import 'package:venera/network/download.dart';
|
||||||
import 'package:venera/pages/comic_page.dart';
|
import 'package:venera/pages/comic_page.dart';
|
||||||
|
import 'package:venera/pages/reader/reader.dart';
|
||||||
import 'package:venera/utils/io.dart';
|
import 'package:venera/utils/io.dart';
|
||||||
import 'package:venera/utils/translations.dart';
|
import 'package:venera/utils/translations.dart';
|
||||||
|
|
||||||
|
@@ -389,8 +389,8 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
onTap: multiSelectMode
|
onTap: (c) {
|
||||||
? (c) {
|
if (multiSelectMode) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (selectedComics.containsKey(c as FavoriteItem)) {
|
if (selectedComics.containsKey(c as FavoriteItem)) {
|
||||||
selectedComics.remove(c);
|
selectedComics.remove(c);
|
||||||
@@ -400,10 +400,17 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
|
|||||||
}
|
}
|
||||||
lastSelectedIndex = comics.indexOf(c);
|
lastSelectedIndex = comics.indexOf(c);
|
||||||
});
|
});
|
||||||
}
|
} else if (appdata.settings["onClickFavorite"] == "viewDetail") {
|
||||||
: (c) {
|
|
||||||
App.mainNavigatorKey?.currentContext
|
App.mainNavigatorKey?.currentContext
|
||||||
?.to(() => ComicPage(id: c.id, sourceKey: c.sourceKey));
|
?.to(() => ComicPage(id: c.id, sourceKey: c.sourceKey));
|
||||||
|
} else {
|
||||||
|
App.mainNavigatorKey?.currentContext?.to(
|
||||||
|
() => ReaderWithLoading(
|
||||||
|
id: c.id,
|
||||||
|
sourceKey: c.sourceKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onLongPressed: (c) {
|
onLongPressed: (c) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@@ -264,7 +264,8 @@ class _HistoryState extends State<_History> {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: history.length,
|
itemCount: history.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return InkWell(
|
return AnimatedTapRegion(
|
||||||
|
borderRadius: 8,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.to(
|
context.to(
|
||||||
() => ComicPage(
|
() => ComicPage(
|
||||||
@@ -273,11 +274,9 @@ class _HistoryState extends State<_History> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 92,
|
width: 92,
|
||||||
height: 114,
|
height: 114,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
@@ -293,7 +292,7 @@ class _HistoryState extends State<_History> {
|
|||||||
filterQuality: FilterQuality.medium,
|
filterQuality: FilterQuality.medium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
).paddingHorizontal(8);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).paddingHorizontal(8).paddingBottom(16),
|
).paddingHorizontal(8).paddingBottom(16),
|
||||||
@@ -386,15 +385,14 @@ class _LocalState extends State<_Local> {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: local.length,
|
itemCount: local.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return InkWell(
|
return AnimatedTapRegion(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
local[index].read();
|
local[index].read();
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: 8,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 92,
|
width: 92,
|
||||||
height: 114,
|
height: 114,
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
@@ -412,7 +410,7 @@ class _LocalState extends State<_Local> {
|
|||||||
filterQuality: FilterQuality.medium,
|
filterQuality: FilterQuality.medium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
).paddingHorizontal(8);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).paddingHorizontal(8),
|
).paddingHorizontal(8),
|
||||||
@@ -497,12 +495,14 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
"Select a directory which contains the comic files.".tl,
|
"Select a directory which contains the comic files.".tl,
|
||||||
"Select a directory which contains the comic directories.".tl,
|
"Select a directory which contains the comic directories.".tl,
|
||||||
"Select a cbz/zip file.".tl,
|
"Select a cbz/zip file.".tl,
|
||||||
|
"Select a directory which contains multiple cbz/zip files.".tl,
|
||||||
"Select an EhViewer database and a download folder.".tl
|
"Select an EhViewer database and a download folder.".tl
|
||||||
][type];
|
][type];
|
||||||
List<String> importMethods = [
|
List<String> importMethods = [
|
||||||
"Single Comic".tl,
|
"Single Comic".tl,
|
||||||
"Multiple Comics".tl,
|
"Multiple Comics".tl,
|
||||||
"A cbz file".tl,
|
"A cbz file".tl,
|
||||||
|
"Multiple cbz files".tl,
|
||||||
"EhViewer downloads".tl
|
"EhViewer downloads".tl
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -630,7 +630,8 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
|
|||||||
0 => await importer.directory(true),
|
0 => await importer.directory(true),
|
||||||
1 => await importer.directory(false),
|
1 => await importer.directory(false),
|
||||||
2 => await importer.cbz(),
|
2 => await importer.cbz(),
|
||||||
3 => await importer.ehViewer(),
|
3 => await importer.multipleCbz(),
|
||||||
|
4 => await importer.ehViewer(),
|
||||||
int() => true,
|
int() => true,
|
||||||
};
|
};
|
||||||
if(result) {
|
if(result) {
|
||||||
|
@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:venera/components/components.dart';
|
import 'package:venera/components/components.dart';
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/foundation/appdata.dart';
|
import 'package:venera/foundation/appdata.dart';
|
||||||
import 'package:venera/foundation/comic_source/comic_source.dart';
|
|
||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/foundation/log.dart';
|
import 'package:venera/foundation/log.dart';
|
||||||
import 'package:venera/pages/downloading_page.dart';
|
import 'package:venera/pages/downloading_page.dart';
|
||||||
|
import 'package:venera/pages/favorites/favorites_page.dart';
|
||||||
import 'package:venera/utils/cbz.dart';
|
import 'package:venera/utils/cbz.dart';
|
||||||
import 'package:venera/utils/epub.dart';
|
import 'package:venera/utils/epub.dart';
|
||||||
import 'package:venera/utils/io.dart';
|
import 'package:venera/utils/io.dart';
|
||||||
@@ -30,7 +30,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
|
|
||||||
bool multiSelectMode = false;
|
bool multiSelectMode = false;
|
||||||
|
|
||||||
Map<Comic, bool> selectedComics = {};
|
Map<LocalComic, bool> selectedComics = {};
|
||||||
|
|
||||||
void update() {
|
void update() {
|
||||||
if (keyword.isEmpty) {
|
if (keyword.isEmpty) {
|
||||||
@@ -117,8 +117,32 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Widget buildMultiSelectMenu() {
|
||||||
Widget build(BuildContext context) {
|
return MenuButton(entries: [
|
||||||
|
MenuEntry(
|
||||||
|
icon: Icons.delete_outline,
|
||||||
|
text: "Delete".tl,
|
||||||
|
onClick: () {
|
||||||
|
deleteComics(selectedComics.keys.toList()).then((value) {
|
||||||
|
if (value) {
|
||||||
|
setState(() {
|
||||||
|
multiSelectMode = false;
|
||||||
|
selectedComics.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuEntry(
|
||||||
|
icon: Icons.favorite_border,
|
||||||
|
text: "Add to favorites".tl,
|
||||||
|
onClick: () {
|
||||||
|
addFavorite(selectedComics.keys.toList());
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
void selectAll() {
|
void selectAll() {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedComics = comics.asMap().map((k, v) => MapEntry(v, true));
|
selectedComics = comics.asMap().map((k, v) => MapEntry(v, true));
|
||||||
@@ -140,25 +164,8 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectRange() {
|
@override
|
||||||
setState(() {
|
Widget build(BuildContext context) {
|
||||||
List<int> l = [];
|
|
||||||
selectedComics.forEach((k, v) {
|
|
||||||
l.add(comics.indexOf(k as LocalComic));
|
|
||||||
});
|
|
||||||
if (l.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
l.sort();
|
|
||||||
int start = l.first;
|
|
||||||
int end = l.last;
|
|
||||||
selectedComics.clear();
|
|
||||||
selectedComics.addEntries(List.generate(end - start + 1, (i) {
|
|
||||||
return MapEntry(comics[start + i], true);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> selectActions = [
|
List<Widget> selectActions = [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.select_all),
|
icon: const Icon(Icons.select_all),
|
||||||
@@ -172,10 +179,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
icon: const Icon(Icons.flip),
|
icon: const Icon(Icons.flip),
|
||||||
tooltip: "Invert Selection".tl,
|
tooltip: "Invert Selection".tl,
|
||||||
onPressed: invertSelection),
|
onPressed: invertSelection),
|
||||||
IconButton(
|
buildMultiSelectMenu(),
|
||||||
icon: const Icon(Icons.border_horizontal_outlined),
|
|
||||||
tooltip: "Select in range".tl,
|
|
||||||
onPressed: selectRange),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var body = Scaffold(
|
var body = Scaffold(
|
||||||
@@ -212,19 +216,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Tooltip(
|
|
||||||
message: multiSelectMode
|
|
||||||
? "Exit Multi-Select".tl
|
|
||||||
: "Multi-Select".tl,
|
|
||||||
child: IconButton(
|
|
||||||
icon: const Icon(Icons.checklist),
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
multiSelectMode = !multiSelectMode;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
else if (multiSelectMode)
|
else if (multiSelectMode)
|
||||||
@@ -275,18 +266,27 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
SliverGridComics(
|
SliverGridComics(
|
||||||
comics: comics,
|
comics: comics,
|
||||||
selections: selectedComics,
|
selections: selectedComics,
|
||||||
onTap: multiSelectMode
|
onLongPressed: (c) {
|
||||||
? (c) {
|
setState(() {
|
||||||
|
multiSelectMode = true;
|
||||||
|
selectedComics[c as LocalComic] = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onTap: (c) {
|
||||||
|
if(multiSelectMode) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (selectedComics.containsKey(c as LocalComic)) {
|
if (selectedComics.containsKey(c as LocalComic)) {
|
||||||
selectedComics.remove(c);
|
selectedComics.remove(c);
|
||||||
} else {
|
} else {
|
||||||
selectedComics[c] = true;
|
selectedComics[c] = true;
|
||||||
}
|
}
|
||||||
});
|
if(selectedComics.isEmpty) {
|
||||||
|
multiSelectMode = false;
|
||||||
}
|
}
|
||||||
: (c) {
|
});
|
||||||
|
} else {
|
||||||
(c as LocalComic).read();
|
(c as LocalComic).read();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
menuBuilder: (c) {
|
menuBuilder: (c) {
|
||||||
return [
|
return [
|
||||||
@@ -294,45 +294,13 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
icon: Icons.delete,
|
icon: Icons.delete,
|
||||||
text: "Delete".tl,
|
text: "Delete".tl,
|
||||||
onClick: () {
|
onClick: () {
|
||||||
showDialog(
|
deleteComics([c as LocalComic]).then((value) {
|
||||||
context: context,
|
if (value && multiSelectMode) {
|
||||||
builder: (context) {
|
|
||||||
bool removeComicFile = true;
|
|
||||||
return StatefulBuilder(builder: (context, state) {
|
|
||||||
return ContentDialog(
|
|
||||||
title: "Delete".tl,
|
|
||||||
content: CheckboxListTile(
|
|
||||||
title: Text("Also remove files on disk".tl),
|
|
||||||
value: removeComicFile,
|
|
||||||
onChanged: (v) {
|
|
||||||
state(() {
|
|
||||||
removeComicFile = !removeComicFile;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
FilledButton(
|
|
||||||
onPressed: () {
|
|
||||||
context.pop();
|
|
||||||
if (multiSelectMode) {
|
|
||||||
for (var comic in selectedComics.keys) {
|
|
||||||
LocalManager().deleteComic(
|
|
||||||
comic as LocalComic,
|
|
||||||
removeComicFile);
|
|
||||||
}
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
multiSelectMode = false;
|
||||||
selectedComics.clear();
|
selectedComics.clear();
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
LocalManager().deleteComic(
|
|
||||||
c as LocalComic, removeComicFile);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
child: Text("Confirm".tl),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
@@ -344,26 +312,14 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
allowCancel: false,
|
allowCancel: false,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
if (multiSelectMode) {
|
|
||||||
for (var comic in selectedComics.keys) {
|
|
||||||
var file = await CBZ.export(comic as LocalComic);
|
|
||||||
await saveFile(filename: file.name, file: file);
|
|
||||||
await file.delete();
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
selectedComics.clear();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
var file = await CBZ.export(c as LocalComic);
|
var file = await CBZ.export(c as LocalComic);
|
||||||
await saveFile(filename: file.name, file: file);
|
await saveFile(filename: file.name, file: file);
|
||||||
await file.delete();
|
await file.delete();
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
context.showMessage(message: e.toString());
|
context.showMessage(message: e.toString());
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
}),
|
}),
|
||||||
if (!multiSelectMode)
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon: Icons.picture_as_pdf_outlined,
|
icon: Icons.picture_as_pdf_outlined,
|
||||||
text: "Export as pdf".tl,
|
text: "Export as pdf".tl,
|
||||||
@@ -391,7 +347,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!multiSelectMode)
|
|
||||||
MenuEntry(
|
MenuEntry(
|
||||||
icon: Icons.import_contacts_outlined,
|
icon: Icons.import_contacts_outlined,
|
||||||
text: "Export as epub".tl,
|
text: "Export as epub".tl,
|
||||||
@@ -444,4 +399,44 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
|
|||||||
child: body,
|
child: body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> deleteComics(List<LocalComic> comics) async {
|
||||||
|
bool isDeleted = false;
|
||||||
|
await showDialog(
|
||||||
|
context: App.rootContext,
|
||||||
|
builder: (context) {
|
||||||
|
bool removeComicFile = true;
|
||||||
|
return StatefulBuilder(builder: (context, state) {
|
||||||
|
return ContentDialog(
|
||||||
|
title: "Delete".tl,
|
||||||
|
content: CheckboxListTile(
|
||||||
|
title: Text("Also remove files on disk".tl),
|
||||||
|
value: removeComicFile,
|
||||||
|
onChanged: (v) {
|
||||||
|
state(() {
|
||||||
|
removeComicFile = !removeComicFile;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.pop();
|
||||||
|
for (var comic in comics) {
|
||||||
|
LocalManager().deleteComic(
|
||||||
|
comic,
|
||||||
|
removeComicFile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
isDeleted = true;
|
||||||
|
},
|
||||||
|
child: Text("Confirm".tl),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return isDeleted;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@ class _ReaderImagesState extends State<_ReaderImages> {
|
|||||||
if (inProgress) return;
|
if (inProgress) return;
|
||||||
inProgress = true;
|
inProgress = true;
|
||||||
if (reader.type == ComicType.local ||
|
if (reader.type == ComicType.local ||
|
||||||
(await LocalManager()
|
(LocalManager()
|
||||||
.isDownloaded(reader.cid, reader.type, reader.chapter))) {
|
.isDownloaded(reader.cid, reader.type, reader.chapter))) {
|
||||||
try {
|
try {
|
||||||
var images = await LocalManager()
|
var images = await LocalManager()
|
||||||
|
101
lib/pages/reader/loading.dart
Normal file
101
lib/pages/reader/loading.dart
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
part of 'reader.dart';
|
||||||
|
|
||||||
|
class ReaderWithLoading extends StatefulWidget {
|
||||||
|
const ReaderWithLoading({
|
||||||
|
super.key,
|
||||||
|
required this.id,
|
||||||
|
required this.sourceKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
final String sourceKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ReaderWithLoading> createState() => _ReaderWithLoadingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReaderWithLoadingState
|
||||||
|
extends LoadingState<ReaderWithLoading, ReaderProps> {
|
||||||
|
@override
|
||||||
|
Widget buildContent(BuildContext context, ReaderProps data) {
|
||||||
|
return Reader(
|
||||||
|
type: data.type,
|
||||||
|
cid: data.cid,
|
||||||
|
name: data.name,
|
||||||
|
chapters: data.chapters,
|
||||||
|
history: data.history,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Res<ReaderProps>> loadData() async {
|
||||||
|
var comicSource = ComicSource.find(widget.sourceKey);
|
||||||
|
var history = HistoryManager().findSync(
|
||||||
|
widget.id,
|
||||||
|
ComicType.fromKey(widget.sourceKey),
|
||||||
|
);
|
||||||
|
if (comicSource == null) {
|
||||||
|
var localComic = LocalManager().find(
|
||||||
|
widget.id,
|
||||||
|
ComicType.fromKey(widget.sourceKey),
|
||||||
|
);
|
||||||
|
if (localComic == null) {
|
||||||
|
return Res.error("comic not found");
|
||||||
|
}
|
||||||
|
return Res(
|
||||||
|
ReaderProps(
|
||||||
|
type: ComicType.fromKey(widget.sourceKey),
|
||||||
|
cid: widget.id,
|
||||||
|
name: localComic.title,
|
||||||
|
chapters: localComic.chapters,
|
||||||
|
history: history ??
|
||||||
|
History.fromModel(
|
||||||
|
model: localComic,
|
||||||
|
ep: 0,
|
||||||
|
page: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
var comic = await comicSource.loadComicInfo!(widget.id);
|
||||||
|
if (comic.error) {
|
||||||
|
return Res.fromErrorRes(comic);
|
||||||
|
}
|
||||||
|
return Res(
|
||||||
|
ReaderProps(
|
||||||
|
type: ComicType.fromKey(widget.sourceKey),
|
||||||
|
cid: widget.id,
|
||||||
|
name: comic.data.title,
|
||||||
|
chapters: comic.data.chapters,
|
||||||
|
history: history ??
|
||||||
|
History.fromModel(
|
||||||
|
model: comic.data,
|
||||||
|
ep: 0,
|
||||||
|
page: 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReaderProps {
|
||||||
|
final ComicType type;
|
||||||
|
|
||||||
|
final String cid;
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
final Map<String, String>? chapters;
|
||||||
|
|
||||||
|
final History history;
|
||||||
|
|
||||||
|
const ReaderProps({
|
||||||
|
required this.type,
|
||||||
|
required this.cid,
|
||||||
|
required this.name,
|
||||||
|
required this.chapters,
|
||||||
|
required this.history,
|
||||||
|
});
|
||||||
|
}
|
@@ -18,11 +18,13 @@ import 'package:venera/components/custom_slider.dart';
|
|||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/foundation/appdata.dart';
|
import 'package:venera/foundation/appdata.dart';
|
||||||
import 'package:venera/foundation/cache_manager.dart';
|
import 'package:venera/foundation/cache_manager.dart';
|
||||||
|
import 'package:venera/foundation/comic_source/comic_source.dart';
|
||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
import 'package:venera/foundation/history.dart';
|
import 'package:venera/foundation/history.dart';
|
||||||
import 'package:venera/foundation/image_provider/reader_image.dart';
|
import 'package:venera/foundation/image_provider/reader_image.dart';
|
||||||
import 'package:venera/foundation/local.dart';
|
import 'package:venera/foundation/local.dart';
|
||||||
import 'package:venera/foundation/log.dart';
|
import 'package:venera/foundation/log.dart';
|
||||||
|
import 'package:venera/foundation/res.dart';
|
||||||
import 'package:venera/pages/settings/settings_page.dart';
|
import 'package:venera/pages/settings/settings_page.dart';
|
||||||
import 'package:venera/utils/data_sync.dart';
|
import 'package:venera/utils/data_sync.dart';
|
||||||
import 'package:venera/utils/file_type.dart';
|
import 'package:venera/utils/file_type.dart';
|
||||||
@@ -36,6 +38,7 @@ part 'scaffold.dart';
|
|||||||
part 'images.dart';
|
part 'images.dart';
|
||||||
part 'gesture.dart';
|
part 'gesture.dart';
|
||||||
part 'comic_image.dart';
|
part 'comic_image.dart';
|
||||||
|
part 'loading.dart';
|
||||||
|
|
||||||
extension _ReaderContext on BuildContext {
|
extension _ReaderContext on BuildContext {
|
||||||
_ReaderState get reader => findAncestorStateOfType<_ReaderState>()!;
|
_ReaderState get reader => findAncestorStateOfType<_ReaderState>()!;
|
||||||
|
@@ -16,18 +16,18 @@ class _LocalFavoritesSettingsState extends State<LocalFavoritesSettings> {
|
|||||||
SelectSetting(
|
SelectSetting(
|
||||||
title: "Add new favorite to".tl,
|
title: "Add new favorite to".tl,
|
||||||
settingKey: "newFavoriteAddTo",
|
settingKey: "newFavoriteAddTo",
|
||||||
optionTranslation: const {
|
optionTranslation: {
|
||||||
"start": "Start",
|
"start": "Start".tl,
|
||||||
"end": "End",
|
"end": "End".tl,
|
||||||
},
|
},
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
SelectSetting(
|
SelectSetting(
|
||||||
title: "Move favorite after reading".tl,
|
title: "Move favorite after reading".tl,
|
||||||
settingKey: "moveFavoriteAfterRead",
|
settingKey: "moveFavoriteAfterRead",
|
||||||
optionTranslation: const {
|
optionTranslation: {
|
||||||
"none": "None",
|
"none": "None".tl,
|
||||||
"end": "End",
|
"end": "End".tl,
|
||||||
"start": "Start",
|
"start": "Start".tl,
|
||||||
},
|
},
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
SelectSetting(
|
SelectSetting(
|
||||||
@@ -48,6 +48,14 @@ class _LocalFavoritesSettingsState extends State<LocalFavoritesSettings> {
|
|||||||
},
|
},
|
||||||
actionTitle: 'Delete'.tl,
|
actionTitle: 'Delete'.tl,
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
|
SelectSetting(
|
||||||
|
title: "Click favorite".tl,
|
||||||
|
settingKey: "onClickFavorite",
|
||||||
|
optionTranslation: {
|
||||||
|
"viewDetail": "View Detail".tl,
|
||||||
|
"read": "Read".tl,
|
||||||
|
},
|
||||||
|
).toSliver(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:venera/foundation/app.dart';
|
import 'package:venera/foundation/app.dart';
|
||||||
import 'package:venera/foundation/comic_type.dart';
|
import 'package:venera/foundation/comic_type.dart';
|
||||||
@@ -63,7 +62,7 @@ abstract class CBZ {
|
|||||||
var cache = Directory(FilePath.join(App.cachePath, 'cbz_import'));
|
var cache = Directory(FilePath.join(App.cachePath, 'cbz_import'));
|
||||||
if (cache.existsSync()) cache.deleteSync(recursive: true);
|
if (cache.existsSync()) cache.deleteSync(recursive: true);
|
||||||
cache.createSync();
|
cache.createSync();
|
||||||
await Isolate.run(() => ZipFile.openAndExtract(file.path, cache.path));
|
await ZipFile.openAndExtractAsync(file.path, cache.path, 4);
|
||||||
var metaDataFile = File(FilePath.join(cache.path, 'metadata.json'));
|
var metaDataFile = File(FilePath.join(cache.path, 'metadata.json'));
|
||||||
ComicMetaData? metaData;
|
ComicMetaData? metaData;
|
||||||
if (metaDataFile.existsSync()) {
|
if (metaDataFile.existsSync()) {
|
||||||
@@ -208,13 +207,13 @@ abstract class CBZ {
|
|||||||
).toJson(),
|
).toJson(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
var cbz = File(FilePath.join(App.cachePath, '${comic.title}.cbz'));
|
var cbz = File(FilePath.join(App.cachePath, sanitizeFileName('${comic.title}.cbz')));
|
||||||
await _compress(cache.path, cbz.path);
|
await _compress(cache.path, cbz.path);
|
||||||
cache.deleteSync(recursive: true);
|
cache.deleteSync(recursive: true);
|
||||||
return cbz;
|
return cbz;
|
||||||
}
|
}
|
||||||
|
|
||||||
static _compress(String src, String dst) async {
|
static _compress(String src, String dst) async {
|
||||||
await Isolate.run(() => ZipFile.compressFolder(src, dst));
|
await ZipFile.compressFolderAsync(src, dst, 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,33 @@ class ImportComic {
|
|||||||
return registerComics(imported, false);
|
return registerComics(imported, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> multipleCbz() async {
|
||||||
|
var picker = DirectoryPicker();
|
||||||
|
var dir = await picker.pickDirectory();
|
||||||
|
if (dir != null) {
|
||||||
|
var files = (await dir.list().toList()).whereType<File>().toList();
|
||||||
|
files.removeWhere((e) => e.extension != 'cbz' && e.extension != 'zip');
|
||||||
|
Map<String?, List<LocalComic>> imported = {};
|
||||||
|
var controller = showLoadingDialog(App.rootContext, allowCancel: false);
|
||||||
|
var comics = <LocalComic>[];
|
||||||
|
for (var file in files) {
|
||||||
|
try {
|
||||||
|
var comic = await CBZ.import(file);
|
||||||
|
comics.add(comic);
|
||||||
|
} catch (e, s) {
|
||||||
|
Log.error("Import Comic", e.toString(), s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (comics.isEmpty) {
|
||||||
|
App.rootContext.showMessage(message: "No valid comics found".tl);
|
||||||
|
}
|
||||||
|
imported[selectedFolder] = comics;
|
||||||
|
controller.close();
|
||||||
|
return registerComics(imported, false);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> ehViewer() async {
|
Future<bool> ehViewer() async {
|
||||||
var dbFile = await selectFile(ext: ['db']);
|
var dbFile = await selectFile(ext: ['db']);
|
||||||
final picker = DirectoryPicker();
|
final picker = DirectoryPicker();
|
||||||
|
@@ -72,6 +72,7 @@ extension FileSystemEntityExt on FileSystemEntity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension FileExtension on File {
|
extension FileExtension on File {
|
||||||
|
/// Get the file extension, not including the dot.
|
||||||
String get extension => path.split('.').last;
|
String get extension => path.split('.').last;
|
||||||
|
|
||||||
/// Copy the file to the specified path using memory.
|
/// Copy the file to the specified path using memory.
|
||||||
|
43
macos/Podfile
Normal file
43
macos/Podfile
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
platform :osx, '12.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_macos_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_macos_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
@@ -557,7 +557,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
@@ -639,7 +639,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
@@ -689,7 +689,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
20
pubspec.lock
20
pubspec.lock
@@ -446,11 +446,10 @@ packages:
|
|||||||
flutter_to_arch:
|
flutter_to_arch:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
path: "."
|
name: flutter_to_arch
|
||||||
ref: HEAD
|
sha256: "656cffc182b05af38aa96a1115931620b8865c4b0cfe00813b26fcff0875f2ab"
|
||||||
resolved-ref: "15bfead0380fda79b0256b37c73b886b0882f1bf"
|
url: "https://pub.dev"
|
||||||
url: "https://github.com/wgh136/flutter_to_arch"
|
source: hosted
|
||||||
source: git
|
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
flutter_to_debian:
|
flutter_to_debian:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
@@ -1125,12 +1124,11 @@ packages:
|
|||||||
zip_flutter:
|
zip_flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
name: zip_flutter
|
||||||
ref: "7fb4e6ad7662f787f2c248b01f89ccbf8e8ab5cc"
|
sha256: "955b53d58709fcd9feefbed3d41b5522bc5273e677603e9fc67017a81e568d24"
|
||||||
resolved-ref: "7fb4e6ad7662f787f2c248b01f89ccbf8e8ab5cc"
|
url: "https://pub.dev"
|
||||||
url: "https://github.com/wgh136/zip_flutter"
|
source: hosted
|
||||||
source: git
|
version: "0.0.5"
|
||||||
version: "0.0.4"
|
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.0 <4.0.0"
|
||||||
flutter: ">=3.27.1"
|
flutter: ">=3.27.1"
|
||||||
|
10
pubspec.yaml
10
pubspec.yaml
@@ -2,7 +2,7 @@ name: venera
|
|||||||
description: "A comic app."
|
description: "A comic app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.1.1+111
|
version: 1.1.2+112
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.6.0 <4.0.0'
|
sdk: '>=3.6.0 <4.0.0'
|
||||||
@@ -51,10 +51,7 @@ dependencies:
|
|||||||
sliver_tools: ^0.2.12
|
sliver_tools: ^0.2.12
|
||||||
flutter_file_dialog: ^3.0.2
|
flutter_file_dialog: ^3.0.2
|
||||||
file_selector: ^1.0.3
|
file_selector: ^1.0.3
|
||||||
zip_flutter:
|
zip_flutter: ^0.0.5
|
||||||
git:
|
|
||||||
url: https://github.com/wgh136/zip_flutter
|
|
||||||
ref: 7fb4e6ad7662f787f2c248b01f89ccbf8e8ab5cc
|
|
||||||
lodepng_flutter:
|
lodepng_flutter:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/venera-app/lodepng_flutter
|
url: https://github.com/venera-app/lodepng_flutter
|
||||||
@@ -79,8 +76,7 @@ dev_dependencies:
|
|||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
flutter_to_arch:
|
flutter_to_arch: ^1.0.0
|
||||||
git: https://github.com/wgh136/flutter_to_arch
|
|
||||||
flutter_to_debian:
|
flutter_to_debian:
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
Reference in New Issue
Block a user