Show read button if loading fails.

This commit is contained in:
2025-09-14 17:05:45 +08:00
parent 3a9d634edf
commit 4c257d7178
3 changed files with 136 additions and 99 deletions

View File

@@ -7,6 +7,7 @@ class NetworkError extends StatelessWidget {
this.retry, this.retry,
this.withAppbar = true, this.withAppbar = true,
this.buttonText, this.buttonText,
this.action,
}); });
final String message; final String message;
@@ -17,6 +18,8 @@ class NetworkError extends StatelessWidget {
final String? buttonText; final String? buttonText;
final Widget? action;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var cfe = CloudflareException.fromString(message); var cfe = CloudflareException.fromString(message);
@@ -67,12 +70,19 @@ class NetworkError extends StatelessWidget {
child: Text('Verify'.tl), child: Text('Verify'.tl),
) )
else else
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (action != null)
action!.paddingRight(8),
FilledButton( FilledButton(
onPressed: retry, onPressed: retry,
child: Text(buttonText ?? 'Retry'.tl), child: Text(buttonText ?? 'Retry'.tl),
), ),
], ],
), ),
],
),
); );
if (withAppbar) { if (withAppbar) {
body = Column( body = Column(

View File

@@ -77,8 +77,10 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
@override @override
void onReadEnd() { void onReadEnd() {
history ??= history ??= HistoryManager().find(
HistoryManager().find(widget.id, ComicType(widget.sourceKey.hashCode)); widget.id,
ComicType(widget.sourceKey.hashCode),
);
update(); update();
} }
@@ -93,6 +95,32 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
); );
} }
@override
Widget buildError() {
final isDownloaded = LocalManager().isDownloaded(
widget.id,
ComicType.fromKey(widget.sourceKey),
);
Widget? action;
if (isDownloaded) {
action = FilledButton.tonal(
child: Text("Read".tl),
onPressed: () {
final localComic = LocalManager().find(
widget.id,
ComicType.fromKey(widget.sourceKey),
);
if (localComic == null) {
context.showMessage(message: "Local comic not found".tl);
return;
}
localComic.read();
},
);
}
return NetworkError(message: error!, retry: retry, action: action);
}
@override @override
void initState() { void initState() {
scrollController.addListener(onScroll); scrollController.addListener(onScroll);
@@ -114,7 +142,8 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
ComicDetails get comic => data!; ComicDetails get comic => data!;
void onScroll() { void onScroll() {
var offset = scrollController.position.pixels - var offset =
scrollController.position.pixels -
scrollController.position.minScrollExtent; scrollController.position.minScrollExtent;
var showFAB = offset > 0; var showFAB = offset > 0;
if (showFAB != this.showFAB) { if (showFAB != this.showFAB) {
@@ -145,9 +174,11 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
floatingActionButton: showFAB floatingActionButton: showFAB
? FloatingActionButton( ? FloatingActionButton(
onPressed: () { onPressed: () {
scrollController.animateTo(0, scrollController.animateTo(
0,
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
curve: Curves.ease); curve: Curves.ease,
);
}, },
child: const Icon(Icons.arrow_upward), child: const Icon(Icons.arrow_upward),
) )
@@ -164,7 +195,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
buildThumbnails(), buildThumbnails(),
buildRecommend(), buildRecommend(),
SliverPadding( SliverPadding(
padding: EdgeInsets.only(bottom: context.padding.bottom + 80), // Add additional padding for FAB padding: EdgeInsets.only(
bottom: context.padding.bottom + 80,
), // Add additional padding for FAB
), ),
], ],
), ),
@@ -190,12 +223,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
initialPage: history?.page, initialPage: history?.page,
initialChapter: history?.ep, initialChapter: history?.ep,
initialChapterGroup: history?.group, initialChapterGroup: history?.group,
history: history ?? history:
History.fromModel( history ??
model: localComic, History.fromModel(model: localComic, ep: 0, page: 0),
ep: 0,
page: 0,
),
author: localComic.subTitle ?? '', author: localComic.subTitle ?? '',
tags: localComic.tags, tags: localComic.tags,
); );
@@ -215,8 +245,10 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
widget.id, widget.id,
ComicType(widget.sourceKey.hashCode), ComicType(widget.sourceKey.hashCode),
); );
history = history = HistoryManager().find(
HistoryManager().find(widget.id, ComicType(widget.sourceKey.hashCode)); widget.id,
ComicType(widget.sourceKey.hashCode),
);
return comicSource.loadComicInfo!(widget.id); return comicSource.loadComicInfo!(widget.id);
} }
@@ -225,11 +257,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
isLiked = comic.isLiked ?? false; isLiked = comic.isLiked ?? false;
isFavorite = comic.isFavorite ?? false; isFavorite = comic.isFavorite ?? false;
if (comic.chapters == null) { if (comic.chapters == null) {
isDownloaded = LocalManager().isDownloaded( isDownloaded = LocalManager().isDownloaded(comic.id, comic.comicType, 0);
comic.id,
comic.comicType,
0,
);
} }
} }
@@ -242,7 +270,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
), ),
actions: [ actions: [
IconButton( IconButton(
onPressed: showMoreActions, icon: const Icon(Icons.more_horiz)) onPressed: showMoreActions,
icon: const Icon(Icons.more_horiz),
),
], ],
); );
@@ -288,8 +318,10 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
children: [ children: [
SelectableText(comic.title, style: ts.s18), SelectableText(comic.title, style: ts.s18),
if (comic.subTitle != null) if (comic.subTitle != null)
SelectableText(comic.subTitle!, style: ts.s14) SelectableText(
.paddingVertical(4), comic.subTitle!,
style: ts.s14,
).paddingVertical(4),
Text( Text(
(ComicSource.find(comic.sourceKey)?.name) ?? '', (ComicSource.find(comic.sourceKey)?.name) ?? '',
style: ts.s12, style: ts.s12,
@@ -338,7 +370,8 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
icon: const Icon(Icons.favorite_border), icon: const Icon(Icons.favorite_border),
activeIcon: const Icon(Icons.favorite), activeIcon: const Icon(Icons.favorite),
isActive: isLiked, isActive: isLiked,
text: ((data!.likesCount != null) text:
((data!.likesCount != null)
? (data!.likesCount! + (isLiked ? 1 : 0)) ? (data!.likesCount! + (isLiked ? 1 : 0))
: (isLiked ? 'Liked'.tl : 'Like'.tl)) : (isLiked ? 'Liked'.tl : 'Like'.tl))
.toString(), .toString(),
@@ -383,9 +416,11 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
Expanded( Expanded(
child: hasHistory child: hasHistory
? FilledButton( ? FilledButton(
onPressed: continueRead, child: Text("Continue".tl)) onPressed: continueRead,
: FilledButton(onPressed: read, child: Text("Read".tl)), child: Text("Continue".tl),
) )
: FilledButton(onPressed: read, child: Text("Read".tl)),
),
], ],
).paddingHorizontal(16).paddingVertical(8), ).paddingHorizontal(16).paddingVertical(8),
if (history != null) if (history != null)
@@ -412,19 +447,20 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
var epName = "E$ep"; var epName = "E$ep";
String? groupName; String? groupName;
try { try {
if (group == null){ if (group == null) {
epName = comic.chapters!.titles.elementAt( epName = comic.chapters!.titles.elementAt(
math.min(ep - 1, comic.chapters!.length - 1), math.min(ep - 1, comic.chapters!.length - 1),
); );
} else { } else {
groupName = comic.chapters!.groups.elementAt(group - 1); groupName = comic.chapters!.groups.elementAt(
group - 1,
);
epName = comic.chapters! epName = comic.chapters!
.getGroupByIndex(group - 1) .getGroupByIndex(group - 1)
.values .values
.elementAt(ep - 1); .elementAt(ep - 1);
} }
} } catch (e) {
catch(e) {
// ignore // ignore
} }
text = groupName == null text = groupName == null
@@ -453,9 +489,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
return SliverLazyToBoxAdapter( return SliverLazyToBoxAdapter(
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(title: Text("Description".tl)),
title: Text("Description".tl),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: SelectableText(comic.description!).fixWidth(double.infinity), child: SelectableText(comic.description!).fixWidth(double.infinity),
@@ -539,10 +573,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
); );
} else { } else {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(color: color, borderRadius: borderRadius),
color: color,
borderRadius: borderRadius,
),
child: Text(text).padding(padding), child: Text(text).padding(padding),
); );
} }
@@ -552,13 +583,13 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
if (int.tryParse(time) != null) { if (int.tryParse(time) != null) {
var t = int.tryParse(time); var t = int.tryParse(time);
if (t! > 1000000000000) { if (t! > 1000000000000) {
return DateTime.fromMillisecondsSinceEpoch(t) return DateTime.fromMillisecondsSinceEpoch(
.toString() t,
.substring(0, 19); ).toString().substring(0, 19);
} else { } else {
return DateTime.fromMillisecondsSinceEpoch(t * 1000) return DateTime.fromMillisecondsSinceEpoch(
.toString() t * 1000,
.substring(0, 19); ).toString().substring(0, 19);
} }
} }
if (time.contains('T') || time.contains('Z')) { if (time.contains('T') || time.contains('Z')) {
@@ -583,17 +614,11 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTile( ListTile(title: Text("Information".tl)),
title: Text("Information".tl),
),
if (comic.stars != null) if (comic.stars != null)
Row( Row(
children: [ children: [
StarRating( StarRating(value: comic.stars!, size: 24, onTap: starRating),
value: comic.stars!,
size: 24,
onTap: starRating,
),
const SizedBox(width: 8), const SizedBox(width: 8),
Text(comic.stars!.toStringAsFixed(2)), Text(comic.stars!.toStringAsFixed(2)),
], ],
@@ -671,24 +696,19 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
if (comic.recommend == null || comic.recommend!.isEmpty) { if (comic.recommend == null || comic.recommend!.isEmpty) {
return const SliverPadding(padding: EdgeInsets.zero); return const SliverPadding(padding: EdgeInsets.zero);
} }
return SliverMainAxisGroup(slivers: [ return SliverMainAxisGroup(
SliverToBoxAdapter( slivers: [
child: ListTile( SliverToBoxAdapter(child: ListTile(title: Text("Related".tl))),
title: Text("Related".tl),
),
),
SliverGridComics(comics: comic.recommend!), SliverGridComics(comics: comic.recommend!),
]); ],
);
} }
Widget buildComments() { Widget buildComments() {
if (comic.comments == null || comic.comments!.isEmpty) { if (comic.comments == null || comic.comments!.isEmpty) {
return const SliverPadding(padding: EdgeInsets.zero); return const SliverPadding(padding: EdgeInsets.zero);
} }
return _CommentsPart( return _CommentsPart(comments: comic.comments!, showMore: showComments);
comments: comic.comments!,
showMore: showComments,
);
} }
} }
@@ -793,8 +813,8 @@ class _SelectDownloadChapterState extends State<_SelectDownloadChapter> {
itemBuilder: (context, i) { itemBuilder: (context, i) {
return CheckboxListTile( return CheckboxListTile(
title: Text(widget.eps[i]), title: Text(widget.eps[i]),
value: selected.contains(i) || value:
widget.downloadedEps.contains(i), selected.contains(i) || widget.downloadedEps.contains(i),
onChanged: widget.downloadedEps.contains(i) onChanged: widget.downloadedEps.contains(i)
? null ? null
: (v) { : (v) {
@@ -805,7 +825,8 @@ class _SelectDownloadChapterState extends State<_SelectDownloadChapter> {
selected.add(i); selected.add(i);
} }
}); });
}); },
);
}, },
), ),
), ),
@@ -813,9 +834,7 @@ class _SelectDownloadChapterState extends State<_SelectDownloadChapter> {
height: 50, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide( top: BorderSide(color: context.colorScheme.outlineVariant),
color: context.colorScheme.outlineVariant,
),
), ),
), ),
child: Row( child: Row(
@@ -880,8 +899,12 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget buildContainer(double? width, double? height, Widget buildContainer(
{Color? color, double? radius}) { double? width,
double? height, {
Color? color,
double? radius,
}) {
return Container( return Container(
height: height, height: height,
width: width, width: width,
@@ -923,13 +946,9 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
if (context.width < changePoint) if (context.width < changePoint)
Row( Row(
children: [ children: [
Expanded( Expanded(child: buildContainer(null, 36, radius: 18)),
child: buildContainer(null, 36, radius: 18),
),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(child: buildContainer(null, 36, radius: 18)),
child: buildContainer(null, 36, radius: 18),
),
], ],
).paddingHorizontal(16), ).paddingHorizontal(16),
const Divider(), const Divider(),
@@ -938,7 +957,7 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2.4, strokeWidth: 2.4,
).fixHeight(24).fixWidth(24), ).fixHeight(24).fixWidth(24),
) ),
], ],
), ),
); );
@@ -948,11 +967,7 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
Widget child; Widget child;
if (cover != null) { if (cover != null) {
child = AnimatedImage( child = AnimatedImage(
image: CachedImageProvider( image: CachedImageProvider(cover!, sourceKey: sourceKey, cid: cid),
cover!,
sourceKey: sourceKey,
cid: cid,
),
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,

View File

@@ -512,6 +512,18 @@ class _LocalFavoritesPageState extends State<_LocalFavoritesPage> {
); );
}, },
), ),
if (selectedComics.length == 1)
MenuEntry(
icon: Icons.chrome_reader_mode_outlined,
text: "Read".tl,
onClick: () {
final c = selectedComics.keys.first as FavoriteItem;
App.rootContext.to(() => ReaderWithLoading(
id: c.id,
sourceKey: c.sourceKey,
));
},
),
]), ]),
], ],
) )