mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
Compare commits
3 Commits
v1.5.1-dev
...
e179c8f67f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e179c8f67f | ||
![]() |
c4b85471c1 | ||
![]() |
a898b57d96 |
@@ -34,12 +34,6 @@ android {
|
|||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion "28.0.13004108"
|
ndkVersion "28.0.13004108"
|
||||||
|
|
||||||
packaging {
|
|
||||||
jniLibs {
|
|
||||||
useLegacyPackaging true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
splits{
|
splits{
|
||||||
abi {
|
abi {
|
||||||
reset()
|
reset()
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
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
|
||||||
@@ -59,6 +58,8 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
<!-- [flutter 3.27.1] Impeller is still worse than skia, disable it -->
|
||||||
|
<meta-data android:name="io.flutter.embedding.android.EnableImpeller" android:value="false"/>
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
@@ -17,7 +17,6 @@ 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;
|
||||||
|
@@ -7,7 +7,6 @@ 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;
|
||||||
@@ -18,8 +17,6 @@ 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);
|
||||||
@@ -70,16 +67,9 @@ class NetworkError extends StatelessWidget {
|
|||||||
child: Text('Verify'.tl),
|
child: Text('Verify'.tl),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Row(
|
FilledButton(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
onPressed: retry,
|
||||||
children: [
|
child: Text(buttonText ?? 'Retry'.tl),
|
||||||
if (action != null)
|
|
||||||
action!.paddingRight(8),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: retry,
|
|
||||||
child: Text(buttonText ?? 'Retry'.tl),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@@ -7,11 +7,8 @@ class PaneItemEntry {
|
|||||||
|
|
||||||
IconData activeIcon;
|
IconData activeIcon;
|
||||||
|
|
||||||
PaneItemEntry({
|
PaneItemEntry(
|
||||||
required this.label,
|
{required this.label, required this.icon, required this.activeIcon});
|
||||||
required this.icon,
|
|
||||||
required this.activeIcon,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PaneActionEntry {
|
class PaneActionEntry {
|
||||||
@@ -21,24 +18,20 @@ class PaneActionEntry {
|
|||||||
|
|
||||||
VoidCallback onTap;
|
VoidCallback onTap;
|
||||||
|
|
||||||
PaneActionEntry({
|
PaneActionEntry(
|
||||||
required this.label,
|
{required this.label, required this.icon, required this.onTap});
|
||||||
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;
|
||||||
|
|
||||||
@@ -194,8 +187,7 @@ class NaviPaneState extends State<NaviPane>
|
|||||||
child: buildLeft(),
|
child: buildLeft(),
|
||||||
),
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
left:
|
left: _kFoldedSideBarWidth * ((value - 1).clamp(0, 1)) +
|
||||||
_kFoldedSideBarWidth * ((value - 1).clamp(0, 1)) +
|
|
||||||
(_kSideBarWidth - _kFoldedSideBarWidth) *
|
(_kSideBarWidth - _kFoldedSideBarWidth) *
|
||||||
((value - 2).clamp(0, 1)),
|
((value - 2).clamp(0, 1)),
|
||||||
child: buildMainView(),
|
child: buildMainView(),
|
||||||
@@ -210,19 +202,14 @@ class NaviPaneState extends State<NaviPane>
|
|||||||
Widget buildMainView() {
|
Widget buildMainView() {
|
||||||
return HeroControllerScope(
|
return HeroControllerScope(
|
||||||
controller: MaterialApp.createMaterialHeroController(),
|
controller: MaterialApp.createMaterialHeroController(),
|
||||||
child: NavigatorPopHandler(
|
child: Navigator(
|
||||||
onPopWithResult: (result) {
|
observers: [widget.observer],
|
||||||
widget.navigatorKey.currentState?.maybePop(result);
|
key: widget.navigatorKey,
|
||||||
},
|
onGenerateRoute: (settings) => AppPageRoute(
|
||||||
child: Navigator(
|
preventRebuild: false,
|
||||||
observers: [widget.observer],
|
builder: (context) {
|
||||||
key: widget.navigatorKey,
|
return _NaviMainView(state: this);
|
||||||
onGenerateRoute: (settings) => AppPageRoute(
|
},
|
||||||
preventRebuild: false,
|
|
||||||
builder: (context) {
|
|
||||||
return _NaviMainView(state: this);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -252,7 +239,7 @@ class NaviPaneState extends State<NaviPane>
|
|||||||
icon: Icon(action.icon),
|
icon: Icon(action.icon),
|
||||||
onPressed: action.onTap,
|
onPressed: action.onTap,
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -274,18 +261,21 @@ class NaviPaneState extends State<NaviPane>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: List<Widget>.generate(widget.paneItems.length, (index) {
|
children: List<Widget>.generate(
|
||||||
return Expanded(
|
widget.paneItems.length,
|
||||||
child: _SingleBottomNaviWidget(
|
(index) {
|
||||||
enabled: currentPage == index,
|
return Expanded(
|
||||||
entry: widget.paneItems[index],
|
child: _SingleBottomNaviWidget(
|
||||||
onTap: () {
|
enabled: currentPage == index,
|
||||||
updatePage(index);
|
entry: widget.paneItems[index],
|
||||||
},
|
onTap: () {
|
||||||
key: ValueKey(index),
|
updatePage(index);
|
||||||
),
|
},
|
||||||
);
|
key: ValueKey(index),
|
||||||
}),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -296,8 +286,7 @@ class NaviPaneState extends State<NaviPane>
|
|||||||
const paddingHorizontal = 12.0;
|
const paddingHorizontal = 12.0;
|
||||||
return Material(
|
return Material(
|
||||||
child: Container(
|
child: Container(
|
||||||
width:
|
width: _kFoldedSideBarWidth +
|
||||||
_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),
|
||||||
@@ -334,7 +323,9 @@ class NaviPaneState extends State<NaviPane>
|
|||||||
key: ValueKey(index + widget.paneItems.length),
|
key: ValueKey(index + widget.paneItems.length),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -343,13 +334,12 @@ 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;
|
||||||
|
|
||||||
@@ -378,18 +368,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(alignment: Alignment.centerLeft, child: icon),
|
: Align(
|
||||||
|
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.entry, required this.showTitle, super.key});
|
||||||
required this.showTitle,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
final PaneActionEntry entry;
|
final PaneActionEntry entry;
|
||||||
|
|
||||||
@@ -409,19 +399,21 @@ 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(alignment: Alignment.centerLeft, child: icon),
|
: Align(
|
||||||
|
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;
|
||||||
|
|
||||||
@@ -490,9 +482,8 @@ 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 = Icon(
|
final icon =
|
||||||
widget.enabled ? widget.entry.activeIcon : widget.entry.icon,
|
Icon(widget.enabled ? widget.entry.activeIcon : widget.entry.icon);
|
||||||
);
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 64,
|
width: 64,
|
||||||
@@ -579,11 +570,8 @@ class NaviObserver extends NavigatorObserver implements Listenable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NaviPopScope extends StatelessWidget {
|
class _NaviPopScope extends StatelessWidget {
|
||||||
const _NaviPopScope({
|
const _NaviPopScope(
|
||||||
required this.child,
|
{required this.child, this.popGesture = false, required this.action});
|
||||||
this.popGesture = false,
|
|
||||||
required this.action,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final bool popGesture;
|
final bool popGesture;
|
||||||
@@ -593,25 +581,32 @@ class _NaviPopScope extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget res = child;
|
Widget res = App.isIOS
|
||||||
|
? 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) {
|
||||||
if (details.globalPosition.dx < 64) {
|
if (details.globalPosition.dx < 64) {
|
||||||
panStartAtEdge = true;
|
panStartAtEdge = true;
|
||||||
}
|
|
||||||
},
|
|
||||||
onPanEnd: (details) {
|
|
||||||
if (details.velocity.pixelsPerSecond.dx < 0 ||
|
|
||||||
details.velocity.pixelsPerSecond.dx > 0) {
|
|
||||||
if (panStartAtEdge) {
|
|
||||||
action();
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
panStartAtEdge = false;
|
onPanEnd: (details) {
|
||||||
},
|
if (details.velocity.pixelsPerSecond.dx < 0 ||
|
||||||
child: res,
|
details.velocity.pixelsPerSecond.dx > 0) {
|
||||||
);
|
if (panStartAtEdge) {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panStartAtEdge = false;
|
||||||
|
},
|
||||||
|
child: res);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@@ -237,7 +237,7 @@ class _AppScrollBarState extends State<AppScrollBar> {
|
|||||||
|
|
||||||
double viewHeight = 0;
|
double viewHeight = 0;
|
||||||
|
|
||||||
final _scrollIndicatorSize = App.isDesktop ? 36.0 : 54.0;
|
final _scrollIndicatorSize = App.isDesktop ? 42.0 : 64.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, 2, true);
|
canvas.drawShadow(path, shadowColor, 4, true);
|
||||||
var backgroundPaint = Paint()
|
var backgroundPaint = Paint()
|
||||||
..color = backgroundColor
|
..color = backgroundColor
|
||||||
..style = PaintingStyle.fill;
|
..style = PaintingStyle.fill;
|
||||||
|
@@ -13,7 +13,7 @@ export "widget_utils.dart";
|
|||||||
export "context.dart";
|
export "context.dart";
|
||||||
|
|
||||||
class _App {
|
class _App {
|
||||||
final version = "1.5.1";
|
final version = "1.5.0";
|
||||||
|
|
||||||
bool get isAndroid => Platform.isAndroid;
|
bool get isAndroid => Platform.isAndroid;
|
||||||
|
|
||||||
|
@@ -2,7 +2,6 @@ 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;
|
||||||
@@ -116,14 +115,7 @@ 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) {
|
||||||
PageTransitionsBuilder builder;
|
return SlidePageTransitionBuilder().buildTransitions(
|
||||||
if (App.isAndroid) {
|
|
||||||
builder = PredictiveBackPageTransitionsBuilder();
|
|
||||||
} else {
|
|
||||||
builder = SlidePageTransitionBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.buildTransitions(
|
|
||||||
this,
|
this,
|
||||||
context,
|
context,
|
||||||
animation,
|
animation,
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
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';
|
||||||
@@ -13,12 +11,7 @@ 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, {
|
const CachedImageProvider(this.url, {this.headers, this.sourceKey, this.cid});
|
||||||
this.headers,
|
|
||||||
this.sourceKey,
|
|
||||||
this.cid,
|
|
||||||
this.fallbackToLocalCover = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String url;
|
final String url;
|
||||||
|
|
||||||
@@ -28,9 +21,6 @@ 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;
|
||||||
@@ -59,24 +49,6 @@ 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--;
|
||||||
}
|
}
|
||||||
|
@@ -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(
|
||||||
|
@@ -77,10 +77,8 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onReadEnd() {
|
void onReadEnd() {
|
||||||
history ??= HistoryManager().find(
|
history ??=
|
||||||
widget.id,
|
HistoryManager().find(widget.id, ComicType(widget.sourceKey.hashCode));
|
||||||
ComicType(widget.sourceKey.hashCode),
|
|
||||||
);
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,32 +93,6 @@ 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);
|
||||||
@@ -142,8 +114,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
ComicDetails get comic => data!;
|
ComicDetails get comic => data!;
|
||||||
|
|
||||||
void onScroll() {
|
void onScroll() {
|
||||||
var offset =
|
var offset = scrollController.position.pixels -
|
||||||
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) {
|
||||||
@@ -174,11 +145,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
floatingActionButton: showFAB
|
floatingActionButton: showFAB
|
||||||
? FloatingActionButton(
|
? FloatingActionButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
scrollController.animateTo(
|
scrollController.animateTo(0,
|
||||||
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),
|
||||||
)
|
)
|
||||||
@@ -195,9 +164,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
buildThumbnails(),
|
buildThumbnails(),
|
||||||
buildRecommend(),
|
buildRecommend(),
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(bottom: context.padding.bottom + 80), // Add additional padding for FAB
|
||||||
bottom: context.padding.bottom + 80,
|
|
||||||
), // Add additional padding for FAB
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -223,9 +190,12 @@ 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 ??
|
History.fromModel(
|
||||||
History.fromModel(model: localComic, ep: 0, page: 0),
|
model: localComic,
|
||||||
|
ep: 0,
|
||||||
|
page: 0,
|
||||||
|
),
|
||||||
author: localComic.subTitle ?? '',
|
author: localComic.subTitle ?? '',
|
||||||
tags: localComic.tags,
|
tags: localComic.tags,
|
||||||
);
|
);
|
||||||
@@ -245,10 +215,8 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
widget.id,
|
widget.id,
|
||||||
ComicType(widget.sourceKey.hashCode),
|
ComicType(widget.sourceKey.hashCode),
|
||||||
);
|
);
|
||||||
history = HistoryManager().find(
|
history =
|
||||||
widget.id,
|
HistoryManager().find(widget.id, ComicType(widget.sourceKey.hashCode));
|
||||||
ComicType(widget.sourceKey.hashCode),
|
|
||||||
);
|
|
||||||
return comicSource.loadComicInfo!(widget.id);
|
return comicSource.loadComicInfo!(widget.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +225,11 @@ 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(comic.id, comic.comicType, 0);
|
isDownloaded = LocalManager().isDownloaded(
|
||||||
|
comic.id,
|
||||||
|
comic.comicType,
|
||||||
|
0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,9 +242,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: showMoreActions,
|
onPressed: showMoreActions, icon: const Icon(Icons.more_horiz))
|
||||||
icon: const Icon(Icons.more_horiz),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -318,10 +288,8 @@ 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(
|
SelectableText(comic.subTitle!, style: ts.s14)
|
||||||
comic.subTitle!,
|
.paddingVertical(4),
|
||||||
style: ts.s14,
|
|
||||||
).paddingVertical(4),
|
|
||||||
Text(
|
Text(
|
||||||
(ComicSource.find(comic.sourceKey)?.name) ?? '',
|
(ComicSource.find(comic.sourceKey)?.name) ?? '',
|
||||||
style: ts.s12,
|
style: ts.s12,
|
||||||
@@ -370,11 +338,10 @@ 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:
|
text: ((data!.likesCount != null)
|
||||||
((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(),
|
|
||||||
isLoading: isLiking,
|
isLoading: isLiking,
|
||||||
onPressed: likeOrUnlike,
|
onPressed: likeOrUnlike,
|
||||||
iconColor: context.useTextColor(Colors.red),
|
iconColor: context.useTextColor(Colors.red),
|
||||||
@@ -416,11 +383,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: hasHistory
|
child: hasHistory
|
||||||
? FilledButton(
|
? FilledButton(
|
||||||
onPressed: continueRead,
|
onPressed: continueRead, child: Text("Continue".tl))
|
||||||
child: Text("Continue".tl),
|
|
||||||
)
|
|
||||||
: FilledButton(onPressed: read, child: Text("Read".tl)),
|
: FilledButton(onPressed: read, child: Text("Read".tl)),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
).paddingHorizontal(16).paddingVertical(8),
|
).paddingHorizontal(16).paddingVertical(8),
|
||||||
if (history != null)
|
if (history != null)
|
||||||
@@ -447,20 +412,19 @@ 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(
|
groupName = comic.chapters!.groups.elementAt(group - 1);
|
||||||
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
|
||||||
@@ -489,7 +453,9 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
return SliverLazyToBoxAdapter(
|
return SliverLazyToBoxAdapter(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(title: Text("Description".tl)),
|
ListTile(
|
||||||
|
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),
|
||||||
@@ -573,7 +539,10 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(color: color, borderRadius: borderRadius),
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
),
|
||||||
child: Text(text).padding(padding),
|
child: Text(text).padding(padding),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -583,13 +552,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(
|
return DateTime.fromMillisecondsSinceEpoch(t)
|
||||||
t,
|
.toString()
|
||||||
).toString().substring(0, 19);
|
.substring(0, 19);
|
||||||
} else {
|
} else {
|
||||||
return DateTime.fromMillisecondsSinceEpoch(
|
return DateTime.fromMillisecondsSinceEpoch(t * 1000)
|
||||||
t * 1000,
|
.toString()
|
||||||
).toString().substring(0, 19);
|
.substring(0, 19);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (time.contains('T') || time.contains('Z')) {
|
if (time.contains('T') || time.contains('Z')) {
|
||||||
@@ -614,11 +583,17 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ListTile(title: Text("Information".tl)),
|
ListTile(
|
||||||
|
title: Text("Information".tl),
|
||||||
|
),
|
||||||
if (comic.stars != null)
|
if (comic.stars != null)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
StarRating(value: comic.stars!, size: 24, onTap: starRating),
|
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)),
|
||||||
],
|
],
|
||||||
@@ -696,19 +671,24 @@ 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(
|
return SliverMainAxisGroup(slivers: [
|
||||||
slivers: [
|
SliverToBoxAdapter(
|
||||||
SliverToBoxAdapter(child: ListTile(title: Text("Related".tl))),
|
child: ListTile(
|
||||||
SliverGridComics(comics: comic.recommend!),
|
title: Text("Related".tl),
|
||||||
],
|
),
|
||||||
);
|
),
|
||||||
|
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(comments: comic.comments!, showMore: showComments);
|
return _CommentsPart(
|
||||||
|
comments: comic.comments!,
|
||||||
|
showMore: showComments,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,21 +792,20 @@ class _SelectDownloadChapterState extends State<_SelectDownloadChapter> {
|
|||||||
itemCount: widget.eps.length,
|
itemCount: widget.eps.length,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
return CheckboxListTile(
|
return CheckboxListTile(
|
||||||
title: Text(widget.eps[i]),
|
title: Text(widget.eps[i]),
|
||||||
value:
|
value: selected.contains(i) ||
|
||||||
selected.contains(i) || widget.downloadedEps.contains(i),
|
widget.downloadedEps.contains(i),
|
||||||
onChanged: widget.downloadedEps.contains(i)
|
onChanged: widget.downloadedEps.contains(i)
|
||||||
? null
|
? null
|
||||||
: (v) {
|
: (v) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (selected.contains(i)) {
|
if (selected.contains(i)) {
|
||||||
selected.remove(i);
|
selected.remove(i);
|
||||||
} else {
|
} else {
|
||||||
selected.add(i);
|
selected.add(i);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -834,7 +813,9 @@ class _SelectDownloadChapterState extends State<_SelectDownloadChapter> {
|
|||||||
height: 50,
|
height: 50,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(color: context.colorScheme.outlineVariant),
|
top: BorderSide(
|
||||||
|
color: context.colorScheme.outlineVariant,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -899,12 +880,8 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget buildContainer(
|
Widget buildContainer(double? width, double? height,
|
||||||
double? width,
|
{Color? color, double? radius}) {
|
||||||
double? height, {
|
|
||||||
Color? color,
|
|
||||||
double? radius,
|
|
||||||
}) {
|
|
||||||
return Container(
|
return Container(
|
||||||
height: height,
|
height: height,
|
||||||
width: width,
|
width: width,
|
||||||
@@ -946,9 +923,13 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
|
|||||||
if (context.width < changePoint)
|
if (context.width < changePoint)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: buildContainer(null, 36, radius: 18)),
|
Expanded(
|
||||||
|
child: buildContainer(null, 36, radius: 18),
|
||||||
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
Expanded(child: buildContainer(null, 36, radius: 18)),
|
Expanded(
|
||||||
|
child: buildContainer(null, 36, radius: 18),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
).paddingHorizontal(16),
|
).paddingHorizontal(16),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
@@ -957,7 +938,7 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
|
|||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeWidth: 2.4,
|
strokeWidth: 2.4,
|
||||||
).fixHeight(24).fixWidth(24),
|
).fixHeight(24).fixWidth(24),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -967,7 +948,11 @@ class _ComicPageLoadingPlaceHolder extends StatelessWidget {
|
|||||||
Widget child;
|
Widget child;
|
||||||
if (cover != null) {
|
if (cover != null) {
|
||||||
child = AnimatedImage(
|
child = AnimatedImage(
|
||||||
image: CachedImageProvider(cover!, sourceKey: sourceKey, cid: cid),
|
image: CachedImageProvider(
|
||||||
|
cover!,
|
||||||
|
sourceKey: sourceKey,
|
||||||
|
cid: cid,
|
||||||
|
),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
|
@@ -512,18 +512,6 @@ 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,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@@ -2,11 +2,11 @@ name: venera
|
|||||||
description: "A comic app."
|
description: "A comic app."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.5.1+151
|
version: 1.5.0+150
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
flutter: 3.35.3
|
flutter: 3.35.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
Reference in New Issue
Block a user