Merge pull request #191 from venera-app/v1.2.5-dev

V1.2.5
This commit is contained in:
nyne
2025-02-13 11:05:20 +08:00
committed by GitHub
28 changed files with 460 additions and 238 deletions

View File

@@ -496,7 +496,7 @@ let Network = {
/**
* [fetch] function for sending HTTP requests. Same api as the browser fetch.
* @param url {string}
* @param options {{method: string, headers: Object, body: any}}
* @param [options] {{method?: string, headers?: Object, body?: any}}
* @returns {Promise<{ok: boolean, status: number, statusText: string, headers: {}, arrayBuffer: (function(): Promise<ArrayBuffer>), text: (function(): Promise<string>), json: (function(): Promise<any>)}>}
* @since 1.2.0
*/
@@ -921,7 +921,7 @@ function Comic({id, title, subtitle, subTitle, cover, tags, description, maxPage
* @param description {string?}
* @param tags {Map<string, string[]> | {} | null | undefined}
* @param chapters {Map<string, string> | {} | null | undefined} - key: chapter id, value: chapter title
* @param isFavorite {boolean | null | undefined} - favorite status. If the comic source supports multiple folders, this field should be null
* @param isFavorite {boolean | null | undefined} - favorite status.
* @param subId {string?} - a param which is passed to comments api
* @param thumbnails {string[]?} - for multiple page thumbnails, set this to null, and use `loadThumbnails` api to load thumbnails
* @param recommend {Comic[]?} - related comics
@@ -1086,6 +1086,19 @@ class ComicSource {
});
}
translation = {}
/**
* Translate given string with the current locale using the translation object.
* @param key {string}
* @returns {string}
* @since 1.2.5
*/
translate(key) {
let locale = APP.locale;
return this.translation[locale]?.[key] ?? key;
}
init() { }
static sources = {}

View File

@@ -139,8 +139,8 @@
"Block": "屏蔽",
"Add new favorite to": "添加新收藏到",
"Move favorite after reading": "阅读后移动收藏",
"Delete folder?" : "除文件夾?",
"Delete folder '@f' ?" : "删除文件夹 '@f' ",
"Delete folder?" : "除文件夹?",
"Delete folder '@f' ?" : "删除文件夹 '@f' ?",
"Import from file": "从文件导入",
"Failed to import": "导入失败",
"Cache Limit": "缓存限制",
@@ -324,7 +324,15 @@
"Success": "成功",
"Compressing": "压缩中",
"Exporting": "导出中",
"Search Sources": "搜索源"
"Search Sources": "搜索源",
"Removed": "已移除",
"Added to favorites": "已添加到收藏",
"Not added": "未添加",
"Create a folder": "新建收藏夹",
"Created successfully": "创建成功",
"name": "名称",
"Reverse tap to turn Pages": "反转点击翻页",
"Show all": "显示全部"
},
"zh_TW": {
"Home": "首頁",
@@ -651,6 +659,14 @@
"Success": "成功",
"Compressing": "壓縮中",
"Exporting": "匯出中",
"Search Sources": "搜索源"
"Search Sources": "搜索源",
"Removed": "已移除",
"Added to favorites": "已添加到收藏",
"Not added": "未添加",
"Create a folder": "新建收藏夾",
"Created successfully": "創建成功",
"name": "名稱",
"Reverse tap to turn Pages": "反轉點擊翻頁",
"Show all": "顯示全部"
}
}

View File

