Merge pull request #104 from venera-app/dev

v1.1.2
This commit is contained in:
nyne
2024-12-18 20:11:11 +08:00
committed by GitHub
32 changed files with 484 additions and 234 deletions

View File

@@ -53,6 +53,7 @@
<meta-data
android:name="flutterEmbedding"
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"/>
</application>
<!-- Required to query activities that can process text, see:

View File

@@ -251,7 +251,14 @@
"Aggregated Search": "聚合搜索",
"No search results found": "未找到搜索结果",
"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": {
"Home": "首頁",
@@ -505,6 +512,13 @@
"Aggregated Search": "聚合搜索",
"No search results found": "未找到搜索結果",
"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" : "未找到有效的漫畫"
}
}

View File

@@ -1,5 +1,5 @@
# 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.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@@ -77,7 +77,7 @@ class ComicTile extends StatelessWidget {
icon: Icons.stars_outlined,
text: 'Add to favorites'.tl,
onClick: () {
addFavorite(comic);
addFavorite([comic]);
},
),
MenuEntry(

View File

@@ -41,13 +41,29 @@ class AnimatedTapRegion extends StatefulWidget {
}
class _AnimatedTapRegionState extends State<AnimatedTapRegion> {
bool isScaled = false;
bool isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => setState(() => isHovered = true),
onExit: (_) => setState(() => isHovered = false),
onEnter: (_) {
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(
onTap: widget.onTap,
child: ClipRRect(
@@ -55,7 +71,7 @@ class _AnimatedTapRegionState extends State<AnimatedTapRegion> {
clipBehavior: Clip.antiAlias,
child: AnimatedScale(
duration: _fastAnimationDuration,
scale: isHovered ? 1.1 : 1,
scale: isScaled ? 1.1 : 1,
child: widget.child,
),
),

View File

@@ -22,8 +22,15 @@ class PopUpWidget<T> extends PopupRoute<T> {
Widget body = PopupIndicatorWidget(
child: Container(
decoration: showPopUp
? const BoxDecoration(
? BoxDecoration(
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,
clipBehavior: showPopUp ? Clip.antiAlias : Clip.none,
@@ -86,7 +93,8 @@ class PopupIndicatorWidget extends InheritedWidget {
}
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 {
@@ -127,9 +135,8 @@ class _PopUpWidgetScaffoldState extends State<PopUpWidgetScaffold> {
message: "Back".tl,
child: IconButton(
icon: const Icon(Icons.arrow_back_sharp),
onPressed: () => context.canPop()
? context.pop()
: App.pop(),
onPressed: () =>
context.canPop() ? context.pop() : App.pop(),
),
),
const SizedBox(

View File

@@ -60,7 +60,15 @@ class SideBarRoute<T> extends PopupRoute<T> {
borderRadius: showSideBar
? const BorderRadius.horizontal(left: Radius.circular(16))
: 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,
constraints: BoxConstraints(maxWidth: sideBarWidth),
height: MediaQuery.of(context).size.height,

View File

@@ -10,7 +10,7 @@ export "widget_utils.dart";
export "context.dart";
class _App {
final version = "1.1.1";
final version = "1.1.2";
bool get isAndroid => Platform.isAndroid;

View File

@@ -122,6 +122,7 @@ class _Settings with ChangeNotifier {
'enableClockAndBatteryInfoInReader': true,
'ignoreCertificateErrors': false,
'authorizationRequired': false,
'onClickFavorite': 'viewDetail', // viewDetail, read
};
operator [](String key) {

View File

@@ -145,7 +145,7 @@ class ComicDetails with HistoryMixin {
final int? likesCount;
final int? commentsCount;
final int? commentCount;
final String? uploader;
@@ -189,7 +189,7 @@ class ComicDetails with HistoryMixin {
subId = json["subId"],
likesCount = json["likesCount"],
isLiked = json["isLiked"],
commentsCount = json["commentsCount"],
commentCount = json["commentCount"],
uploader = json["uploader"],
uploadTime = json["uploadTime"],
updateTime = json["updateTime"],
@@ -216,7 +216,7 @@ class ComicDetails with HistoryMixin {
"subId": subId,
"isLiked": isLiked,
"likesCount": likesCount,
"commentsCount": commentsCount,
"commentsCount": commentCount,
"uploader": uploader,
"uploadTime": uploadTime,
"updateTime": updateTime,

View File

@@ -28,4 +28,12 @@ class ComicType {
}
static const local = ComicType(0);
factory ComicType.fromKey(String key) {
if(key == "local") {
return local;
} else {
return ComicType(key.hashCode);
}
}
}

View File

@@ -27,10 +27,8 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
screen.size.height * _normalComicImageRatio,
);
} else {
_effectiveScreenWidth = max(
_effectiveScreenWidth ?? 0,
screen.size.width
);
_effectiveScreenWidth =
max(_effectiveScreenWidth ?? 0, screen.size.width);
}
}
if (_effectiveScreenWidth! < _minComicImageWidth) {
@@ -110,7 +108,10 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
try {
final buffer = await ImmutableBuffer.fromUint8List(data);
return await decode(buffer, getTargetSize: _getTargetSize);
return await decode(
buffer,
getTargetSize: enableResize ? _getTargetSize : null,
);
} catch (e) {
await CacheManager().delete(this.key);
if (data.length < 2 * 1024) {
@@ -151,6 +152,8 @@ abstract class BaseImageProvider<T extends BaseImageProvider<T>>
String toString() {
return "$runtimeType($key)";
}
bool get enableResize => false;
}
typedef FileDecoderCallback = Future<ui.Codec> Function(Uint8List);

View File

@@ -49,4 +49,7 @@ class ReaderImageProvider
@override
String get key => "$imageKey@$sourceKey@$cid@$eid";
@override
bool get enableResize => true;
}

View File

@@ -474,6 +474,7 @@ class LocalManager with ChangeNotifier {
dir.deleteIgnoreError(recursive: true);
}
// 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) {
HistoryManager().remove(c.id, c.comicType);
}
@@ -481,6 +482,7 @@ class LocalManager with ChangeNotifier {
for (var f in folders) {
LocalFavoritesManager().deleteComicWithId(f, c.id, c.comicType);
}
}
remove(c.id, c.comicType);
notifyListeners();
}

View File

@@ -146,14 +146,19 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
String? _cover;
/// All images to download, key is chapter name
Map<String, List<String>>? _images;
/// Downloaded image count
int _downloadedCount = 0;
/// Total image count
int _totalCount = 0;
/// Current downloading image index
int _index = 0;
/// Current downloading chapter, index of [_images]
int _chapter = 0;
var tasks = <int, _ImageDownloadWrapper>{};
@@ -180,10 +185,10 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
if (comic!.chapters != null) {
saveTo = Directory(FilePath.join(
path!,
comic!.chapters!.keys.elementAt(_chapter),
_images!.keys.elementAt(_chapter),
));
if (!saveTo.existsSync()) {
saveTo.createSync();
saveTo.createSync(recursive: true);
}
} else {
saveTo = Directory(path!);

View File

@@ -172,7 +172,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
isLiked = comic.isLiked ?? false;
isFavorite = comic.isFavorite ?? false;
if (comic.chapters == null) {
isDownloaded = await LocalManager().isDownloaded(
isDownloaded = LocalManager().isDownloaded(
comic.id,
comic.comicType,
0,
@@ -292,7 +292,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
if (comicSource.commentsLoader != null)
_ActionButton(
icon: const Icon(Icons.comment),
text: (comic.commentsCount ?? 'Comments'.tl).toString(),
text: (comic.commentCount ?? 'Comments'.tl).toString(),
onPressed: showComments,
iconColor: context.useTextColor(Colors.green),
),
@@ -679,7 +679,7 @@ abstract mixin class _ComicPageActions {
return;
}
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);
return;
}

View File

@@ -77,7 +77,7 @@ String? validateFolderName(String newFolderName) {
return null;
}
void addFavorite(Comic comic) {
void addFavorite(List<Comic> comics) {
var folders = LocalFavoritesManager().folderNames;
showDialog(
@@ -105,6 +105,7 @@ void addFavorite(Comic comic) {
FilledButton(
onPressed: () {
if (selectedFolder != null) {
for (var comic in comics) {
LocalFavoritesManager().addComic(
selectedFolder!,
FavoriteItem(
@@ -118,6 +119,7 @@ void addFavorite(Comic comic) {
tags: comic.tags ?? [],
),
);
}
context.pop();
}
},

View File

@@ -14,6 +14,7 @@ import 'package:venera/foundation/local.dart';
import 'package:venera/foundation/res.dart';
import 'package:venera/network/download.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/translations.dart';

View File

@@ -389,8 +389,8 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
),
];
},
onTap: multiSelectMode
? (c) {
onTap: (c) {
if (multiSelectMode) {
setState(() {
if (selectedComics.containsKey(c as FavoriteItem)) {
selectedComics.remove(c);
@@ -400,10 +400,17 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
}
lastSelectedIndex = comics.indexOf(c);
});
}
: (c) {
} else if (appdata.settings["onClickFavorite"] == "viewDetail") {
App.mainNavigatorKey?.currentContext
?.to(() => ComicPage(id: c.id, sourceKey: c.sourceKey));
} else {
App.mainNavigatorKey?.currentContext?.to(
() => ReaderWithLoading(
id: c.id,
sourceKey: c.sourceKey,
),
);
}
},
onLongPressed: (c) {
setState(() {

View File

@@ -264,7 +264,8 @@ class _HistoryState extends State<_History> {
scrollDirection: Axis.horizontal,
itemCount: history.length,
itemBuilder: (context, index) {
return InkWell(
return AnimatedTapRegion(
borderRadius: 8,
onTap: () {
context.to(
() => ComicPage(
@@ -273,11 +274,9 @@ class _HistoryState extends State<_History> {
),
);
},
borderRadius: BorderRadius.circular(8),
child: Container(
width: 92,
height: 114,
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context)
@@ -293,7 +292,7 @@ class _HistoryState extends State<_History> {
filterQuality: FilterQuality.medium,
),
),
);
).paddingHorizontal(8);
},
),
).paddingHorizontal(8).paddingBottom(16),
@@ -386,15 +385,14 @@ class _LocalState extends State<_Local> {
scrollDirection: Axis.horizontal,
itemCount: local.length,
itemBuilder: (context, index) {
return InkWell(
return AnimatedTapRegion(
onTap: () {
local[index].read();
},
borderRadius: BorderRadius.circular(8),
borderRadius: 8,
child: Container(
width: 92,
height: 114,
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context)
@@ -412,7 +410,7 @@ class _LocalState extends State<_Local> {
filterQuality: FilterQuality.medium,
),
),
);
).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 directories.".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
][type];
List<String> importMethods = [
"Single Comic".tl,
"Multiple Comics".tl,
"A cbz file".tl,
"Multiple cbz files".tl,
"EhViewer downloads".tl
];
@@ -630,7 +630,8 @@ class _ImportComicsWidgetState extends State<_ImportComicsWidget> {
0 => await importer.directory(true),
1 => await importer.directory(false),
2 => await importer.cbz(),
3 => await importer.ehViewer(),
3 => await importer.multipleCbz(),
4 => await importer.ehViewer(),
int() => true,
};
if(result) {

View File

@@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
import 'package:venera/components/components.dart';
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/local.dart';
import 'package:venera/foundation/log.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/epub.dart';
import 'package:venera/utils/io.dart';
@@ -30,7 +30,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
bool multiSelectMode = false;
Map<Comic, bool> selectedComics = {};
Map<LocalComic, bool> selectedComics = {};
void update() {
if (keyword.isEmpty) {
@@ -117,8 +117,32 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
);
}
@override
Widget build(BuildContext context) {
Widget buildMultiSelectMenu() {
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() {
setState(() {
selectedComics = comics.asMap().map((k, v) => MapEntry(v, true));
@@ -140,25 +164,8 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
});
}
void selectRange() {
setState(() {
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);
}));
});
}
@override
Widget build(BuildContext context) {
List<Widget> selectActions = [
IconButton(
icon: const Icon(Icons.select_all),
@@ -172,10 +179,7 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
icon: const Icon(Icons.flip),
tooltip: "Invert Selection".tl,
onPressed: invertSelection),
IconButton(
icon: const Icon(Icons.border_horizontal_outlined),
tooltip: "Select in range".tl,
onPressed: selectRange),
buildMultiSelectMenu(),
];
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)
@@ -275,18 +266,27 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
SliverGridComics(
comics: comics,
selections: selectedComics,
onTap: multiSelectMode
? (c) {
onLongPressed: (c) {
setState(() {
multiSelectMode = true;
selectedComics[c as LocalComic] = true;
});
},
onTap: (c) {
if(multiSelectMode) {
setState(() {
if (selectedComics.containsKey(c as LocalComic)) {
selectedComics.remove(c);
} else {
selectedComics[c] = true;
}
});
if(selectedComics.isEmpty) {
multiSelectMode = false;
}
: (c) {
});
} else {
(c as LocalComic).read();
}
},
menuBuilder: (c) {
return [
@@ -294,45 +294,13 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
icon: Icons.delete,
text: "Delete".tl,
onClick: () {
showDialog(
context: context,
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);
}
deleteComics([c as LocalComic]).then((value) {
if (value && multiSelectMode) {
setState(() {
multiSelectMode = false;
selectedComics.clear();
});
} else {
LocalManager().deleteComic(
c as LocalComic, removeComicFile);
}
},
child: Text("Confirm".tl),
),
],
);
});
});
}),
MenuEntry(
@@ -344,26 +312,14 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
allowCancel: false,
);
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);
await saveFile(filename: file.name, file: file);
await file.delete();
}
} catch (e) {
context.showMessage(message: e.toString());
}
controller.close();
}),
if (!multiSelectMode)
MenuEntry(
icon: Icons.picture_as_pdf_outlined,
text: "Export as pdf".tl,
@@ -391,7 +347,6 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
}
},
),
if (!multiSelectMode)
MenuEntry(
icon: Icons.import_contacts_outlined,
text: "Export as epub".tl,
@@ -444,4 +399,44 @@ class _LocalComicsPageState extends State<LocalComicsPage> {
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;
}
}

View File

@@ -25,7 +25,7 @@ class _ReaderImagesState extends State<_ReaderImages> {
if (inProgress) return;
inProgress = true;
if (reader.type == ComicType.local ||
(await LocalManager()
(LocalManager()
.isDownloaded(reader.cid, reader.type, reader.chapter))) {
try {
var images = await LocalManager()

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

View File

@@ -18,11 +18,13 @@ import 'package:venera/components/custom_slider.dart';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/appdata.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/history.dart';
import 'package:venera/foundation/image_provider/reader_image.dart';
import 'package:venera/foundation/local.dart';
import 'package:venera/foundation/log.dart';
import 'package:venera/foundation/res.dart';
import 'package:venera/pages/settings/settings_page.dart';
import 'package:venera/utils/data_sync.dart';
import 'package:venera/utils/file_type.dart';
@@ -36,6 +38,7 @@ part 'scaffold.dart';
part 'images.dart';
part 'gesture.dart';
part 'comic_image.dart';
part 'loading.dart';
extension _ReaderContext on BuildContext {
_ReaderState get reader => findAncestorStateOfType<_ReaderState>()!;

View File

@@ -16,18 +16,18 @@ class _LocalFavoritesSettingsState extends State<LocalFavoritesSettings> {
SelectSetting(
title: "Add new favorite to".tl,
settingKey: "newFavoriteAddTo",
optionTranslation: const {
"start": "Start",
"end": "End",
optionTranslation: {
"start": "Start".tl,
"end": "End".tl,
},
).toSliver(),
SelectSetting(
title: "Move favorite after reading".tl,
settingKey: "moveFavoriteAfterRead",
optionTranslation: const {
"none": "None",
"end": "End",
"start": "Start",
optionTranslation: {
"none": "None".tl,
"end": "End".tl,
"start": "Start".tl,
},
).toSliver(),
SelectSetting(
@@ -48,6 +48,14 @@ class _LocalFavoritesSettingsState extends State<LocalFavoritesSettings> {
},
actionTitle: 'Delete'.tl,
).toSliver(),
SelectSetting(
title: "Click favorite".tl,
settingKey: "onClickFavorite",
optionTranslation: {
"viewDetail": "View Detail".tl,
"read": "Read".tl,
},
).toSliver(),
],
);
}

View File

@@ -1,5 +1,4 @@
import 'dart:convert';
import 'dart:isolate';
import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/comic_type.dart';
@@ -63,7 +62,7 @@ abstract class CBZ {
var cache = Directory(FilePath.join(App.cachePath, 'cbz_import'));
if (cache.existsSync()) cache.deleteSync(recursive: true);
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'));
ComicMetaData? metaData;
if (metaDataFile.existsSync()) {
@@ -208,13 +207,13 @@ abstract class CBZ {
).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);
cache.deleteSync(recursive: true);
return cbz;
}
static _compress(String src, String dst) async {
await Isolate.run(() => ZipFile.compressFolder(src, dst));
await ZipFile.compressFolderAsync(src, dst, 4);
}
}

View File

@@ -37,6 +37,33 @@ class ImportComic {
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 {
var dbFile = await selectFile(ext: ['db']);
final picker = DirectoryPicker();

View File

@@ -72,6 +72,7 @@ extension FileSystemEntityExt on FileSystemEntity {
}
extension FileExtension on File {
/// Get the file extension, not including the dot.
String get extension => path.split('.').last;
/// Copy the file to the specified path using memory.

43
macos/Podfile Normal file
View 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

View File

@@ -557,7 +557,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -639,7 +639,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@@ -689,7 +689,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;

View File

@@ -446,11 +446,10 @@ packages:
flutter_to_arch:
dependency: "direct dev"
description:
path: "."
ref: HEAD
resolved-ref: "15bfead0380fda79b0256b37c73b886b0882f1bf"
url: "https://github.com/wgh136/flutter_to_arch"
source: git
name: flutter_to_arch
sha256: "656cffc182b05af38aa96a1115931620b8865c4b0cfe00813b26fcff0875f2ab"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flutter_to_debian:
dependency: "direct dev"
@@ -1125,12 +1124,11 @@ packages:
zip_flutter:
dependency: "direct main"
description:
path: "."
ref: "7fb4e6ad7662f787f2c248b01f89ccbf8e8ab5cc"
resolved-ref: "7fb4e6ad7662f787f2c248b01f89ccbf8e8ab5cc"
url: "https://github.com/wgh136/zip_flutter"
source: git
version: "0.0.4"
name: zip_flutter
sha256: "955b53d58709fcd9feefbed3d41b5522bc5273e677603e9fc67017a81e568d24"
url: "https://pub.dev"
source: hosted
version: "0.0.5"
sdks:
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.27.1"

View File

@@ -2,7 +2,7 @@ name: venera
description: "A comic app."
publish_to: 'none'
version: 1.1.1+111
version: 1.1.2+112
environment:
sdk: '>=3.6.0 <4.0.0'
@@ -51,10 +51,7 @@ dependencies:
sliver_tools: ^0.2.12
flutter_file_dialog: ^3.0.2
file_selector: ^1.0.3
zip_flutter:
git:
url: https://github.com/wgh136/zip_flutter
ref: 7fb4e6ad7662f787f2c248b01f89ccbf8e8ab5cc
zip_flutter: ^0.0.5
lodepng_flutter:
git:
url: https://github.com/venera-app/lodepng_flutter
@@ -79,8 +76,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter_to_arch:
git: https://github.com/wgh136/flutter_to_arch
flutter_to_arch: ^1.0.0
flutter_to_debian:
flutter: