diff --git a/lib/config.dart b/lib/config.dart index c04b1c9..0c8906f 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -17,6 +17,7 @@ const DB_NAME = 'db_manhuagui'; const DL_NTFC_ID = 'com.aoihosizora.manhuagui:download'; const DL_NTFC_NAME = '漫画下载通知'; const DL_NTFC_DESCRIPTION = '显示当前的漫画下载进度'; +const LOG_CONSOLE_BUFFER = 200; const DEBUG_ERROR = true; const CONNECT_TIMEOUT = 5000; // 5.0s (local -> my server) diff --git a/lib/page/download_select.dart b/lib/page/download_select.dart index 514b3e3..4c4406c 100644 --- a/lib/page/download_select.dart +++ b/lib/page/download_select.dart @@ -227,7 +227,7 @@ class _DownloadSelectPageState extends State { physics: AlwaysScrollableScrollPhysics(), children: [ WarningTextView( - text: '本应用为第三方漫画柜客户端,请不要连续下载过多章节,避免因短时间内的频繁访问而导致当前的IP被漫画柜封禁。', + text: '本应用为第三方漫画柜客户端,请不要连续下载过多章节,避免因短时间内的频繁访问而导致当前IP被漫画柜封禁。', isWarning: true, ), MangaTocView( diff --git a/lib/page/manga_viewer.dart b/lib/page/manga_viewer.dart index 719ec4b..ca426a2 100644 --- a/lib/page/manga_viewer.dart +++ b/lib/page/manga_viewer.dart @@ -789,7 +789,6 @@ class _MangaViewerPageState extends State with AutomaticKeepAli ), ActionRowView.four( compact: true, - shrink: false, textColor: Colors.white, iconColor: Colors.white, action1: ActionItem( diff --git a/lib/page/message.dart b/lib/page/message.dart index 51b8899..f908d4b 100644 --- a/lib/page/message.dart +++ b/lib/page/message.dart @@ -9,7 +9,7 @@ import 'package:manhuagui_flutter/service/dio/retrofit.dart'; import 'package:manhuagui_flutter/service/dio/wrap_error.dart'; import 'package:manhuagui_flutter/service/prefs/message.dart'; -/// 历史消息页 +/// 历史消息页,网络请求并展示 [Message] 信息 class MessagePage extends StatefulWidget { const MessagePage({Key? key}) : super(key: key); @@ -42,16 +42,18 @@ class _MessagePageState extends State { _total = result.data.data.length; if (mounted) setState(() {}); - await _loadReadMangas(); + await _loadReadMangas(data: result.data.data); return result.data.data; } - Future _loadReadMangas() async { + Future _loadReadMangas({List? data}) async { var messages = await MessagePrefs.getReadMessages(); _readMessages.clear(); _readMessages.addAll(messages); - _unreadCount = _data.length - _data.where((el) => _readMessages.contains(el.mid)).length; + + data ??= _data; + _unreadCount = data.length - data.where((msg) => _readMessages.contains(msg.mid)).length; if (mounted) setState(() {}); } @@ -69,6 +71,7 @@ class _MessagePageState extends State { var r = await MessagePrefs.addReadMessages(_data.map((m) => m.mid).toList()); _readMessages.clear(); _readMessages.addAll(r); + _unreadCount = 0; if (mounted) setState(() {}); }, ), @@ -94,6 +97,7 @@ class _MessagePageState extends State { Navigator.of(c).pop(); await MessagePrefs.clearReadMessages(); _readMessages.clear(); + _unreadCount = _data.length; if (mounted) setState(() {}); }, ), diff --git a/lib/page/page/dl_setting.dart b/lib/page/page/dl_setting.dart index a9066f7..4499384 100644 --- a/lib/page/page/dl_setting.dart +++ b/lib/page/page/dl_setting.dart @@ -102,7 +102,7 @@ class _DlSettingSubPageState extends State with SettingSubPage ), SettingComboBoxView( title: '同时下载的页面数量', - hint: '本应用为第三方漫画柜客户端,请不要同时下载过多页面,避免因短时间内的频繁访问而导致当前的IP被漫画柜封禁。', + hint: '本应用为第三方漫画柜客户端,请不要同时下载过多页面,避免因短时间内的频繁访问而导致当前IP被漫画柜封禁。', width: 75, value: _downloadPagesTogether.clamp(1, 8), values: List.generate(8, (i) => i + 1), @@ -118,7 +118,7 @@ class _DlSettingSubPageState extends State with SettingSubPage ), SettingButtonView( title: '漫画下载存储路径', - hint: _lowerThanAndroidR == null + hint: _lowerThanAndroidR == null || _lowerThanAndroidR == true ? null // : '当前设备搭载着 Android 11 或以上版本的系统。\n\n由于 Android 系统限制,漫画将被下载至应用私有沙盒存储中。当卸载本应用时,若需要保留已下载的漫画,请选择保留本应用的数据。', buttonChild: Text('查看'), diff --git a/lib/page/page/glb_setting.dart b/lib/page/page/glb_setting.dart index 8b1ce28..35fa0ef 100644 --- a/lib/page/page/glb_setting.dart +++ b/lib/page/page/glb_setting.dart @@ -48,7 +48,7 @@ class GlbSetting { if (!s.enableLogger) { LogConsolePage.finalize(); } else if (!LogConsolePage.initialized) { - LogConsolePage.initialize(globalLogger, bufferSize: 100); + LogConsolePage.initialize(globalLogger, bufferSize: LOG_CONSOLE_BUFFER); globalLogger.i('initialize LogConsolePage'); } } @@ -157,7 +157,7 @@ class _GlbSettingSubPageState extends State with SettingSubPa hint: '当前设置对应的漫画下载超时时间为:' + (_dlTimeoutBehavior == TimeoutBehavior.normal ? '${DOWNLOAD_HEAD_TIMEOUT / 1000}s + ${DOWNLOAD_IMAGE_TIMEOUT / 1000}s' - : _timeoutBehavior == TimeoutBehavior.long + : _dlTimeoutBehavior == TimeoutBehavior.long ? '${DOWNLOAD_HEAD_LTIMEOUT / 1000}s + ${DOWNLOAD_IMAGE_LTIMEOUT / 1000}s' : '无超时时间设置'), width: 75, diff --git a/lib/page/page/view_extra.dart b/lib/page/page/view_extra.dart index 79f3ae2..749acd2 100644 --- a/lib/page/page/view_extra.dart +++ b/lib/page/page/view_extra.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:manhuagui_flutter/model/chapter.dart'; +import 'package:manhuagui_flutter/page/view/action_row.dart'; import 'package:manhuagui_flutter/page/view/network_image.dart'; /// 漫画章节阅读页-额外页 @@ -118,67 +119,47 @@ class ViewExtraSubPage extends StatelessWidget { } Widget _buildActions(BuildContext context) { - Widget _buildAction({required String text, required IconData icon, required void Function() action, bool enable = true}) { - return InkWell( - onTap: enable ? action : null, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: Column( - children: [ - Container( - height: 48, - width: 48, - decoration: BoxDecoration( - border: Border.all(width: 0.8, color: Colors.grey[400]!), - shape: BoxShape.circle, - ), - child: Icon( - icon, - size: 22, - color: enable ? Colors.grey[800] : Colors.grey, - ), - ), - SizedBox(height: 10), - Text( - text, - style: TextStyle(color: enable ? Colors.black : Colors.grey), - ), - ], - ), - ), - ); - } - - return Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildAction( - text: '结束阅读', - icon: Icons.arrow_back, - action: () => toPop.call(), + return ActionRowView.five( + iconBuilder: (action) => Container( + height: 45, + width: 45, + margin: EdgeInsets.only(bottom: 3), + decoration: BoxDecoration( + border: Border.all(width: 0.8, color: Colors.grey[400]!), + shape: BoxShape.circle, ), - _buildAction( - text: !subscribed ? '订阅漫画' : '取消订阅', - icon: !subscribed ? Icons.star_border : Icons.star, - action: () => toSubscribe.call(), - enable: !subscribing, + child: Icon( + action.icon, + size: 22, + color: action.enable ? Colors.grey[800] : Colors.grey, ), - _buildAction( - text: '下载漫画', - icon: Icons.download, - action: () => toDownload.call(), - ), - _buildAction( - text: '漫画目录', - icon: Icons.menu, - action: () => toShowToc.call(), - ), - _buildAction( - text: '查看评论', - icon: Icons.forum, - action: () => toShowComments.call(), - ), - ], + ), + action1: ActionItem( + text: '结束阅读', + icon: Icons.arrow_back, + action: () => toPop.call(), + ), + action2: ActionItem( + text: !subscribed ? '订阅漫画' : '取消订阅', + icon: !subscribed ? Icons.star_border : Icons.star, + action: () => toSubscribe.call(), + enable: !subscribing, + ), + action3: ActionItem( + text: '下载漫画', + icon: Icons.download, + action: () => toDownload.call(), + ), + action4: ActionItem( + text: '漫画目录', + icon: Icons.menu, + action: () => toShowToc.call(), + ), + action5: ActionItem( + text: '查看评论', + icon: Icons.forum, + action: () => toShowComments.call(), + ), ); } @@ -336,10 +317,10 @@ class ViewExtraSubPage extends StatelessWidget { SizedBox(height: 18), Container( color: Colors.white, - padding: EdgeInsets.symmetric(horizontal: 15, vertical: 18 - 6), + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 18 - 6 - 8), child: Material( color: Colors.transparent, - child: _buildActions(context), // InkWell vertical padding: 6 + child: _buildActions(context), // InkWell vertical padding: 6, ActionRowView vertical padding: 8 ), ), ], diff --git a/lib/page/splash.dart b/lib/page/splash.dart index 718b62c..47d8b2d 100644 --- a/lib/page/splash.dart +++ b/lib/page/splash.dart @@ -3,7 +3,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:manhuagui_flutter/config.dart'; -import 'package:manhuagui_flutter/model/message.dart'; import 'package:manhuagui_flutter/page/page/glb_setting.dart'; import 'package:manhuagui_flutter/page/view/message_dialog.dart'; import 'package:manhuagui_flutter/service/db/db_manager.dart'; @@ -35,6 +34,9 @@ class SplashPage extends StatefulWidget { static void remove() => FlutterNativeSplash.remove(); static Future prepare() async { + // 0. fake delay + await Future.delayed(Duration(milliseconds: 500)); + // 1. check permission var ok = await _checkPermission(); if (!ok) { @@ -63,8 +65,14 @@ class SplashPage extends StatefulWidget { // 1. register context for notification NotificationManager.instance.registerContext(context); - // 2. check message asynchronously - _checkLatestMessage(context); + // 2. check latest message asynchronously + Future.microtask(() async { + try { + await _checkLatestMessage(context); + } catch (e, s) { + wrapError(e, s); // ignored + } + }); // 3. check auth asynchronously Future.microtask(() async { @@ -78,26 +86,19 @@ class SplashPage extends StatefulWidget { static Future _checkLatestMessage(BuildContext context) async { var readMessages = await MessagePrefs.getReadMessages(); final client = RestClient(DioManager.instance.dio); - LatestMessage lm; - try { - var result = await client.getLatestMessage(); - lm = result.data; - } catch (e, s) { - wrapError(e, s); // ignored - return; - } + var m = (await client.getLatestMessage()).data; - if (lm.mustUpgradeNewVersion != null && isVersionNewer(lm.mustUpgradeNewVersion!.newVersion!.version, APP_VERSION) == true) { - await showNewVersionDialog(context: context, newVersion: lm.mustUpgradeNewVersion!); + if (m.mustUpgradeNewVersion != null && isVersionNewer(m.mustUpgradeNewVersion!.newVersion!.version, APP_VERSION) == true) { + await showNewVersionDialog(context: context, newVersion: m.mustUpgradeNewVersion!); } - if (lm.notDismissibleNotification != null && !readMessages.contains(lm.notDismissibleNotification!.mid)) { - await showNotificationDialog(context: context, notification: lm.notDismissibleNotification!); + if (m.notDismissibleNotification != null && !readMessages.contains(m.notDismissibleNotification!.mid)) { + await showNotificationDialog(context: context, notification: m.notDismissibleNotification!); } - if (lm.newVersion != null && !readMessages.contains(lm.newVersion!.mid) && isVersionNewer(lm.newVersion!.newVersion!.version, APP_VERSION) == true) { - await showNewVersionDialog(context: context, newVersion: lm.newVersion!); + if (m.newVersion != null && !readMessages.contains(m.newVersion!.mid) && isVersionNewer(m.newVersion!.newVersion!.version, APP_VERSION) == true) { + await showNewVersionDialog(context: context, newVersion: m.newVersion!); } - if (lm.notification != null && !readMessages.contains(lm.notification!.mid)) { - await showNotificationDialog(context: context, notification: lm.notification!); + if (m.notification != null && !readMessages.contains(m.notification!.mid)) { + await showNotificationDialog(context: context, notification: m.notification!); } } } diff --git a/lib/page/view/action_row.dart b/lib/page/view/action_row.dart index cb60d2e..9b6ff37 100644 --- a/lib/page/view/action_row.dart +++ b/lib/page/view/action_row.dart @@ -37,11 +37,11 @@ class ActionRowView extends StatelessWidget { required this.action3, required this.action4, this.compact = false, - this.shrink = true, this.textColor, this.iconColor, this.disabledTextColor, this.disabledIconColor, + this.iconBuilder, }) : action5 = null, super(key: key); @@ -53,11 +53,11 @@ class ActionRowView extends StatelessWidget { required this.action4, required ActionItem this.action5, this.compact = false, - this.shrink = true, this.textColor, this.iconColor, this.disabledTextColor, this.disabledIconColor, + this.iconBuilder, }) : super(key: key); final ActionItem action1; @@ -66,11 +66,11 @@ class ActionRowView extends StatelessWidget { final ActionItem action4; final ActionItem? action5; final bool compact; - final bool shrink; final Color? textColor; final Color? iconColor; final Color? disabledTextColor; final Color? disabledIconColor; + final Widget Function(ActionItem action)? iconBuilder; Widget _buildAction(BuildContext context, ActionItem action) { return InkWell( @@ -84,18 +84,17 @@ class ActionRowView extends StatelessWidget { alignment: IconTextAlignment.t2b, space: compact ? 2 // compact - : action5 == null - ? 8 // four - : 5 /* five */, - icon: Transform.rotate( - angle: action.rotateAngle, - child: Icon( - action.icon, - color: action.enable - ? (textColor ?? Colors.black54) // enabled - : (disabledTextColor ?? Colors.grey) /* disabled */, - ), - ), + : 8 /* normal */, + icon: iconBuilder?.call(action) ?? + Transform.rotate( + angle: action.rotateAngle, + child: Icon( + action.icon, + color: action.enable + ? (textColor ?? Colors.black54) // enabled + : (disabledTextColor ?? Colors.grey) /* disabled */, + ), + ), text: Text( action.text, style: Theme.of(context).textTheme.bodyText1!.copyWith( @@ -115,14 +114,12 @@ class ActionRowView extends StatelessWidget { color: Colors.transparent, child: Padding( padding: compact - ? EdgeInsets.zero // compact - : action5 == null // normal - ? EdgeInsets.symmetric(horizontal: 25, vertical: 8) // four - : EdgeInsets.symmetric(horizontal: 15, vertical: 5) /* five */, + ? EdgeInsets.symmetric(horizontal: 0, vertical: 0) // compact + : EdgeInsets.symmetric(horizontal: 0, vertical: 8) /* normal */, child: Row( - mainAxisAlignment: shrink - ? MainAxisAlignment.spaceBetween // shrink - : MainAxisAlignment.spaceAround /* normal */, + mainAxisAlignment: compact + ? MainAxisAlignment.spaceAround // compact + : MainAxisAlignment.spaceEvenly /* normal */, children: [ _buildAction(context, action1), _buildAction(context, action2), diff --git a/lib/page/view/manga_rating.dart b/lib/page/view/manga_rating.dart index e77c98b..416ebf9 100644 --- a/lib/page/view/manga_rating.dart +++ b/lib/page/view/manga_rating.dart @@ -49,75 +49,71 @@ class MangaRatingDetailView extends StatelessWidget { @override Widget build(BuildContext context) { - final width = MediaQuery.of(context).size.width - // - (MediaQuery.of(context).padding + kDialogDefaultInsetPadding + kAlertDialogDefaultContentPadding).horizontal; + final width = MediaQuery.of(context).size.width - (MediaQuery.of(context).padding + kDialogDefaultInsetPadding + kAlertDialogDefaultContentPadding).horizontal; final barWidth = width * 0.6; - return SizedBox( - width: width, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - RatingBar.builder( - itemCount: 5, - itemBuilder: (c, i) => Icon(Icons.star, color: Colors.amber), - initialRating: averageScore / 2.0, - itemSize: 32, - itemPadding: EdgeInsets.symmetric(horizontal: 2), - allowHalfRating: true, - ignoreGestures: true, - onRatingUpdate: (_) {}, - ), - SizedBox(width: 10), - Text( - averageScore.toString(), - style: Theme.of(context).textTheme.bodyText1?.copyWith( - fontSize: 28, - color: Colors.orangeAccent, - ), - ), - ], - ), - SizedBox(height: 2), - Align( - alignment: Alignment.centerRight, - child: Text( - '共 $scoreCount 人评分', - style: Theme.of(context).textTheme.bodyText2, + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + RatingBar.builder( + itemCount: 5, + itemBuilder: (c, i) => Icon(Icons.star, color: Colors.amber), + initialRating: averageScore / 2.0, + itemSize: 32, + itemPadding: EdgeInsets.symmetric(horizontal: 2), + allowHalfRating: true, + ignoreGestures: true, + onRatingUpdate: (_) {}, ), - ), - Divider(height: 16, thickness: 1), - for (var i = 4; i >= 0; i--) - Padding( - padding: EdgeInsets.only(bottom: i == 0 ? 0 : 5), - child: Row( - children: [ - RatingBar.builder( - itemCount: 5, - itemBuilder: (c, i) => Icon(Icons.star, color: Colors.amber), - initialRating: (i + 1).toDouble(), - itemSize: 16, - allowHalfRating: false, - ignoreGestures: true, - onRatingUpdate: (_) {}, - ), - Container( - width: barWidth * (double.tryParse(perScores[i + 1].replaceAll('%', '')) ?? 0) / 100, - height: 16, - color: Colors.amber, - margin: EdgeInsets.only(left: 4, right: 6), - ), - Text( - perScores[i + 1], - style: Theme.of(context).textTheme.bodyText2, + SizedBox(width: 10), + Text( + averageScore.toString(), + style: Theme.of(context).textTheme.bodyText1?.copyWith( + fontSize: 28, + color: Colors.orangeAccent, ), - ], - ), ), - ], - ), + ], + ), + SizedBox(height: 2), + Align( + alignment: Alignment.centerRight, + child: Text( + '共 $scoreCount 人评分', + style: Theme.of(context).textTheme.bodyText2, + ), + ), + Divider(height: 16, thickness: 1), + for (var i = 4; i >= 0; i--) + Padding( + padding: EdgeInsets.only(bottom: i == 0 ? 0 : 5), + child: Row( + children: [ + RatingBar.builder( + itemCount: 5, + itemBuilder: (c, i) => Icon(Icons.star, color: Colors.amber), + initialRating: (i + 1).toDouble(), + itemSize: 16, + allowHalfRating: false, + ignoreGestures: true, + onRatingUpdate: (_) {}, + ), + Container( + width: barWidth * (double.tryParse(perScores[i + 1].replaceAll('%', '')) ?? 0) / 100, + height: 16, + color: Colors.amber, + margin: EdgeInsets.only(left: 4, right: 6), + ), + Text( + perScores[i + 1], + style: Theme.of(context).textTheme.bodyText2, + ), + ], + ), + ), + ], ); } } diff --git a/lib/page/view/message_dialog.dart b/lib/page/view/message_dialog.dart index db52d38..eed85b0 100644 --- a/lib/page/view/message_dialog.dart +++ b/lib/page/view/message_dialog.dart @@ -32,9 +32,15 @@ Future showNewVersionDialog({ crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '发布于:${msg.createdAtString}\n', - style: Theme.of(context).textTheme.bodyText2, + '发布于:${msg.createdAtString}', + style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 13, color: Colors.grey), ), + if (msg.createdAt != msg.updatedAt) + Text( + '更新于:${msg.updatedAtString}', + style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 13, color: Colors.grey), + ), + SizedBox(height: kDividerDefaultHeight), Text( cnt.changeLogs.isEmpty ? '无版本更新日志' : cnt.changeLogs, ), @@ -112,9 +118,15 @@ Future showNotificationDialog({ crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '发布于:${msg.createdAtString}\n', - style: Theme.of(context).textTheme.bodyText2, + '发布于:${msg.createdAtString}', + style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 13, color: Colors.grey), ), + if (msg.createdAt != msg.updatedAt) + Text( + '更新于:${msg.updatedAtString}', + style: Theme.of(context).textTheme.bodyText2?.copyWith(fontSize: 13, color: Colors.grey), + ), + SizedBox(height: kDividerDefaultHeight), Text(cnt.content), ], ), diff --git a/lib/page/view/message_line.dart b/lib/page/view/message_line.dart index 96dfdfe..e785fb0 100644 --- a/lib/page/view/message_line.dart +++ b/lib/page/view/message_line.dart @@ -116,11 +116,12 @@ class MessageLineView extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, ), - SizedBox(height: 4), + SizedBox(height: 6), Text( '发布于:${message.createdAtString}', style: Theme.of(context).textTheme.bodyText2?.copyWith( - color: Theme.of(context).primaryColor, + fontSize: 13, + color: Colors.grey, ), ), ],