10 Commits

Author SHA1 Message Date
nyne
7fa48cec29 Merge pull request #515 from venera-app/v1.5.1-dev
V1.5.1
2025-09-14 18:56:12 +08:00
e549a18dbf flutter 3.35.3 2025-09-14 18:54:26 +08:00
c17c4abb5b Reduce size of scroll bar. 2025-09-14 18:43:11 +08:00
af57bc31b1 Update version code. 2025-09-14 18:33:19 +08:00
16449a1440 Change page transition animation for Android. 2025-09-14 18:30:54 +08:00
a7c1983f35 Fallback to local cover if loading fails for favorite comic. 2025-09-14 17:19:23 +08:00
4c257d7178 Show read button if loading fails. 2025-09-14 17:05:45 +08:00
nyne
e179c8f67f Change padding check condition for Android platform (#503) 2025-09-05 17:52:33 +08:00
nyne
c4b85471c1 Merge pull request #499 from KarlZeo/fix-ios-padding-check
fix padding check error on ios
2025-09-05 17:42:49 +08:00
KarlZeo
a898b57d96 fix padding check error on ios 2025-09-04 20:04:28 +08:00
12 changed files with 277 additions and 197 deletions

View File

@@ -16,6 +16,7 @@
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:enableOnBackInvokedCallback="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user

View File

@@ -17,6 +17,7 @@ ImageProvider? _findImageProvider(Comic comic) {
comic.cover, comic.cover,
sourceKey: comic.sourceKey, sourceKey: comic.sourceKey,
cid: comic.id, cid: comic.id,
fallbackToLocalCover: comic is FavoriteItem,
); );
} }
return image; return image;

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

