mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
@@ -496,7 +496,7 @@ let Network = {
|
|||||||
/**
|
/**
|
||||||
* [fetch] function for sending HTTP requests. Same api as the browser fetch.
|
* [fetch] function for sending HTTP requests. Same api as the browser fetch.
|
||||||
* @param url {string}
|
* @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>)}>}
|
* @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
|
* @since 1.2.0
|
||||||
*/
|
*/
|
||||||
@@ -921,7 +921,7 @@ function Comic({id, title, subtitle, subTitle, cover, tags, description, maxPage
|
|||||||
* @param description {string?}
|
* @param description {string?}
|
||||||
* @param tags {Map<string, string[]> | {} | null | undefined}
|
* @param tags {Map<string, string[]> | {} | null | undefined}
|
||||||
* @param chapters {Map<string, string> | {} | null | undefined} - key: chapter id, value: chapter title
|
* @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 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 thumbnails {string[]?} - for multiple page thumbnails, set this to null, and use `loadThumbnails` api to load thumbnails
|
||||||
* @param recommend {Comic[]?} - related comics
|
* @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() { }
|
init() { }
|
||||||
|
|
||||||
static sources = {}
|
static sources = {}
|
||||||
|
@@ -139,8 +139,8 @@
|
|||||||
"Block": "屏蔽",
|
"Block": "屏蔽",
|
||||||
"Add new favorite to": "添加新收藏到",
|
"Add new favorite to": "添加新收藏到",
|
||||||
"Move favorite after reading": "阅读后移动收藏",
|
"Move favorite after reading": "阅读后移动收藏",
|
||||||
"Delete folder?" : "刪除文件夾?",
|
"Delete folder?" : "删除文件夹?",
|
||||||
"Delete folder '@f' ?" : "删除文件夹 '@f' ?",
|
"Delete folder '@f' ?" : "删除文件夹 '@f' ?",
|
||||||
"Import from file": "从文件导入",
|
"Import from file": "从文件导入",
|
||||||
"Failed to import": "导入失败",
|
"Failed to import": "导入失败",
|
||||||
"Cache Limit": "缓存限制",
|
"Cache Limit": "缓存限制",
|
||||||
@@ -324,7 +324,15 @@
|
|||||||
"Success": "成功",
|
"Success": "成功",
|
||||||
"Compressing": "压缩中",
|
"Compressing": "压缩中",
|
||||||
"Exporting": "导出中",
|
"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": {
|
"zh_TW": {
|
||||||
"Home": "首頁",
|
"Home": "首頁",
|
||||||
@@ -651,6 +659,14 @@
|
|||||||
"Success": "成功",
|
"Success": "成功",
|
||||||
"Compressing": "壓縮中",
|
"Compressing": "壓縮中",
|
||||||
"Exporting": "匯出中",
|
"Exporting": "匯出中",
|
||||||
"Search Sources": "搜索源"
|
"Search Sources": "搜索源",
|
||||||
|
"Removed": "已移除",
|
||||||
|
"Added to favorites": "已添加到收藏",
|
||||||
|
"Not added": "未添加",
|
||||||
|
"Create a folder": "新建收藏夾",
|
||||||
|
"Created successfully": "創建成功",
|
||||||
|
"name": "名稱",
|
||||||
|
"Reverse tap to turn Pages": "反轉點擊翻頁",
|
||||||
|
"Show all": "顯示全部"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -742,7 +742,7 @@ class _SliverGridComicsState extends State<SliverGridComics> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant SliverGridComics oldWidget) {
|
void didUpdateWidget(covariant SliverGridComics oldWidget) {
|
||||||
if (oldWidget.comics != widget.comics) {
|
if (oldWidget.comics.isEqualTo(widget.comics)) {
|
||||||
comics.clear();
|
comics.clear();
|
||||||
for (var comic in widget.comics) {
|
for (var comic in widget.comics) {
|
||||||
if (isBlocked(comic) == null) {
|
if (isBlocked(comic) == null) {
|
||||||
|
@@ -10,7 +10,7 @@ export "widget_utils.dart";
|
|||||||
export "context.dart";
|
export "context.dart";
|
||||||
|
|
||||||
class _App {
|
class _App {
|
||||||
final version = "1.2.4";
|
final version = "1.2.5";
|
||||||
|
|
||||||
bool get isAndroid => Platform.isAndroid;
|
bool get isAndroid => Platform.isAndroid;
|
||||||
|
|
||||||
|
@@ -135,6 +135,7 @@ class _Settings with ChangeNotifier {
|
|||||||
'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
|
'readerMode': 'galleryLeftToRight', // values of [ReaderMode]
|
||||||
'readerScreenPicNumber': 1, // 1 - 5
|
'readerScreenPicNumber': 1, // 1 - 5
|
||||||
'enableTapToTurnPages': true,
|
'enableTapToTurnPages': true,
|
||||||
|
'reverseTapToTurnPages': false,
|
||||||
'enablePageAnimation': true,
|
'enablePageAnimation': true,
|
||||||
'language': 'system', // system, zh-CN, zh-TW, en-US
|
'language': 'system', // system, zh-CN, zh-TW, en-US
|
||||||
'cacheSize': 2048, // in MB
|
'cacheSize': 2048, // in MB
|
||||||
|
@@ -417,7 +417,7 @@ class SearchOptions {
|
|||||||
|
|
||||||
const SearchOptions(this.options, this.label, this.type, this.defaultVal);
|
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(
|
typedef CategoryComicsLoader = Future<Res<List<Comic>>> Function(
|
||||||
|
@@ -37,6 +37,8 @@ class FavoriteData {
|
|||||||
|
|
||||||
final AddOrDelFavFunc? addOrDelFavorite;
|
final AddOrDelFavFunc? addOrDelFavorite;
|
||||||
|
|
||||||
|
final bool singleFolderForSingleComic;
|
||||||
|
|
||||||
const FavoriteData({
|
const FavoriteData({
|
||||||
required this.key,
|
required this.key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -49,6 +51,7 @@ class FavoriteData {
|
|||||||
this.allFavoritesId,
|
this.allFavoritesId,
|
||||||
this.addOrDelFavorite,
|
this.addOrDelFavorite,
|
||||||
this.isOldToNewSort,
|
this.isOldToNewSort,
|
||||||
|
this.singleFolderForSingleComic = false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -620,6 +620,7 @@ class ComicSourceParser {
|
|||||||
|
|
||||||
final bool multiFolder = _getValue("favorites.multiFolder");
|
final bool multiFolder = _getValue("favorites.multiFolder");
|
||||||
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
|
final bool? isOldToNewSort = _getValue("favorites.isOldToNewSort");
|
||||||
|
final bool? singleFolderForSingleComic = _getValue("favorites.singleFolderForSingleComic");
|
||||||
|
|
||||||
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
|
Future<Res<T>> retryZone<T>(Future<Res<T>> Function() func) async {
|
||||||
if (!ComicSource.find(_key!)!.isLogged) {
|
if (!ComicSource.find(_key!)!.isLogged) {
|
||||||
@@ -773,6 +774,7 @@ class ComicSourceParser {
|
|||||||
deleteFolder: deleteFolder,
|
deleteFolder: deleteFolder,
|
||||||
addOrDelFavorite: addOrDelFavFunc,
|
addOrDelFavorite: addOrDelFavFunc,
|
||||||
isOldToNewSort: isOldToNewSort,
|
isOldToNewSort: isOldToNewSort,
|
||||||
|
singleFolderForSingleComic: singleFolderForSingleComic ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -156,7 +156,7 @@ class JsEngine with _JSEngineApi, JsUiApi {
|
|||||||
case "UI":
|
case "UI":
|
||||||
return handleUIMessage(Map.from(message));
|
return handleUIMessage(Map.from(message));
|
||||||
case "getLocale":
|
case "getLocale":
|
||||||
return "${App.locale.languageCode}-${App.locale.countryCode}";
|
return "${App.locale.languageCode}_${App.locale.countryCode}";
|
||||||
case "getPlatform":
|
case "getPlatform":
|
||||||
return Platform.operatingSystem;
|
return Platform.operatingSystem;
|
||||||
}
|
}
|
||||||
|
@@ -118,7 +118,7 @@ void passCloudflare(CloudflareException e, void Function() onFinished) async {
|
|||||||
|
|
||||||
// windows version of package `flutter_inappwebview` cannot get some cookies
|
// windows version of package `flutter_inappwebview` cannot get some cookies
|
||||||
// Using DesktopWebview instead
|
// Using DesktopWebview instead
|
||||||
if (App.isLinux || App.isWindows) {
|
if (App.isLinux) {
|
||||||
var webview = DesktopWebview(
|
var webview = DesktopWebview(
|
||||||
initialUrl: url,
|
initialUrl: url,
|
||||||
onTitleChange: (title, controller) async {
|
onTitleChange: (title, controller) async {
|
||||||
|
@@ -58,7 +58,11 @@ class _AggregatedSearchPageState extends State<AggregatedSearchPage> {
|
|||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(context, index) {
|
(context, index) {
|
||||||
final source = sources[index];
|
final source = sources[index];
|
||||||
return _SliverSearchResult(source: source, keyword: _keyword);
|
return _SliverSearchResult(
|
||||||
|
key: ValueKey(source.key),
|
||||||
|
source: source,
|
||||||
|
keyword: _keyword,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
childCount: sources.length,
|
childCount: sources.length,
|
||||||
),
|
),
|
||||||
@@ -68,7 +72,11 @@ class _AggregatedSearchPageState extends State<AggregatedSearchPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SliverSearchResult extends StatefulWidget {
|
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;
|
final ComicSource source;
|
||||||
|
|
||||||
@@ -90,6 +98,8 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
|||||||
|
|
||||||
List<Comic>? comics;
|
List<Comic>? comics;
|
||||||
|
|
||||||
|
String? error;
|
||||||
|
|
||||||
void load() async {
|
void load() async {
|
||||||
final data = widget.source.searchPageData!;
|
final data = widget.source.searchPageData!;
|
||||||
var options =
|
var options =
|
||||||
@@ -101,6 +111,11 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
|||||||
comics = res.data;
|
comics = res.data;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
error = res.errorMessage ?? "Unknown error".tl;
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (data.loadNext != null) {
|
} else if (data.loadNext != null) {
|
||||||
var res = await data.loadNext!(widget.keyword, null, options);
|
var res = await data.loadNext!(widget.keyword, null, options);
|
||||||
@@ -109,6 +124,11 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
|||||||
comics = res.data;
|
comics = res.data;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
error = res.errorMessage ?? "Unknown error".tl;
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,6 +159,9 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (error != null && error!.startsWith("CloudflareException")) {
|
||||||
|
error = "Cloudflare verification required".tl;
|
||||||
|
}
|
||||||
super.build(context);
|
super.build(context);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
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(
|
SizedBox(
|
||||||
height: _kComicHeight,
|
height: _kComicHeight,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -190,7 +213,13 @@ class _SliverSearchResultState extends State<_SliverSearchResult>
|
|||||||
children: [
|
children: [
|
||||||
const Icon(Icons.error_outline),
|
const Icon(Icons.error_outline),
|
||||||
const SizedBox(width: 8),
|
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(),
|
const Spacer(),
|
||||||
|
@@ -32,7 +32,7 @@ class _CategoriesPageState extends State<CategoriesPage> {
|
|||||||
.toList();
|
.toList();
|
||||||
categories =
|
categories =
|
||||||
categories.where((element) => allCategories.contains(element)).toList();
|
categories.where((element) => allCategories.contains(element)).toList();
|
||||||
if (!categories.isEqualsTo(this.categories)) {
|
if (!categories.isEqualTo(this.categories)) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.categories = categories;
|
this.categories = categories;
|
||||||
});
|
});
|
||||||
|
@@ -49,19 +49,19 @@ class ComicPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
||||||
with _ComicPageActions {
|
with _ComicPageActions {
|
||||||
|
@override
|
||||||
|
History? history;
|
||||||
|
|
||||||
bool showAppbarTitle = false;
|
bool showAppbarTitle = false;
|
||||||
|
|
||||||
var scrollController = ScrollController();
|
var scrollController = ScrollController();
|
||||||
|
|
||||||
bool isDownloaded = false;
|
bool isDownloaded = false;
|
||||||
|
|
||||||
void updateHistory() async {
|
@override
|
||||||
var newHistory = await HistoryManager()
|
void onReadEnd() {
|
||||||
.find(widget.id, ComicType(widget.sourceKey.hashCode));
|
// The history is passed by reference, so it will be updated automatically.
|
||||||
if (newHistory?.ep != history?.ep || newHistory?.page != history?.page) {
|
update();
|
||||||
history = newHistory;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -77,14 +77,12 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
scrollController.addListener(onScroll);
|
scrollController.addListener(onScroll);
|
||||||
HistoryManager().addListener(updateHistory);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.removeListener(onScroll);
|
scrollController.removeListener(onScroll);
|
||||||
HistoryManager().removeListener(updateHistory);
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +550,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
if (comic.chapters == null) {
|
if (comic.chapters == null) {
|
||||||
return const SliverPadding(padding: EdgeInsets.zero);
|
return const SliverPadding(padding: EdgeInsets.zero);
|
||||||
}
|
}
|
||||||
return const _ComicChapters();
|
return _ComicChapters(history);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildThumbnails() {
|
Widget buildThumbnails() {
|
||||||
@@ -594,7 +592,7 @@ abstract mixin class _ComicPageActions {
|
|||||||
|
|
||||||
ComicSource get comicSource => ComicSource.find(comic.sourceKey)!;
|
ComicSource get comicSource => ComicSource.find(comic.sourceKey)!;
|
||||||
|
|
||||||
History? history;
|
History? get history;
|
||||||
|
|
||||||
bool isLiking = false;
|
bool isLiking = false;
|
||||||
|
|
||||||
@@ -614,8 +612,10 @@ abstract mixin class _ComicPageActions {
|
|||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// whether the comic is added to local favorite
|
||||||
bool isAddToLocalFav = false;
|
bool isAddToLocalFav = false;
|
||||||
|
|
||||||
|
/// whether the comic is favorite on the server
|
||||||
bool isFavorite = false;
|
bool isFavorite = false;
|
||||||
|
|
||||||
FavoriteItem _toFavoriteItem() {
|
FavoriteItem _toFavoriteItem() {
|
||||||
@@ -686,11 +686,13 @@ abstract mixin class _ComicPageActions {
|
|||||||
chapters: comic.chapters,
|
chapters: comic.chapters,
|
||||||
initialChapter: ep,
|
initialChapter: ep,
|
||||||
initialPage: page,
|
initialPage: page,
|
||||||
history: History.fromModel(model: comic, ep: 0, page: 0),
|
history: history ?? History.fromModel(model: comic, ep: 0, page: 0),
|
||||||
author: comic.findAuthor() ?? '',
|
author: comic.findAuthor() ?? '',
|
||||||
tags: comic.plainTags,
|
tags: comic.plainTags,
|
||||||
),
|
),
|
||||||
);
|
).then((_) {
|
||||||
|
onReadEnd();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void continueRead() {
|
void continueRead() {
|
||||||
@@ -699,6 +701,8 @@ abstract mixin class _ComicPageActions {
|
|||||||
read(ep, page);
|
read(ep, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onReadEnd();
|
||||||
|
|
||||||
void download() async {
|
void download() async {
|
||||||
if (LocalManager().isDownloading(comic.id, comic.comicType)) {
|
if (LocalManager().isDownloading(comic.id, comic.comicType)) {
|
||||||
App.rootContext.showMessage(message: "The comic is downloading".tl);
|
App.rootContext.showMessage(message: "The comic is downloading".tl);
|
||||||
@@ -1081,7 +1085,9 @@ class _ActionButton extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ComicChapters extends StatefulWidget {
|
class _ComicChapters extends StatefulWidget {
|
||||||
const _ComicChapters();
|
const _ComicChapters(this.history);
|
||||||
|
|
||||||
|
final History? history;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_ComicChapters> createState() => _ComicChaptersState();
|
State<_ComicChapters> createState() => _ComicChaptersState();
|
||||||
@@ -1094,104 +1100,133 @@ class _ComicChaptersState extends State<_ComicChapters> {
|
|||||||
|
|
||||||
bool showAll = false;
|
bool showAll = false;
|
||||||
|
|
||||||
|
late History? history;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
history = widget.history;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
state = context.findAncestorStateOfType<_ComicPageState>()!;
|
state = context.findAncestorStateOfType<_ComicPageState>()!;
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant _ComicChapters oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
setState(() {
|
||||||
|
history = widget.history;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final eps = state.comic.chapters!;
|
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) {
|
return SliverMainAxisGroup(
|
||||||
length = math.min(length, 20);
|
slivers: [
|
||||||
}
|
SliverToBoxAdapter(
|
||||||
|
child: ListTile(
|
||||||
return SliverMainAxisGroup(
|
title: Text("Chapters".tl),
|
||||||
slivers: [
|
trailing: Tooltip(
|
||||||
SliverToBoxAdapter(
|
message: "Order".tl,
|
||||||
child: ListTile(
|
child: IconButton(
|
||||||
title: Text("Chapters".tl),
|
icon: Icon(reverse
|
||||||
trailing: Tooltip(
|
? Icons.vertical_align_top
|
||||||
message: "Order".tl,
|
: Icons.vertical_align_bottom_outlined),
|
||||||
child: IconButton(
|
onPressed: () {
|
||||||
icon: Icon(reverse
|
setState(() {
|
||||||
? Icons.vertical_align_top
|
reverse = !reverse;
|
||||||
: 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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}),
|
|
||||||
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),
|
|
||||||
),
|
),
|
||||||
),
|
SliverGrid(
|
||||||
const SliverToBoxAdapter(
|
delegate: SliverChildBuilderDelegate(
|
||||||
child: Divider(),
|
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() {
|
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) {
|
if (isLoadingFolders) {
|
||||||
loadFolders();
|
loadFolders();
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
@@ -1011,7 +1011,7 @@ class _LoginPageState extends State<_LoginPage> {
|
|||||||
if (widget.config.loginWebsite != null)
|
if (widget.config.loginWebsite != null)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (App.isWindows || App.isLinux) {
|
if (App.isLinux) {
|
||||||
loginWithWebview2();
|
loginWithWebview2();
|
||||||
} else {
|
} else {
|
||||||
loginWithWebview();
|
loginWithWebview();
|
||||||
@@ -1127,7 +1127,7 @@ class _LoginPageState extends State<_LoginPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for windows and linux
|
// for linux
|
||||||
void loginWithWebview2() async {
|
void loginWithWebview2() async {
|
||||||
if (!await DesktopWebview.isAvailable()) {
|
if (!await DesktopWebview.isAvailable()) {
|
||||||
context.showMessage(message: "Webview is not available".tl);
|
context.showMessage(message: "Webview is not available".tl);
|
||||||
|
@@ -37,7 +37,7 @@ class _ExplorePageState extends State<ExplorePage>
|
|||||||
.expand((e) => e.map((e) => e.title))
|
.expand((e) => e.map((e) => e.title))
|
||||||
.toList();
|
.toList();
|
||||||
explorePages = explorePages.where((e) => all.contains(e)).toList();
|
explorePages = explorePages.where((e) => all.contains(e)).toList();
|
||||||
if (!pages.isEqualsTo(explorePages)) {
|
if (!pages.isEqualTo(explorePages)) {
|
||||||
setState(() {
|
setState(() {
|
||||||
pages = explorePages;
|
pages = explorePages;
|
||||||
controller = TabController(
|
controller = TabController(
|
||||||
|
@@ -476,55 +476,47 @@ class _CreateFolderDialogState extends State<_CreateFolderDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SimpleDialog(
|
return ContentDialog(
|
||||||
title: Text("Create a folder".tl),
|
title: "Create a folder".tl,
|
||||||
children: [
|
content: Column(
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
Padding(
|
||||||
child: TextField(
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
|
||||||
controller: controller,
|
child: TextField(
|
||||||
decoration: InputDecoration(
|
controller: controller,
|
||||||
border: const OutlineInputBorder(),
|
decoration: InputDecoration(
|
||||||
labelText: "name".tl,
|
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),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
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),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,8 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
|
|
||||||
int fingers = 0;
|
int fingers = 0;
|
||||||
|
|
||||||
|
late _ReaderState reader;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_tapGestureRecognizer = TapGestureRecognizer()
|
_tapGestureRecognizer = TapGestureRecognizer()
|
||||||
@@ -33,6 +35,7 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
};
|
};
|
||||||
super.initState();
|
super.initState();
|
||||||
context.readerScaffold._gestureDetectorState = this;
|
context.readerScaffold._gestureDetectorState = this;
|
||||||
|
reader = context.reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -166,7 +169,9 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onTap(Offset location) {
|
void onTap(Offset location) {
|
||||||
if (context.readerScaffold.isOpen) {
|
if (reader._imageViewController!.handleOnTap(location)) {
|
||||||
|
return;
|
||||||
|
} else if (context.readerScaffold.isOpen) {
|
||||||
context.readerScaffold.openOrClose();
|
context.readerScaffold.openOrClose();
|
||||||
} else {
|
} else {
|
||||||
if (appdata.settings['enableTapToTurnPages']) {
|
if (appdata.settings['enableTapToTurnPages']) {
|
||||||
@@ -186,31 +191,37 @@ class _ReaderGestureDetectorState extends State<_ReaderGestureDetector> {
|
|||||||
isBottom = true;
|
isBottom = true;
|
||||||
}
|
}
|
||||||
bool isCenter = false;
|
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) {
|
switch (context.reader.mode) {
|
||||||
case ReaderMode.galleryLeftToRight:
|
case ReaderMode.galleryLeftToRight:
|
||||||
case ReaderMode.continuousLeftToRight:
|
case ReaderMode.continuousLeftToRight:
|
||||||
if (isLeft) {
|
if (isLeft) {
|
||||||
context.reader.toPrevPage();
|
prev();
|
||||||
} else if (isRight) {
|
} else if (isRight) {
|
||||||
context.reader.toNextPage();
|
next();
|
||||||
} else {
|
} else {
|
||||||
isCenter = true;
|
isCenter = true;
|
||||||
}
|
}
|
||||||
case ReaderMode.galleryRightToLeft:
|
case ReaderMode.galleryRightToLeft:
|
||||||
case ReaderMode.continuousRightToLeft:
|
case ReaderMode.continuousRightToLeft:
|
||||||
if (isLeft) {
|
if (isLeft) {
|
||||||
context.reader.toNextPage();
|
next();
|
||||||
} else if (isRight) {
|
} else if (isRight) {
|
||||||
context.reader.toPrevPage();
|
prev();
|
||||||
} else {
|
} else {
|
||||||
isCenter = true;
|
isCenter = true;
|
||||||
}
|
}
|
||||||
case ReaderMode.galleryTopToBottom:
|
case ReaderMode.galleryTopToBottom:
|
||||||
case ReaderMode.continuousTopToBottom:
|
case ReaderMode.continuousTopToBottom:
|
||||||
if (isTop) {
|
if (isTop) {
|
||||||
context.reader.toPrevPage();
|
prev();
|
||||||
} else if (isBottom) {
|
} else if (isBottom) {
|
||||||
context.reader.toNextPage();
|
next();
|
||||||
} else {
|
} else {
|
||||||
isCenter = true;
|
isCenter = true;
|
||||||
}
|
}
|
||||||
|
@@ -335,6 +335,11 @@ class _GalleryModeState extends State<_GalleryMode>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool handleOnTap(Offset location) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
|
const Set<PointerDeviceKind> _kTouchLikeDeviceTypes = <PointerDeviceKind>{
|
||||||
@@ -366,6 +371,18 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
var fingers = 0;
|
var fingers = 0;
|
||||||
bool disableScroll = false;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
reader = context.reader;
|
reader = context.reader;
|
||||||
@@ -374,6 +391,12 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
itemPositionsListener.itemPositions.removeListener(onPositionChanged);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
void onPositionChanged() {
|
void onPositionChanged() {
|
||||||
var page = itemPositionsListener.itemPositions.value.first.index;
|
var page = itemPositionsListener.itemPositions.value.first.index;
|
||||||
page = page.clamp(1, reader.maxPage);
|
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) {
|
onPointerPanZoomUpdate: (event) {
|
||||||
if (event.scale == 1.0) {
|
if (event.scale == 1.0) {
|
||||||
smoothTo(0 - event.panDelta.dy);
|
smoothTo(0 - event.panDelta.dy);
|
||||||
@@ -516,8 +547,14 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
child: widget,
|
child: widget,
|
||||||
);
|
);
|
||||||
|
|
||||||
widget = NotificationListener<ScrollUpdateNotification>(
|
widget = NotificationListener<ScrollNotification>(
|
||||||
onNotification: (notification) {
|
onNotification: (notification) {
|
||||||
|
if (notification is ScrollStartNotification) {
|
||||||
|
delayedSetIsScrolling(true);
|
||||||
|
} else if (notification is ScrollEndNotification) {
|
||||||
|
delayedSetIsScrolling(false);
|
||||||
|
}
|
||||||
|
|
||||||
var length = reader.maxChapter;
|
var length = reader.maxChapter;
|
||||||
if (!scrollController.hasClients) return false;
|
if (!scrollController.hasClients) return false;
|
||||||
if (scrollController.position.pixels <=
|
if (scrollController.position.pixels <=
|
||||||
@@ -592,7 +629,7 @@ class _ContinuousModeState extends State<_ContinuousMode>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void handleLongPressDown(Offset location) {
|
void handleLongPressDown(Offset location) {
|
||||||
if (!appdata.settings['enableLongPressToZoom']) {
|
if (!appdata.settings['enableLongPressToZoom'] || delayedIsScrolling) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
double target = photoViewController.getInitialScale!.call()! * 1.75;
|
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(
|
ImageProvider _createImageProviderFromKey(
|
||||||
|
@@ -237,6 +237,7 @@ class _ReaderState extends State<Reader> with _ReaderLocation, _ReaderWindow {
|
|||||||
history!.maxPage = maxPage;
|
history!.maxPage = maxPage;
|
||||||
}
|
}
|
||||||
history!.readEpisode.add(chapter);
|
history!.readEpisode.add(chapter);
|
||||||
|
print(history!.readEpisode);
|
||||||
history!.time = DateTime.now();
|
history!.time = DateTime.now();
|
||||||
HistoryManager().addHistory(history!);
|
HistoryManager().addHistory(history!);
|
||||||
}
|
}
|
||||||
@@ -430,4 +431,7 @@ abstract interface class _ImageViewController {
|
|||||||
void handleLongPressUp(Offset location);
|
void handleLongPressUp(Offset location);
|
||||||
|
|
||||||
void handleKeyEvent(KeyEvent event);
|
void handleKeyEvent(KeyEvent event);
|
||||||
|
|
||||||
|
/// Returns true if the event is handled.
|
||||||
|
bool handleOnTap(Offset location);
|
||||||
}
|
}
|
||||||
|
@@ -660,12 +660,16 @@ class _ReaderScaffoldState extends State<_ReaderScaffold> {
|
|||||||
App.rootContext.pop();
|
App.rootContext.pop();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
foregroundDecoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
child: Image(
|
child: Image(
|
||||||
|
@@ -189,7 +189,7 @@ class _SearchPageState extends State<SearchPage> {
|
|||||||
void updateSearchSourcesIfNeeded() {
|
void updateSearchSourcesIfNeeded() {
|
||||||
var old = searchSources;
|
var old = searchSources;
|
||||||
findSearchSources();
|
findSearchSources();
|
||||||
if (old.isEqualsTo(searchSources)) {
|
if (old.isEqualTo(searchSources)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
@@ -196,7 +196,7 @@ class _SearchResultPageState extends State<SearchResultPage> {
|
|||||||
return _SearchSettingsDialog(state: this);
|
return _SearchSettingsDialog(state: this);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (!previousOptions.isEqualsTo(options) ||
|
if (!previousOptions.isEqualTo(options) ||
|
||||||
previousSourceKey != sourceKey) {
|
previousSourceKey != sourceKey) {
|
||||||
text = checkAutoLanguage(controller.text);
|
text = checkAutoLanguage(controller.text);
|
||||||
controller.currentText = text;
|
controller.currentText = text;
|
||||||
|
@@ -22,6 +22,13 @@ class _ReaderSettingsState extends State<ReaderSettings> {
|
|||||||
widget.onChanged?.call("enableTapToTurnPages");
|
widget.onChanged?.call("enableTapToTurnPages");
|
||||||
},
|
},
|
||||||
).toSliver(),
|
).toSliver(),
|
||||||
|
_SwitchSetting(
|
||||||
|
title: "Reverse tap to turn Pages".tl,
|
||||||
|
settingKey: "reverseTapToTurnPages",
|
||||||
|
onChanged: () {
|
||||||
|
widget.onChanged?.call("reverseTapToTurnPages");
|
||||||
|
},
|
||||||
|
).toSliver(),
|
||||||
_SwitchSetting(
|
_SwitchSetting(
|
||||||
title: "Page animation".tl,
|
title: "Page animation".tl,
|
||||||
settingKey: "enablePageAnimation",
|
settingKey: "enablePageAnimation",
|
||||||
|
@@ -25,8 +25,13 @@ extension WebviewExtension on InAppWebViewController {
|
|||||||
if (url[url.length - 1] == '/') {
|
if (url[url.length - 1] == '/') {
|
||||||
url = url.substring(0, url.length - 1);
|
url = url.substring(0, url.length - 1);
|
||||||
}
|
}
|
||||||
CookieManager cookieManager = CookieManager.instance();
|
CookieManager cookieManager = CookieManager.instance(
|
||||||
final cookies = await cookieManager.getCookies(url: WebUri(url));
|
webViewEnvironment: AppWebview.webViewEnvironment,
|
||||||
|
);
|
||||||
|
final cookies = await cookieManager.getCookies(
|
||||||
|
url: WebUri(url),
|
||||||
|
webViewController: this,
|
||||||
|
);
|
||||||
var res = <io.Cookie>[];
|
var res = <io.Cookie>[];
|
||||||
for (var cookie in cookies) {
|
for (var cookie in cookies) {
|
||||||
var c = io.Cookie(cookie.name, cookie.value);
|
var c = io.Cookie(cookie.name, cookie.value);
|
||||||
@@ -90,7 +95,8 @@ class _AppWebviewState extends State<AppWebview> {
|
|||||||
var proxy = appdata.settings['proxy'].toString();
|
var proxy = appdata.settings['proxy'].toString();
|
||||||
if (proxy != "system" && proxy != "direct") {
|
if (proxy != "system" && proxy != "direct") {
|
||||||
var proxyAvailable = await WebViewFeature.isFeatureSupported(
|
var proxyAvailable = await WebViewFeature.isFeatureSupported(
|
||||||
WebViewFeature.PROXY_OVERRIDE);
|
WebViewFeature.PROXY_OVERRIDE,
|
||||||
|
);
|
||||||
if (proxyAvailable) {
|
if (proxyAvailable) {
|
||||||
ProxyController proxyController = ProxyController.instance();
|
ProxyController proxyController = ProxyController.instance();
|
||||||
await proxyController.clearProxyOverride();
|
await proxyController.clearProxyOverride();
|
||||||
@@ -147,22 +153,21 @@ class _AppWebviewState extends State<AppWebview> {
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
Widget body = (App.isWindows && AppWebview.webViewEnvironment == null)
|
Widget body = FutureBuilder(
|
||||||
? FutureBuilder(
|
future: future,
|
||||||
future: future,
|
builder: (context, e) {
|
||||||
builder: (context, e) {
|
if (e.error != null) {
|
||||||
if (e.error != null) {
|
return Center(child: Text("Error: ${e.error}"));
|
||||||
return Center(child: Text("Error: ${e.error}"));
|
}
|
||||||
}
|
if (e.data == null) {
|
||||||
if (e.data == null) {
|
return const SizedBox();
|
||||||
return const Center(child: CircularProgressIndicator());
|
}
|
||||||
}
|
AppWebview.webViewEnvironment = e.data;
|
||||||
AppWebview.webViewEnvironment = e.data;
|
return createWebviewWithEnvironment(
|
||||||
return createWebviewWithEnvironment(
|
AppWebview.webViewEnvironment,
|
||||||
AppWebview.webViewEnvironment);
|
);
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
: createWebviewWithEnvironment(AppWebview.webViewEnvironment);
|
|
||||||
|
|
||||||
body = Stack(
|
body = Stack(
|
||||||
children: [
|
children: [
|
||||||
|
@@ -115,7 +115,17 @@ abstract class CBZ {
|
|||||||
cache.deleteSync(recursive: true);
|
cache.deleteSync(recursive: true);
|
||||||
throw Exception('No images found in the archive');
|
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(
|
var coverFile = files.firstWhereOrNull(
|
||||||
(element) =>
|
(element) =>
|
||||||
element.path.endsWith('cover.${element.path.split('.').last}'),
|
element.path.endsWith('cover.${element.path.split('.').last}'),
|
||||||
|
@@ -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){
|
if(length != list.length){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -81,10 +83,6 @@ extension StringExt on String{
|
|||||||
return '$before$to$after';
|
return '$before$to$after';
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool hasMatch(String? value, String pattern) {
|
|
||||||
return (value == null) ? false : RegExp(pattern).hasMatch(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isURL(){
|
bool _isURL(){
|
||||||
final regex = RegExp(
|
final regex = RegExp(
|
||||||
r'^((http|https|ftp)://)[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-|]*[\w@?^=%&/~+#-])?$',
|
r'^((http|https|ftp)://)[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-|]*[\w@?^=%&/~+#-])?$',
|
||||||
|
77
pubspec.lock
77
pubspec.lock
@@ -307,19 +307,21 @@ packages:
|
|||||||
flutter_inappwebview:
|
flutter_inappwebview:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview
|
path: flutter_inappwebview
|
||||||
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
|
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
source: hosted
|
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
|
||||||
version: "6.1.5"
|
source: git
|
||||||
|
version: "6.2.0-beta.3"
|
||||||
flutter_inappwebview_android:
|
flutter_inappwebview_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_android
|
path: flutter_inappwebview_android
|
||||||
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
|
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
source: hosted
|
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
|
||||||
version: "1.1.3"
|
source: git
|
||||||
|
version: "1.2.0-beta.3"
|
||||||
flutter_inappwebview_internal_annotations:
|
flutter_inappwebview_internal_annotations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -331,43 +333,48 @@ packages:
|
|||||||
flutter_inappwebview_ios:
|
flutter_inappwebview_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_ios
|
path: flutter_inappwebview_ios
|
||||||
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
|
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
source: hosted
|
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
|
||||||
version: "1.1.2"
|
source: git
|
||||||
|
version: "1.2.0-beta.3"
|
||||||
flutter_inappwebview_macos:
|
flutter_inappwebview_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_macos
|
path: flutter_inappwebview_macos
|
||||||
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
|
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
source: hosted
|
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
|
||||||
version: "1.1.2"
|
source: git
|
||||||
|
version: "1.2.0-beta.3"
|
||||||
flutter_inappwebview_platform_interface:
|
flutter_inappwebview_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_platform_interface
|
path: flutter_inappwebview_platform_interface
|
||||||
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
|
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
source: hosted
|
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
|
||||||
version: "1.3.0+1"
|
source: git
|
||||||
|
version: "1.4.0-beta.3"
|
||||||
flutter_inappwebview_web:
|
flutter_inappwebview_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_web
|
path: flutter_inappwebview_web
|
||||||
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
|
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
source: hosted
|
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
|
||||||
version: "1.1.2"
|
source: git
|
||||||
|
version: "1.2.0-beta.3"
|
||||||
flutter_inappwebview_windows:
|
flutter_inappwebview_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_inappwebview_windows
|
path: flutter_inappwebview_windows
|
||||||
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
|
ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
url: "https://pub.dev"
|
resolved-ref: "0aaf7a0bfc01d61a4d1453cefb57fb6783b6e676"
|
||||||
source: hosted
|
url: "https://github.com/pichillilorenzo/flutter_inappwebview"
|
||||||
version: "0.6.0"
|
source: git
|
||||||
|
version: "0.7.0-beta.3"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@@ -2,7 +2,7 @@ name: venera
|
|||||||
description: "A comic app."
|
description: "A comic app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.2.4+124
|
version: 1.2.5+125
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.6.0 <4.0.0'
|
sdk: '>=3.6.0 <4.0.0'
|
||||||
@@ -43,7 +43,11 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/wgh136/flutter_desktop_webview
|
url: https://github.com/wgh136/flutter_desktop_webview
|
||||||
path: packages/desktop_webview_window
|
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
|
app_links: ^6.3.3
|
||||||
sliver_tools: ^0.2.12
|
sliver_tools: ^0.2.12
|
||||||
flutter_file_dialog: ^3.0.2
|
flutter_file_dialog: ^3.0.2
|
||||||
|
Reference in New Issue
Block a user