fix local comic display

This commit is contained in:
nyne
2024-10-27 18:39:33 +08:00
parent 27d25db407
commit f17af25e2c
16 changed files with 123 additions and 24 deletions

View File

@@ -149,6 +149,11 @@ class ComicTile extends StatelessWidget {
ImageProvider image; ImageProvider image;
if (comic is LocalComic) { if (comic is LocalComic) {
image = FileImage((comic as LocalComic).coverFile); image = FileImage((comic as LocalComic).coverFile);
} else if (comic.cover.startsWith('file://')) {
image = FileImage(File(comic.cover.substring(7)));
} else if (comic.sourceKey == 'local') {
var localComic = LocalManager().find(comic.id, ComicType.local);
image = FileImage(localComic!.coverFile);
} else { } else {
image = CachedImageProvider(comic.cover, sourceKey: comic.sourceKey); image = CachedImageProvider(comic.cover, sourceKey: comic.sourceKey);
} }

View File

@@ -26,6 +26,7 @@ import 'package:venera/network/cloudflare.dart';
import 'package:venera/pages/comic_page.dart'; import 'package:venera/pages/comic_page.dart';
import 'package:venera/pages/favorites/favorites_page.dart'; import 'package:venera/pages/favorites/favorites_page.dart';
import 'package:venera/utils/ext.dart'; import 'package:venera/utils/ext.dart';
import 'package:venera/utils/io.dart';
import 'package:venera/utils/tags_translation.dart'; import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';

View File

@@ -113,6 +113,7 @@ abstract class LoadingState<T extends StatefulWidget, S extends Object>
if (res.success) { if (res.success) {
return res; return res;
} else { } else {
if(!mounted) return res;
if (retry >= 3) { if (retry >= 3) {
return res; return res;
} }
@@ -170,6 +171,7 @@ abstract class LoadingState<T extends StatefulWidget, S extends Object>
isLoading = true; isLoading = true;
Future.microtask(() { Future.microtask(() {
loadDataWithRetry().then((value) async { loadDataWithRetry().then((value) async {
if(!mounted) return;
if (value.success) { if (value.success) {
data = value.data; data = value.data;
await onDataLoaded(); await onDataLoaded();

View File

@@ -11,6 +11,14 @@ class ComicType {
@override @override
int get hashCode => value.hashCode; int get hashCode => value.hashCode;
String get sourceKey {
if(this == local) {
return "local";
} else {
return comicSource!.key;
}
}
ComicSource? get comicSource { ComicSource? get comicSource {
if(this == local) { if(this == local) {
return null; return null;

View File

@@ -19,6 +19,11 @@ extension Navigation on BuildContext {
.push<T>(AppPageRoute(builder: (context) => builder())); .push<T>(AppPageRoute(builder: (context) => builder()));
} }
Future<void> toReplacement<T>(Widget Function() builder) {
return Navigator.of(this)
.pushReplacement(AppPageRoute(builder: (context) => builder()));
}
double get width => MediaQuery.of(this).size.width; double get width => MediaQuery.of(this).size.width;
double get height => MediaQuery.of(this).size.height; double get height => MediaQuery.of(this).size.height;

View File

@@ -55,7 +55,7 @@ class FavoriteItem implements Comic {
@override @override
String toString() { String toString() {
var s = "FavoriteItem: $name $author $coverPath $hashCode $tags"; var s = "FavoriteItem: $name $author $coverPath $hashCode $tags";
if(s.length > 100) { if (s.length > 100) {
return s.substring(0, 100); return s.substring(0, 100);
} }
return s; return s;
@@ -65,7 +65,9 @@ class FavoriteItem implements Comic {
String get cover => coverPath; String get cover => coverPath;
@override @override
String get description => "$time | ${type.comicSource?.name ?? "Unknown"}"; String get description {
return "$time | ${type == ComicType.local ? 'local' : type.comicSource?.name ?? "Unknown"}";
}
@override @override
String? get favoriteId => null; String? get favoriteId => null;
@@ -77,7 +79,7 @@ class FavoriteItem implements Comic {
int? get maxPage => null; int? get maxPage => null;
@override @override
String get sourceKey => type.comicSource?.key ?? "Unknown:${type.value}"; String get sourceKey => type == ComicType.local ? 'local' : type.comicSource?.key ?? "Unknown:${type.value}";
@override @override
double? get stars => null; double? get stars => null;
@@ -514,6 +516,13 @@ class LocalFavoritesManager {
update "$folder" update "$folder"
set name = ?, author = ?, cover_path = ?, tags = ? set name = ?, author = ?, cover_path = ?, tags = ?
where id == ? and type == ?; where id == ? and type == ?;
""", [comic.name, comic.author, comic.coverPath, comic.tags.join(","), comic.id, comic.type.value]); """, [
comic.name,
comic.author,
comic.coverPath,
comic.tags.join(","),
comic.id,
comic.type.value
]);
} }
} }

View File

@@ -79,7 +79,7 @@ class LocalComic with HistoryMixin implements Comic {
String get description => ""; String get description => "";
@override @override
String get sourceKey => comicType.comicSource?.key ?? '_local_'; String get sourceKey => comicType == ComicType.local ? "local" : "";
@override @override
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@@ -214,7 +214,7 @@ class LocalManager with ChangeNotifier {
SELECT id FROM comics WHERE comic_type = ? SELECT id FROM comics WHERE comic_type = ?
ORDER BY CAST(id AS INTEGER) DESC ORDER BY CAST(id AS INTEGER) DESC
LIMIT 1; LIMIT 1;
'''[type.value], ''', [type.value],
); );
if (res.isEmpty) { if (res.isEmpty) {
return '1'; return '1';
@@ -341,7 +341,7 @@ class LocalManager with ChangeNotifier {
if (comic == null) return false; if (comic == null) return false;
if (comic.chapters == null) return true; if (comic.chapters == null) return true;
return comic.downloadedChapters return comic.downloadedChapters
.contains(comic.chapters!.keys.elementAt(ep)); .contains(comic.chapters!.keys.elementAt(ep-1));
} }
List<DownloadTask> downloadingTasks = []; List<DownloadTask> downloadingTasks = [];

View File

@@ -161,7 +161,7 @@ class NetworkCacheManager implements Interceptor {
return handler.next(response); return handler.next(response);
} }
var size = _calculateSize(response.data); var size = _calculateSize(response.data);
if(size != null && size < 1024 * 1024) { if(size != null && size < 1024 * 1024 && size > 1024) {
var cache = NetworkCache( var cache = NetworkCache(
uri: response.requestOptions.uri, uri: response.requestOptions.uri,
requestHeaders: response.requestOptions.headers, requestHeaders: response.requestOptions.headers,

View File

@@ -203,6 +203,8 @@ class ImagesDownloadTask extends DownloadTask with _TransferSpeedMixin {
@override @override
void resume() async { void resume() async {
if (_isRunning) return; if (_isRunning) return;
_isError = false;
_message = "Resuming...";
_isRunning = true; _isRunning = true;
notifyListeners(); notifyListeners();
runRecorder(); runRecorder();

View File

@@ -72,6 +72,8 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
} }
} }
var isFirst = true;
@override @override
Widget buildContent(BuildContext context, ComicDetails data) { Widget buildContent(BuildContext context, ComicDetails data) {
return SmoothCustomScrollView( return SmoothCustomScrollView(
@@ -91,8 +93,37 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
@override @override
Future<Res<ComicDetails>> loadData() async { Future<Res<ComicDetails>> loadData() async {
if (widget.sourceKey == 'local') {
var localComic = LocalManager().find(widget.id, ComicType.local);
if (localComic == null) {
return const Res.error('Local comic not found');
}
var history = await HistoryManager().find(widget.id, ComicType.local);
if(isFirst) {
Future.microtask(() {
App.rootContext.to(() {
return Reader(
type: ComicType.local,
cid: widget.id,
name: localComic.title,
chapters: localComic.chapters,
history: history ??
History.fromModel(
model: localComic,
ep: 0,
page: 0,
),
);
});
App.mainNavigatorKey!.currentContext!.pop();
});
isFirst = false;
}
await Future.delayed(const Duration(milliseconds: 200));
return const Res.error('Local comic');
}
var comicSource = ComicSource.find(widget.sourceKey); var comicSource = ComicSource.find(widget.sourceKey);
if(comicSource == null) { if (comicSource == null) {
return const Res.error('Comic source not found'); return const Res.error('Comic source not found');
} }
isAddToLocalFav = LocalFavoritesManager().isExist( isAddToLocalFav = LocalFavoritesManager().isExist(
@@ -101,7 +132,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
); );
history = await HistoryManager() history = await HistoryManager()
.find(widget.id, ComicType(widget.sourceKey.hashCode)); .find(widget.id, ComicType(widget.sourceKey.hashCode));
return comicSource!.loadComicInfo!(widget.id); return comicSource.loadComicInfo!(widget.id);
} }
@override @override
@@ -500,7 +531,11 @@ abstract mixin class _ComicPageActions {
} }
void share() { void share() {
Share.shareText(comic.title); var text = comic.title;
if (comic.url != null) {
text += '\n${comic.url}';
}
Share.shareText(text);
} }
/// read the comic /// read the comic
@@ -694,8 +729,7 @@ abstract mixin class _ComicPageActions {
setState(() { setState(() {
isLoading = true; isLoading = true;
}); });
comicSource.starRatingFunc! comicSource.starRatingFunc!(comic.id, rating.round())
(comic.id, rating.round())
.then((value) { .then((value) {
if (value.success) { if (value.success) {
App.rootContext App.rootContext

View File

@@ -66,7 +66,16 @@ class _ExplorePageState extends State<ExplorePage>
return NetworkError( return NetworkError(
message: "No Explore Pages".tl, message: "No Explore Pages".tl,
retry: () { retry: () {
setState(() {}); setState(() {
pages = ComicSource.all()
.map((e) => e.explorePages)
.expand((e) => e.map((e) => e.title))
.toList();
controller = TabController(
length: pages.length,
vsync: this,
);
});
}, },
withAppbar: false, withAppbar: false,
); );

View File

@@ -98,7 +98,7 @@ void addFavorite(Comic comic) {
name: comic.title, name: comic.title,
coverPath: comic.cover, coverPath: comic.cover,
author: comic.subtitle ?? '', author: comic.subtitle ?? '',
type: ComicType(comic.sourceKey.hashCode), type: ComicType((comic.sourceKey == 'local' ? 0 : comic.sourceKey.hashCode)),
tags: comic.tags ?? [], tags: comic.tags ?? [],
), ),
); );

View File

@@ -4,6 +4,8 @@ import 'package:venera/foundation/app.dart';
import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/comic_type.dart'; import 'package:venera/foundation/comic_type.dart';
import 'package:venera/foundation/history.dart'; import 'package:venera/foundation/history.dart';
import 'package:venera/foundation/local.dart';
import 'package:venera/utils/ext.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
class HistoryPage extends StatefulWidget { class HistoryPage extends StatefulWidget {
@@ -78,9 +80,19 @@ class _HistoryPageState extends State<HistoryPage> {
SliverGridComics( SliverGridComics(
comics: comics.map( comics: comics.map(
(e) { (e) {
var cover = e.cover;
if (!cover.isURL) {
var localComic = LocalManager().find(
e.id,
e.type,
);
if(localComic != null) {
cover = "file://${localComic.coverFile.path}";
}
}
return Comic( return Comic(
e.title, e.title,
e.cover, cover,
e.id, e.id,
e.subtitle, e.subtitle,
null, null,
@@ -100,7 +112,7 @@ class _HistoryPageState extends State<HistoryPage> {
icon: Icons.remove, icon: Icons.remove,
text: 'Remove'.tl, text: 'Remove'.tl,
onClick: () { onClick: () {
if(c.sourceKey.startsWith("Invalid")) { if (c.sourceKey.startsWith("Invalid")) {
HistoryManager().remove( HistoryManager().remove(
c.id, c.id,
ComicType(int.parse(c.sourceKey.split(':')[1])), ComicType(int.parse(c.sourceKey.split(':')[1])),

View File

@@ -15,6 +15,7 @@ import 'package:venera/pages/comic_source_page.dart';
import 'package:venera/pages/downloading_page.dart'; import 'package:venera/pages/downloading_page.dart';
import 'package:venera/pages/history_page.dart'; import 'package:venera/pages/history_page.dart';
import 'package:venera/pages/search_page.dart'; import 'package:venera/pages/search_page.dart';
import 'package:venera/utils/ext.dart';
import 'package:venera/utils/io.dart'; import 'package:venera/utils/io.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
@@ -155,12 +156,26 @@ class _HistoryState extends State<_History> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: history.length, itemCount: history.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var cover = history[index].cover;
ImageProvider imageProvider = CachedImageProvider(
cover,
sourceKey: history[index].type.comicSource?.key,
);
if (!cover.isURL) {
var localComic = LocalManager().find(
history[index].id,
history[index].type,
);
if (localComic != null) {
imageProvider = FileImage(localComic.coverFile);
}
}
return InkWell( return InkWell(
onTap: () { onTap: () {
context.to( context.to(
() => ComicPage( () => ComicPage(
id: history[index].id, id: history[index].id,
sourceKey: history[index].type.comicSource!.key, sourceKey: history[index].type.sourceKey,
), ),
); );
}, },
@@ -177,10 +192,7 @@ class _HistoryState extends State<_History> {
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: AnimatedImage( child: AnimatedImage(
image: CachedImageProvider( image: imageProvider,
history[index].cover,
sourceKey: history[index].type.comicSource?.key,
),
width: 96, width: 96,
height: 128, height: 128,
fit: BoxFit.cover, fit: BoxFit.cover,

View File

@@ -196,7 +196,7 @@ class _SearchPageState extends State<SearchPage> {
runSpacing: 8, runSpacing: 8,
children: sources.map((e) { children: sources.map((e) {
return OptionChip( return OptionChip(
text: e.name.tl, text: e.name,
isSelected: searchTarget == e.key, isSelected: searchTarget == e.key,
onTap: () { onTap: () {
setState(() { setState(() {

View File

@@ -75,7 +75,7 @@ extension StringExt on String{
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@?^=%&/~+#-])?$',
caseSensitive: false); caseSensitive: false);
return regex.hasMatch(this); return regex.hasMatch(this);
} }