@@ -742,7 +742,7 @@ class _SliverGridComicsState extends State<SliverGridComics> {
@override
void didUpdateWidget(covariant SliverGridComics oldWidget) {
if (oldWidget.comics != widget.comics) {
if (oldWidget.comics.isEqualTo(widget.comics)) {
comics.clear();
for (var comic in widget.comics) {
if (isBlocked(comic) == null) {

View File

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

View File

@@ -135,6 +135,7 @@ class _Settings with ChangeNotifier {
'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
'readerScreenPicNumber': 1, // 1 - 5
'enableTapToTurnPages': true,
'reverseTapToTurnPages': false,
'enablePageAnimation': true,
'language': 'system', // system, zh-CN, zh-TW, en-US
'cacheSize': 2048, // in MB

View File

@@ -417,7 +417,7 @@ class SearchOptions {
const SearchOptions(this.options, this.label, this.type, this.defaultVal);
String get defaultValue => defaultVal ?? options.keys.first;
String get defaultValue => defaultVal ?? options.keys.firstOrNull ?? "";
}
typedef CategoryComicsLoader = Future<Res<List<Comic>>> Function(

View File

@@ -37,6 +37,8 @@ class FavoriteData {
final AddOrDelFavFunc? addOrDelFavorite;
final bool singleFolderForSingleComic;
const FavoriteData({
required this.key,
required this.title,
@@ -49,6 +51,7 @@ class FavoriteData {
this.allFavoritesId,
this.addOrDelFavorite,
this.isOldToNewSort,
this.singleFolderForSingleComic = false,
});
}

View File

@@ -620,6 +620,7 @@ class ComicSourceParser {
final bool multiFolder = _getValue("favorites.multiFolder");
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
final bool? singleFolderForSingleComic = _getValue("favorites.singleFolderForSingleComic");
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
if (!ComicSource.find(_key!)!.isLogged) {
@@ -773,6 +774,7 @@ class ComicSourceParser {
deleteFolder: deleteFolder,
addOrDelFavorite: addOrDelFavFunc,
isOldToNewSort: isOldToNewSort,
singleFolderForSingleComic: singleFolderForSingleComic ?? false,
);
}

View File

@@ -156,7 +156,7 @@ class JsEngine with _JSEngineApi, JsUiApi {
case "UI":
return handleUIMessage(Map.from(message));
case "getLocale":
return "${App.locale.languageCode}-${App.locale.countryCode}";
return "${App.locale.languageCode}_${App.locale.countryCode}";
case "getPlatform":
return Platform.operatingSystem;
}

View File

@@ -118,7 +118,7 @@ void passCloudflare(CloudflareException e, void Function() onFinished) async {
// windows version of package `flutter_inappwebview` cannot get some cookies
// Using DesktopWebview instead
if (App.isLinux || App.isWindows) {
if (App.isLinux) {
var webview = DesktopWebview(
initialUrl: url,
onTitleChange: (title, controller) async {

View File

@@ -58,7 +58,11 @@ class _AggregatedSearchPageState extends State<AggregatedSearchPage> {
delegate: SliverChildBuilderDelegate(
(context, index) {
final source = sources[index];
return _SliverSearchResult(source: source, keyword: _keyword);
return _SliverSearchResult(
key: ValueKey(source.key),
source: source,
keyword: _keyword,
);
},
childCount: sources.length,
),
@@ -68,7 +72,11 @@ class _AggregatedSearchPageState extends State<AggregatedSearchPage> {
}
class _SliverSearchResult extends StatefulWidget {
const _SliverSearchResult({required this.source, required this.keyword});
const _SliverSearchResult({
required this.source,
required this.keyword,
super.key,
});
final ComicSource source;
@@ -90,6 +98,8 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
List<Comic>? comics;
String? error;
void load() async {
final data = widget.source.searchPageData!;
var options =
@@ -101,6 +111,11 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
comics = res.data;
isLoading = false;
});
} else {
setState(() {
error = res.errorMessage ?? "Unknown error".tl;
isLoading = false;
});
}
} else if (data.loadNext != null) {
var res = await data.loadNext!(widget.keyword, null, options);
@@ -109,6 +124,11 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
comics = res.data;
isLoading = false;
});
} else {
setState(() {
error = res.errorMessage ?? "Unknown error".tl;
isLoading = false;
});
}
}
}
@@ -139,6 +159,9 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
@override
Widget build(BuildContext context) {
if (error != null && error!.startsWith("CloudflareException")) {
error = "Cloudflare verification required".tl;
}
super.build(context);
return InkWell(
onTap: () {
@@ -181,7 +204,7 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
}),
),
)
else if (comics == null || comics!.isEmpty)
else if (error != null || comics == null || comics!.isEmpty)
SizedBox(
height: _kComicHeight,
child: Column(
@@ -190,7 +213,13 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
children: [
const Icon(Icons.error_outline),
const SizedBox(width: 8),
Text("No search results found".tl),
Expanded(
child: Text(
error ?? "No search results found".tl,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
)
],
),
const Spacer(),

View File

@@ -32,7 +32,7 @@ class _CategoriesPageState extends State<CategoriesPage> {
.toList();
categories =
categories.where((element) => allCategories.contains(element)).toList();
if (!categories.isEqualsTo(this.categories)) {
if (!categories.isEqualTo(this.categories)) {
setState(() {
this.categories = categories;
});

View File

@@ -49,19 +49,19 @@ class ComicPage extends StatefulWidget {
class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
with _ComicPageActions {
@override
History? history;
bool showAppbarTitle = false;
var scrollController = ScrollController();
bool isDownloaded = false;
void updateHistory() async {
var newHistory = await HistoryManager()
.find(widget.id, ComicType(widget.sourceKey.hashCode));
if (newHistory?.ep != history?.ep || newHistory?.page != history?.page) {
history = newHistory;
update();
}
@override
void onReadEnd() {
// The history is passed by reference, so it will be updated automatically.
update();
}
@override
@@ -77,14 +77,12 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
@override
void initState() {
scrollController.addListener(onScroll);
HistoryManager().addListener(updateHistory);
super.initState();
}
@override
void dispose() {
scrollController.removeListener(onScroll);
HistoryManager().removeListener(updateHistory);
super.dispose();
}
@@ -552,7 +550,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
if (comic.chapters == null) {
return const SliverPadding(padding: EdgeInsets.zero);
}
return const _ComicChapters();
return _ComicChapters(history);
}
Widget buildThumbnails() {
@@ -594,7 +592,7 @@ abstract mixin class _ComicPageActions {
ComicSource get comicSource => ComicSource.find(comic.sourceKey)!;
History? history;
History? get history;
bool isLiking = false;
@@ -614,8 +612,10 @@ abstract mixin class _ComicPageActions {
update();
}
/// whether the comic is added to local favorite
bool isAddToLocalFav = false;
/// whether the comic is favorite on the server
bool isFavorite = false;
FavoriteItem _toFavoriteItem() {
@@ -686,11 +686,13 @@ abstract mixin class _ComicPageActions {
chapters: comic.chapters,
initialChapter: ep,
initialPage: page,
history: History.fromModel(model: comic, ep: 0, page: 0),
history: history ?? History.fromModel(model: comic, ep: 0, page: 0),
author: comic.findAuthor() ?? '',
tags: comic.plainTags,
),
);
).then((_) {
onReadEnd();
});
}
void continueRead() {
@@ -699,6 +701,8 @@ abstract mixin class _ComicPageActions {
read(ep, page);
}
void onReadEnd();
void download() async {
if (LocalManager().isDownloading(comic.id, comic.comicType)) {
App.rootContext.showMessage(message: "The comic is downloading".tl);
@@ -1081,7 +1085,9 @@ class _ActionButton extends StatelessWidget {
}
class _ComicChapters extends StatefulWidget {
const _ComicChapters();
const _ComicChapters(this.history);
final History? history;
@override
State<_ComicChapters> createState() => _ComicChaptersState();
@@ -1094,104 +1100,133 @@ class _ComicChaptersState extends State<_ComicChapters> {
bool showAll = false;
late History? history;
@override
void initState() {
super.initState();
history = widget.history;
}
@override
void didChangeDependencies() {
state = context.findAncestorStateOfType<_ComicPageState>()!;
super.didChangeDependencies();
}
@override
void didUpdateWidget(covariant _ComicChapters oldWidget) {
super.didUpdateWidget(oldWidget);
setState(() {
history = widget.history;
});
}
@override
Widget build(BuildContext context) {
final eps = state.comic.chapters!;
int length = eps.length;
return SliverLayoutBuilder(
builder: (context, constrains) {
int length = eps.length;
bool canShowAll = showAll;
if (!showAll) {
var width = constrains.crossAxisExtent - 16;
var crossItems = width ~/ 200;
if (width % 200 != 0) {
crossItems += 1;
}
length = math.min(length, crossItems * 8);
if (length == eps.length) {
canShowAll = true;
}
}
if (!showAll) {
length = math.min(length, 20);
}
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(
child: ListTile(
title: Text("Chapters".tl),
trailing: Tooltip(
message: "Order".tl,
child: IconButton(
icon: Icon(reverse
? Icons.vertical_align_top
: Icons.vertical_align_bottom_outlined),
onPressed: () {
setState(() {
reverse = !reverse;
});
},
),
),
),
),
SliverGrid(
delegate:
SliverChildBuilderDelegate(childCount: length, (context, i) {
if (reverse) {
i = eps.length - i - 1;
}
var key = eps.keys.elementAt(i);
var value = eps[key]!;
bool visited =
(state.history?.readEpisode ?? const {}).contains(i + 1);
return Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Material(
color: context.colorScheme.surfaceContainer,
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: InkWell(
onTap: () => state.read(i + 1),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Center(
child: Text(
value,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: visited ? context.colorScheme.outline : null,
),
),
),
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(
child: ListTile(
title: Text("Chapters".tl),
trailing: Tooltip(
message: "Order".tl,
child: IconButton(
icon: Icon(reverse
? Icons.vertical_align_top
: Icons.vertical_align_bottom_outlined),
onPressed: () {
setState(() {
reverse = !reverse;
});
},
),
),
),
);
}),
gridDelegate: const SliverGridDelegateWithFixedHeight(
maxCrossAxisExtent: 200, itemHeight: 48),
).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)),
if (eps.length > 20 && !showAll)
SliverToBoxAdapter(
child: Align(
alignment: Alignment.center,
child: FilledButton.tonal(
style: ButtonStyle(
shape: WidgetStateProperty.all(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)))),
),
onPressed: () {
setState(() {
showAll = true;
});
},
child: Text("${"Show all".tl} (${eps.length})"),
).paddingTop(12),
),
),
const SliverToBoxAdapter(
child: Divider(),
),
],
SliverGrid(
delegate: SliverChildBuilderDelegate(
childCount: length,
(context, i) {
if (reverse) {
i = eps.length - i - 1;
}
var key = eps.keys.elementAt(i);
var value = eps[key]!;
bool visited = (history?.readEpisode ?? {}).contains(i + 1);
return Padding(
padding: const EdgeInsets.fromLTRB(6, 4, 6, 4),
child: Material(
color: context.colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(16),
child: InkWell(
onTap: () => state.read(i + 1),
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Center(
child: Text(
value,
maxLines: 1,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: visited
? context.colorScheme.outline
: null,
),
),
),
),
),
),
);
},
),
gridDelegate: const SliverGridDelegateWithFixedHeight(
maxCrossAxisExtent: 200,
itemHeight: 48,
),
).sliverPadding(const EdgeInsets.symmetric(horizontal: 8)),
if (eps.length > 20 && !canShowAll)
SliverToBoxAdapter(
child: Align(
alignment: Alignment.center,
child: TextButton.icon(
icon: const Icon(Icons.arrow_drop_down),
onPressed: () {
setState(() {
showAll = true;
});
},
label: Text("${"Show all".tl} (${eps.length})"),
).paddingTop(12),
),
),
const SliverToBoxAdapter(
child: Divider(),
),
],
);
},
);
}
}
@@ -1672,6 +1707,42 @@ class _NetworkFavoritesState extends State<_NetworkFavorites> {
}
Widget buildMultiFolder() {
if (widget.isFavorite == true &&
widget.comicSource.favoriteData!.singleFolderForSingleComic) {
return Column(
children: [
Expanded(
child: Center(
child: Text("Added to favorites".tl),
),
),
Center(
child: Button.filled(
isLoading: isLoading,
onPressed: () async {
setState(() {
isLoading = true;
});
var res = await widget.comicSource.favoriteData!
.addOrDelFavorite!(widget.cid, '', false, null);
if (res.success) {
widget.onFavorite(false);
context.pop();
App.rootContext.showMessage(message: "Removed".tl);
} else {
setState(() {
isLoading = false;
});
context.showMessage(message: res.errorMessage!);
}
},
child: Text("Remove".tl),
).paddingVertical(8),
),
],
);
}
if (isLoadingFolders) {
loadFolders();
return const Center(child: CircularProgressIndicator());

View File

@@ -1011,7 +1011,7 @@ class _LoginPageState extends State<_LoginPage> {
if (widget.config.loginWebsite != null)
TextButton(
onPressed: () {
if (App.isWindows || App.isLinux) {
if (App.isLinux) {
loginWithWebview2();
} else {
loginWithWebview();
@@ -1127,7 +1127,7 @@ class _LoginPageState extends State<_LoginPage> {
}
}
// for windows and linux
// for linux
void loginWithWebview2() async {
if (!await DesktopWebview.isAvailable()) {
context.showMessage(message: "Webview is not available".tl);

View File

@@ -37,7 +37,7 @@ class _ExplorePageState extends State<ExplorePage>
.expand((e) => e.map((e) => e.title))
.toList();
explorePages = explorePages.where((e) => all.contains(e)).toList();
if (!pages.isEqualsTo(explorePages)) {
if (!pages.isEqualTo(explorePages)) {
setState(() {
pages = explorePages;
controller = TabController(

View File

@@ -476,55 +476,47 @@ class _CreateFolderDialogState extends State<_CreateFolderDialog> {
@override
Widget build(BuildContext context) {
return SimpleDialog(
title: Text("Create a folder".tl),
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextField(
controller: controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: "name".tl,
),
),
),
const SizedBox(
width: 200,
height: 10,
),
if (loading)
Center(
child: const CircularProgressIndicator(
strokeWidth: 2,
).fixWidth(24).fixHeight(24),
)
else
SizedBox(
height: 35,
child: Center(
child: TextButton(
onPressed: () {
setState(() {
loading = true;
});
widget.data.addFolder!(controller.text).then((b) {
if (b.error) {
context.showMessage(message: b.errorMessage!);
setState(() {
loading = false;
});
} else {
context.pop();
context.showMessage(message: "Created successfully".tl);
widget.updateState();
}
});
},
child: Text("Submit".tl),
return ContentDialog(
title: "Create a folder".tl,
content: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
child: TextField(
controller: controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: "name".tl,
),
),
)
),
const SizedBox(
height: 16
),
],
),
actions: [
Button.filled(
isLoading: loading,
onPressed: () {
setState(() {
loading = true;
});
widget.data.addFolder!(controller.text).then((b) {
if (b.error) {
context.showMessage(message: b.errorMessage!);
setState(() {
loading = false;
});
} else {
context.pop();
context.showMessage(message: "Created successfully".tl);
widget.updateState();
}
});
},
child: Text("Submit".tl),
)
],
);
}

View File

@@ -24,6 +24,8 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
int fingers = 0;
late _ReaderState reader;
@override
void initState() {
_tapGestureRecognizer = TapGestureRecognizer()
@@ -33,6 +35,7 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
};
super.initState();
context.readerScaffold._gestureDetectorState = this;
reader = context.reader;
}
@override
@@ -166,7 +169,9 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
}
void onTap(Offset location) {
if (context.readerScaffold.isOpen) {
if (reader._imageViewController!.handleOnTap(location)) {
return;
} else if (context.readerScaffold.isOpen) {
context.readerScaffold.openOrClose();
} else {
if (appdata.settings['enableTapToTurnPages']) {
@@ -186,31 +191,37 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
isBottom = true;
}
bool isCenter = false;
var prev = context.reader.toPrevPage;
var next = context.reader.toNextPage;
if (appdata.settings['reverseTapToTurnPages']) {
prev = context.reader.toNextPage;
next = context.reader.toPrevPage;
}
switch (context.reader.mode) {
case ReaderMode.galleryLeftToRight:
case ReaderMode.continuousLeftToRight:
if (isLeft) {
context.reader.toPrevPage();
prev();
} else if (isRight) {
context.reader.toNextPage();
next();
} else {
isCenter = true;
}
case ReaderMode.galleryRightToLeft:
case ReaderMode.continuousRightToLeft:
if (isLeft) {
context.reader.toNextPage();
next();
} else if (isRight) {
context.reader.toPrevPage();
prev();
} else {
isCenter = true;
}
case ReaderMode.galleryTopToBottom:
case ReaderMode.continuousTopToBottom:
if (isTop) {
context.reader.toPrevPage();
prev();
} else if (isBottom) {
context.reader.toNextPage();
next();
} else {
isCenter = true;
}

View File

@@ -335,6 +335,11 @@ class _GalleryModeState extends State<_GalleryMode>
}
}
}
@override
bool handleOnTap(Offset location) {
return false;
}
}
const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
@@ -366,6 +371,18 @@ class _ContinuousModeState extends State<_ContinuousMode>
var fingers = 0;
bool disableScroll = false;
/// Whether the user was scrolling the page.
/// The gesture detector has a delay to detect tap event.
/// To handle the tap event, we need to know if the user was scrolling before the delay.
bool delayedIsScrolling = false;
void delayedSetIsScrolling(bool value) {
Future.delayed(
const Duration(milliseconds: 300),
() => delayedIsScrolling = value,
);
}
@override
void initState() {
reader = context.reader;
@@ -374,6 +391,12 @@ class _ContinuousModeState extends State<_ContinuousMode>
super.initState();
}
@override
void dispose() {
itemPositionsListener.itemPositions.removeListener(onPositionChanged);
super.dispose();
}
void onPositionChanged() {
var page = itemPositionsListener.itemPositions.value.first.index;
page = page.clamp(1, reader.maxPage);
@@ -489,6 +512,14 @@ class _ContinuousModeState extends State<_ContinuousMode>
});
}
},
onPointerCancel: (event) {
fingers--;
if (fingers <= 1 && disableScroll) {
setState(() {
disableScroll = false;
});
}
},
onPointerPanZoomUpdate: (event) {
if (event.scale == 1.0) {
smoothTo(0 - event.panDelta.dy);
@@ -516,8 +547,14 @@ class _ContinuousModeState extends State<_ContinuousMode>
child: widget,
);
widget = NotificationListener<ScrollUpdateNotification>(
widget = NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollStartNotification) {
delayedSetIsScrolling(true);
} else if (notification is ScrollEndNotification) {
delayedSetIsScrolling(false);
}
var length = reader.maxChapter;
if (!scrollController.hasClients) return false;
if (scrollController.position.pixels <=
@@ -592,7 +629,7 @@ class _ContinuousModeState extends State<_ContinuousMode>
@override
void handleLongPressDown(Offset location) {
if (!appdata.settings['enableLongPressToZoom']) {
if (!appdata.settings['enableLongPressToZoom'] || delayedIsScrolling) {
return;
}
double target = photoViewController.getInitialScale!.call()! * 1.75;
@@ -667,6 +704,14 @@ class _ContinuousModeState extends State<_ContinuousMode>
);
}
}
@override
bool handleOnTap(Offset location) {
if (delayedIsScrolling) {
return true;
}
return false;
}
}
ImageProvider _createImageProviderFromKey(

View File

@@ -237,6 +237,7 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
history!.maxPage = maxPage;
}
history!.readEpisode.add(chapter);
print(history!.readEpisode);
history!.time = DateTime.now();
HistoryManager().addHistory(history!);
}
@@ -430,4 +431,7 @@ abstract interface class _ImageViewController {
void handleLongPressUp(Offset location);
void handleKeyEvent(KeyEvent event);
/// Returns true if the event is handled.
bool handleOnTap(Offset location);
}

View File

@@ -660,12 +660,16 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
App.rootContext.pop();
},
child: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Theme.of(context).colorScheme.outline,
),
),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
),
width: double.infinity,
height: double.infinity,
child: Image(

View File

@@ -189,7 +189,7 @@ class _SearchPageState extends State<SearchPage> {
void updateSearchSourcesIfNeeded() {
var old = searchSources;
findSearchSources();
if (old.isEqualsTo(searchSources)) {
if (old.isEqualTo(searchSources)) {
return;
}
setState(() {});

View File

@@ -196,7 +196,7 @@ class _SearchResultPageState extends State<SearchResultPage> {
return _SearchSettingsDialog(state: this);
},
);
if (!previousOptions.isEqualsTo(options) ||
if (!previousOptions.isEqualTo(options) ||
previousSourceKey != sourceKey) {
text = checkAutoLanguage(controller.text);
controller.currentText = text;

View File

@@ -22,6 +22,13 @@ class _ReaderSettingsState extends State<ReaderSettings> {
widget.onChanged?.call("enableTapToTurnPages");
},
).toSliver(),
_SwitchSetting(
title: "Reverse tap to turn Pages".tl,
settingKey: "reverseTapToTurnPages",
onChanged: () {
widget.onChanged?.call("reverseTapToTurnPages");
},
).toSliver(),
_SwitchSetting(
title: "Page animation".tl,
settingKey: "enablePageAnimation",

View File

@@ -25,8 +25,13 @@ extension WebviewExtension on InAppWebViewController {
if (url[url.length - 1] == '/') {
url = url.substring(0, url.length - 1);
}
CookieManager cookieManager = CookieManager.instance();
final cookies = await cookieManager.getCookies(url: WebUri(url));
CookieManager cookieManager = CookieManager.instance(
webViewEnvironment: AppWebview.webViewEnvironment,
);
final cookies = await cookieManager.getCookies(
url: WebUri(url),
webViewController: this,
);
var res = <io.Cookie>[];
for (var cookie in cookies) {
var c = io.Cookie(cookie.name, cookie.value);
@@ -90,7 +95,8 @@ class _AppWebviewState extends State<AppWebview> {
var proxy = appdata.settings['proxy'].toString();
if (proxy != "system" && proxy != "direct") {
var proxyAvailable = await WebViewFeature.isFeatureSupported(
WebViewFeature.PROXY_OVERRIDE);
WebViewFeature.PROXY_OVERRIDE,
);
if (proxyAvailable) {
ProxyController proxyController = ProxyController.instance();
await proxyController.clearProxyOverride();
@@ -147,22 +153,21 @@ class _AppWebviewState extends State<AppWebview> {
)
];
Widget body = (App.isWindows && AppWebview.webViewEnvironment == null)
? FutureBuilder(
future: future,
builder: (context, e) {
if (e.error != null) {
return Center(child: Text("Error: ${e.error}"));
}
if (e.data == null) {
return const Center(child: CircularProgressIndicator());
}
AppWebview.webViewEnvironment = e.data;
return createWebviewWithEnvironment(
AppWebview.webViewEnvironment);
},
)
: createWebviewWithEnvironment(AppWebview.webViewEnvironment);
Widget body = FutureBuilder(
future: future,
builder: (context, e) {
if (e.error != null) {
return Center(child: Text("Error: ${e.error}"));
}
if (e.data == null) {
return const SizedBox();
}
AppWebview.webViewEnvironment = e.data;
return createWebviewWithEnvironment(
AppWebview.webViewEnvironment,
);
},
);
body = Stack(
children: [

View File

@@ -115,7 +115,17 @@ abstract class CBZ {
cache.deleteSync(recursive: true);
throw Exception('No images found in the archive');
}
files.sort((a, b) => a.path.compareTo(b.path));
files.sort((a, b) {
var aName = a.basenameWithoutExt;
var bName = b.basenameWithoutExt;
var aIndex = int.tryParse(aName);
var bIndex = int.tryParse(bName);
if (aIndex != null && bIndex != null) {
return aIndex.compareTo(bIndex);
} else {
return a.path.compareTo(b.path);
}
});
var coverFile = files.firstWhereOrNull(
(element) =>
element.path.endsWith('cover.${element.path.split('.').last}'),

View File

@@ -25,7 +25,9 @@ extension ListExt<T> on List<T>{
}
}
bool isEqualsTo(List<T> list){
/// Compare every element of this list with another list.
/// Return true if all elements are equal.
bool isEqualTo(List<T> list){
if(length != list.length){
return false;
}
@@ -81,10 +83,6 @@ extension StringExt on String{
return '$before$to$after';
}
static bool hasMatch(String? value, String pattern) {
return (value == null) ? false : RegExp(pattern).hasMatch(value);
}
bool _isURL(){
final regex = RegExp(
r'^((http|https|ftp)://)[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-|]*[\w@?^=%&/~+#-])?$',

View File

@@ -307,19 +307,21 @@ packages:
flutter_inappwebview:
dependency: "direct main"
description:
name: flutter_inappwebview
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
path: flutter_inappwebview
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
source: git
version: "6.2.0-beta.3"
flutter_inappwebview_android:
dependency: transitive
description:
name: flutter_inappwebview_android
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
path: flutter_inappwebview_android
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_internal_annotations:
dependency: transitive
description:
@@ -331,43 +333,48 @@ packages:
flutter_inappwebview_ios:
dependency: transitive
description:
name: flutter_inappwebview_ios
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
path: flutter_inappwebview_ios
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_macos:
dependency: transitive
description:
name: flutter_inappwebview_macos
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
url: "https://pub.dev"
source: hosted
version: "1.1.2"
path: flutter_inappwebview_macos
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_platform_interface:
dependency: transitive
description:
name: flutter_inappwebview_platform_interface
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
url: "https://pub.dev"
source: hosted
version: "1.3.0+1"
path: flutter_inappwebview_platform_interface
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
source: git
version: "1.4.0-beta.3"
flutter_inappwebview_web:
dependency: transitive
description:
name: flutter_inappwebview_web
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
path: flutter_inappwebview_web
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
source: git
version: "1.2.0-beta.3"
flutter_inappwebview_windows:
dependency: transitive
description:
name: flutter_inappwebview_windows
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
path: flutter_inappwebview_windows
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
source: git
version: "0.7.0-beta.3"
flutter_lints:
dependency: "direct dev"
description:

View File

@@ -2,7 +2,7 @@ name: venera
description: "A comic app."
publish_to: 'none'
version: 1.2.4+124
version: 1.2.5+125
environment:
sdk: '>=3.6.0 <4.0.0'
@@ -43,7 +43,11 @@ dependencies:
git:
url: https://github.com/wgh136/flutter_desktop_webview
path: packages/desktop_webview_window
flutter_inappwebview: ^6.1.5
flutter_inappwebview:
git:
url: https://github.com/pichillilorenzo/flutter_inappwebview
path: flutter_inappwebview
ref: 0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676
app_links: ^6.3.3
sliver_tools: ^0.2.12
flutter_file_dialog: ^3.0.2