@@ -7,8 +7,11 @@ class PaneItemEntry {
IconData activeIcon; IconData activeIcon;
PaneItemEntry( PaneItemEntry({
{required this.label, required this.icon, required this.activeIcon}); required this.label,
required this.icon,
required this.activeIcon,
});
} }
class PaneActionEntry { class PaneActionEntry {
@@ -18,20 +21,24 @@ class PaneActionEntry {
VoidCallback onTap; VoidCallback onTap;
PaneActionEntry( PaneActionEntry({
{required this.label, required this.icon, required this.onTap}); required this.label,
required this.icon,
required this.onTap,
});
} }
class NaviPane extends StatefulWidget { class NaviPane extends StatefulWidget {
const NaviPane( const NaviPane({
{required this.paneItems, required this.paneItems,
required this.paneActions, required this.paneActions,
required this.pageBuilder, required this.pageBuilder,
this.initialPage = 0, this.initialPage = 0,
this.onPageChanged, this.onPageChanged,
required this.observer, required this.observer,
required this.navigatorKey, required this.navigatorKey,
super.key}); super.key,
});
final List<PaneItemEntry> paneItems; final List<PaneItemEntry> paneItems;
@@ -187,7 +194,8 @@ class NaviPaneState extends State<NaviPane>
child: buildLeft(), child: buildLeft(),
), ),
Positioned.fill( Positioned.fill(
left: _kFoldedSideBarWidth * ((value - 1).clamp(0, 1)) + left:
_kFoldedSideBarWidth * ((value - 1).clamp(0, 1)) +
(_kSideBarWidth - _kFoldedSideBarWidth) * (_kSideBarWidth - _kFoldedSideBarWidth) *
((value - 2).clamp(0, 1)), ((value - 2).clamp(0, 1)),
child: buildMainView(), child: buildMainView(),
@@ -202,6 +210,10 @@ class NaviPaneState extends State<NaviPane>
Widget buildMainView() { Widget buildMainView() {
return HeroControllerScope( return HeroControllerScope(
controller: MaterialApp.createMaterialHeroController(), controller: MaterialApp.createMaterialHeroController(),
child: NavigatorPopHandler(
onPopWithResult: (result) {
widget.navigatorKey.currentState?.maybePop(result);
},
child: Navigator( child: Navigator(
observers: [widget.observer], observers: [widget.observer],
key: widget.navigatorKey, key: widget.navigatorKey,
@@ -212,6 +224,7 @@ class NaviPaneState extends State<NaviPane>
}, },
), ),
), ),
),
); );
} }
@@ -239,7 +252,7 @@ class NaviPaneState extends State<NaviPane>
icon: Icon(action.icon), icon: Icon(action.icon),
onPressed: action.onTap, onPressed: action.onTap,
), ),
) ),
], ],
), ),
), ),
@@ -261,9 +274,7 @@ class NaviPaneState extends State<NaviPane>
), ),
), ),
child: Row( child: Row(
children: List<Widget>.generate( children: List<Widget>.generate(widget.paneItems.length, (index) {
widget.paneItems.length,
(index) {
return Expanded( return Expanded(
child: _SingleBottomNaviWidget( child: _SingleBottomNaviWidget(
enabled: currentPage == index, enabled: currentPage == index,
@@ -274,8 +285,7 @@ class NaviPaneState extends State<NaviPane>
key: ValueKey(index), key: ValueKey(index),
), ),
); );
}, }),
),
), ),
), ),
); );
@@ -286,7 +296,8 @@ class NaviPaneState extends State<NaviPane>
const paddingHorizontal = 12.0; const paddingHorizontal = 12.0;
return Material( return Material(
child: Container( child: Container(
width: _kFoldedSideBarWidth + width:
_kFoldedSideBarWidth +
(_kSideBarWidth - _kFoldedSideBarWidth) * ((value - 2).clamp(0, 1)), (_kSideBarWidth - _kFoldedSideBarWidth) * ((value - 2).clamp(0, 1)),
height: double.infinity, height: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: paddingHorizontal), padding: const EdgeInsets.symmetric(horizontal: paddingHorizontal),
@@ -323,9 +334,7 @@ class NaviPaneState extends State<NaviPane>
key: ValueKey(index + widget.paneItems.length), key: ValueKey(index + widget.paneItems.length),
), ),
), ),
const SizedBox( const SizedBox(height: 16),
height: 16,
)
], ],
), ),
), ),
@@ -334,12 +343,13 @@ class NaviPaneState extends State<NaviPane>
} }
class _SideNaviWidget extends StatelessWidget { class _SideNaviWidget extends StatelessWidget {
const _SideNaviWidget( const _SideNaviWidget({
{required this.enabled, required this.enabled,
required this.entry, required this.entry,
required this.onTap, required this.onTap,
required this.showTitle, required this.showTitle,
super.key}); super.key,
});
final bool enabled; final bool enabled;
@@ -368,18 +378,18 @@ class _SideNaviWidget extends StatelessWidget {
? Row( ? Row(
children: [icon, const SizedBox(width: 12), Text(entry.label)], children: [icon, const SizedBox(width: 12), Text(entry.label)],
) )
: Align( : Align(alignment: Alignment.centerLeft, child: icon),
alignment: Alignment.centerLeft,
child: icon,
),
), ),
).paddingVertical(4); ).paddingVertical(4);
} }
} }
class _PaneActionWidget extends StatelessWidget { class _PaneActionWidget extends StatelessWidget {
const _PaneActionWidget( const _PaneActionWidget({
{required this.entry, required this.showTitle, super.key}); required this.entry,
required this.showTitle,
super.key,
});
final PaneActionEntry entry; final PaneActionEntry entry;
@@ -399,21 +409,19 @@ class _PaneActionWidget extends StatelessWidget {
? Row( ? Row(
children: [icon, const SizedBox(width: 12), Text(entry.label)], children: [icon, const SizedBox(width: 12), Text(entry.label)],
) )
: Align( : Align(alignment: Alignment.centerLeft, child: icon),
alignment: Alignment.centerLeft,
child: icon,
),
), ),
).paddingVertical(4); ).paddingVertical(4);
} }
} }
class _SingleBottomNaviWidget extends StatefulWidget { class _SingleBottomNaviWidget extends StatefulWidget {
const _SingleBottomNaviWidget( const _SingleBottomNaviWidget({
{required this.enabled, required this.enabled,
required this.entry, required this.entry,
required this.onTap, required this.onTap,
super.key}); super.key,
});
final bool enabled; final bool enabled;
@@ -482,8 +490,9 @@ class _SingleBottomNaviWidgetState extends State<_SingleBottomNaviWidget>
Widget buildContent() { Widget buildContent() {
final value = controller.value; final value = controller.value;
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final icon = final icon = Icon(
Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon); widget.enabled ? widget.entry.activeIcon : widget.entry.icon,
);
return Center( return Center(
child: Container( child: Container(
width: 64, width: 64,
@@ -570,8 +579,11 @@ class NaviObserver extends NavigatorObserver implements Listenable {
} }
class _NaviPopScope extends StatelessWidget { class _NaviPopScope extends StatelessWidget {
const _NaviPopScope( const _NaviPopScope({
{required this.child, this.popGesture = false, required this.action}); required this.child,
this.popGesture = false,
required this.action,
});
final Widget child; final Widget child;
final bool popGesture; final bool popGesture;
@@ -581,15 +593,7 @@ class _NaviPopScope extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget res = App.isIOS Widget res = child;
? child
: PopScope(
canPop: App.isAndroid ? false : true,
onPopInvokedWithResult: (value, result) {
action();
},
child: child,
);
if (popGesture) { if (popGesture) {
res = GestureDetector( res = GestureDetector(
onPanStart: (details) { onPanStart: (details) {
@@ -606,7 +610,8 @@ class _NaviPopScope extends StatelessWidget {
} }
panStartAtEdge = false; panStartAtEdge = false;
}, },
child: res); child: res,
);
} }
return res; return res;
} }

View File

@@ -237,7 +237,7 @@ class _AppScrollBarState extends State<AppScrollBar> {
double viewHeight = 0; double viewHeight = 0;
final _scrollIndicatorSize = App.isDesktop ? 42.0 : 64.0; final _scrollIndicatorSize = App.isDesktop ? 36.0 : 54.0;
late final VerticalDragGestureRecognizer _dragGestureRecognizer; late final VerticalDragGestureRecognizer _dragGestureRecognizer;
@@ -354,7 +354,7 @@ class _ScrollIndicatorPainter extends CustomPainter {
Offset(size.width, 0), Offset(size.width, 0),
radius: Radius.circular(size.width), radius: Radius.circular(size.width),
); );
canvas.drawShadow(path, shadowColor, 4, true); canvas.drawShadow(path, shadowColor, 2, true);
var backgroundPaint = Paint() var backgroundPaint = Paint()
..color = backgroundColor ..color = backgroundColor
..style = PaintingStyle.fill; ..style = PaintingStyle.fill;

View File

@@ -13,7 +13,7 @@ export "widget_utils.dart";
export "context.dart"; export "context.dart";
class _App { class _App {
final version = "1.5.0"; final version = "1.5.1";
bool get isAndroid => Platform.isAndroid; bool get isAndroid => Platform.isAndroid;

View File

@@ -2,6 +2,7 @@ import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:venera/foundation/app.dart';
const double _kBackGestureWidth = 20.0; const double _kBackGestureWidth = 20.0;
const int _kMaxDroppedSwipePageForwardAnimationTime = 800; const int _kMaxDroppedSwipePageForwardAnimationTime = 800;
@@ -115,7 +116,14 @@ mixin _AppRouteTransitionMixin<T> on PageRoute<T> {
@override @override
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
return SlidePageTransitionBuilder().buildTransitions( PageTransitionsBuilder builder;
if (App.isAndroid) {
builder = PredictiveBackPageTransitionsBuilder();
} else {
builder = SlidePageTransitionBuilder();
}
return builder.buildTransitions(
this, this,
context, context,
animation, animation,

View File

@@ -1,6 +1,8 @@
import 'dart:async' show Future; import 'dart:async' show Future;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:venera/foundation/comic_type.dart';
import 'package:venera/foundation/local.dart';
import 'package:venera/network/images.dart'; import 'package:venera/network/images.dart';
import 'package:venera/utils/io.dart'; import 'package:venera/utils/io.dart';
import 'base_image_provider.dart'; import 'base_image_provider.dart';
@@ -11,7 +13,12 @@ class CachedImageProvider
/// Image provider for normal image. /// Image provider for normal image.
/// ///
/// [url] is the url of the image. Local file path is also supported. /// [url] is the url of the image. Local file path is also supported.
const CachedImageProvider(this.url, {this.headers, this.sourceKey, this.cid}); const CachedImageProvider(this.url, {
this.headers,
this.sourceKey,
this.cid,
this.fallbackToLocalCover = false,
});
final String url; final String url;
@@ -21,6 +28,9 @@ class CachedImageProvider
final String? cid; final String? cid;
// Use local cover if network image fails to load.
final bool fallbackToLocalCover;
static int loadingCount = 0; static int loadingCount = 0;
static const _kMaxLoadingCount = 8; static const _kMaxLoadingCount = 8;
@@ -49,6 +59,24 @@ class CachedImageProvider
} }
throw "Error: Empty response body."; throw "Error: Empty response body.";
} }
catch(e) {
if (fallbackToLocalCover && sourceKey != null && cid != null) {
final localComic = LocalManager().find(
cid!,
ComicType.fromKey(sourceKey!),
);
if (localComic != null) {
var file = localComic.coverFile;
if (await file.exists()) {
var data = await file.readAsBytes();
if (data.isNotEmpty) {
return data;
}
}
}
}
rethrow;
}
finally { finally {
loadingCount--; loadingCount--;
} }

View File

@@ -248,7 +248,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
MediaQuery.of(context).viewPadding.top <= 0 || MediaQuery.of(context).viewPadding.top <= 0 ||
MediaQuery.of(context).viewPadding.top > 50; MediaQuery.of(context).viewPadding.top > 50;
if (isPaddingCheckError) { if (isPaddingCheckError && Platform.isAndroid) {
widget = MediaQuery( widget = MediaQuery(
data: MediaQuery.of(context).copyWith( data: MediaQuery.of(context).copyWith(
viewPadding: const EdgeInsets.only( viewPadding: const EdgeInsets.only(

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

View File

@@ -2,11 +2,11 @@ name: venera
description: "A comic app." description: "A comic app."
publish_to: 'none' publish_to: 'none'
version: 1.5.0+150 version: 1.5.1+151
environment: environment:
sdk: '>=3.8.0 <4.0.0' sdk: '>=3.8.0 <4.0.0'
flutter: 3.35.2 flutter: 3.35.3
dependencies: dependencies:
flutter: flutter: