diff --git a/android/app/build.gradle b/android/app/build.gradle index f1097ef..78dcc3a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -44,8 +44,11 @@ android { applicationId "com.aoihosizora.manhuagui" minSdkVersion 16 targetSdkVersion 29 - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + // versionCode flutterVersionCode.toInteger() + // versionName flutterVersionName + + versionCode 3 + versionName "1.0.2" } signingConfigs { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 01249d8..a634996 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ diff --git a/lib/model/comment.dart b/lib/model/comment.dart index bc77c5b..2b7aeb5 100644 --- a/lib/model/comment.dart +++ b/lib/model/comment.dart @@ -22,6 +22,10 @@ class Comment { Map toJson() => _$CommentToJson(this); static const fields = ['cid', 'uid', 'username', 'avatar', 'gender', 'content', 'like_count', 'reply_count', 'comment_time', 'reply_timeline']; + + RepliedComment toRepliedComment() { + return RepliedComment(cid: cid, uid: uid, username: username, avatar: avatar, gender: gender, content: content, likeCount: likeCount, replyCount: replyCount, commentTime: commentTime); + } } @JsonSerializable(fieldRename: FieldRename.snake) diff --git a/lib/model/manga.dart b/lib/model/manga.dart index 51becd7..6f05a4d 100644 --- a/lib/model/manga.dart +++ b/lib/model/manga.dart @@ -17,6 +17,7 @@ class Manga { List genres; List authors; String alias; + String aliasTitle; bool finished; String newestChapter; String newestDate; @@ -30,34 +31,13 @@ class Manga { bool copyright; List chapterGroups; - Manga( - {this.mid, - this.title, - this.cover, - this.url, - this.publishYear, - this.mangaZone, - this.genres, - this.authors, - this.alias, - this.finished, - this.newestChapter, - this.newestDate, - this.briefIntroduction, - this.introduction, - this.mangaRank, - this.averageScore, - this.scoreCount, - this.perScores, - this.banned, - this.copyright, - this.chapterGroups}); + Manga({this.mid, this.title, this.cover, this.url, this.publishYear, this.mangaZone, this.genres, this.authors, this.alias, this.aliasTitle, this.finished, this.newestChapter, this.newestDate, this.briefIntroduction, this.introduction, this.mangaRank, this.averageScore, this.scoreCount, this.perScores, this.banned, this.copyright, this.chapterGroups}); factory Manga.fromJson(Map json) => _$MangaFromJson(json); Map toJson() => _$MangaToJson(this); - static const fields = ['mid', 'title', 'cover', 'url', 'publish_year', 'manga_zone', 'genres', 'authors', 'alias', 'finished', 'newest_chapter', 'newest_date', 'brief_introduction', 'introduction', 'manga_rank', 'average_score', 'score_count', 'per_scores', 'banned', 'copyright', 'chapter_groups']; + static const fields = ['mid', 'title', 'cover', 'url', 'publish_year', 'manga_zone', 'genres', 'authors', 'alias', 'alias_title', 'finished', 'newest_chapter', 'newest_date', 'brief_introduction', 'introduction', 'manga_rank', 'average_score', 'score_count', 'per_scores', 'banned', 'copyright', 'chapter_groups']; } @JsonSerializable(fieldRename: FieldRename.snake) diff --git a/lib/page/comment.dart b/lib/page/comment.dart new file mode 100644 index 0000000..6c0097e --- /dev/null +++ b/lib/page/comment.dart @@ -0,0 +1,210 @@ +import 'package:flutter/material.dart'; +import 'package:manhuagui_flutter/model/comment.dart'; +import 'package:manhuagui_flutter/page/view/network_image.dart'; +import 'package:manhuagui_flutter/service/natives/clipboard.dart'; + +/// 评论详情页 +class CommentPage extends StatefulWidget { + const CommentPage({ + Key key, + @required this.comment, + }) : assert(comment != null), + super(key: key); + + final Comment comment; + + @override + _CommentPageState createState() => _CommentPageState(); +} + +class _CommentPageState extends State { + Widget _buildLine({@required RepliedComment comment, @required int idx}) { + assert(comment != null); + assert(idx != null); + return Stack( + children: [ + Container( + color: Colors.white, + padding: EdgeInsets.only(top: 15, bottom: 15, left: 15, right: 15), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipOval( + child: NetworkImageView( + url: comment.avatar, + height: 40, + width: 40, + fit: BoxFit.cover, + ), + ), + SizedBox(width: 15), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // **************************************************************** + // 第一行 + // **************************************************************** + Container( + width: MediaQuery.of(context).size.width - 3 * 15 - 40, // | ▢▢ ▢▢▢▢▢ | + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // **************************************************************** + // 用户名 性别 + // **************************************************************** + Expanded( + child: Row( + children: [ + Flexible( + child: Text( + comment.username == '-' ? '匿名用户' : comment.username, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.subtitle1, + ), + ), + SizedBox(width: 8), + Container( + decoration: BoxDecoration( + color: comment.gender == 1 ? Colors.blue[300] : Colors.red[400], + borderRadius: BorderRadius.all(Radius.circular(3)), + ), + height: 18, + width: 18, + child: Center( + child: Text( + widget.comment.gender == 1 ? '♂' : '♀', + style: TextStyle(fontSize: 14, color: Colors.white), + ), + ), + ), + ], + ), + ), + // **************************************************************** + // 楼层 + // **************************************************************** + Container( + decoration: BoxDecoration( + color: Theme.of(context).primaryColor, + borderRadius: BorderRadius.all(Radius.circular(3)), + ), + height: 18, + width: 26, + child: Center( + child: Text( + '#$idx', + style: TextStyle( + color: Colors.white, + fontSize: 14, + ), + ), + ), + ), + ], + ), + ), + SizedBox(height: 15), + // **************************************************************** + // 评论内容 + // **************************************************************** + Container( + width: MediaQuery.of(context).size.width - 3 * 15 - 40, + child: Text( + comment.content, + style: Theme.of(context).textTheme.subtitle1, + ), + ), + SizedBox(height: 15), + // **************************************************************** + // 评论数据 + // **************************************************************** + Container( + width: MediaQuery.of(context).size.width - 3 * 15 - 40, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + comment.commentTime, + style: TextStyle(color: Colors.grey), + ), + Row( + children: [ + Icon( + Icons.thumb_up, + color: Colors.grey[400], + size: 16, + ), + SizedBox(width: 4), + Text(comment.likeCount.toString()), + SizedBox(width: 10), + Icon( + Icons.chat_bubble, + color: Colors.grey[400], + size: 16, + ), + SizedBox(width: 4), + Text(comment.replyCount.toString()), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => copyText(comment.content), + ), + ), + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + toolbarHeight: 45, + title: Text('评论详情'), + ), + body: Container( + width: MediaQuery.of(context).size.width, + child: ListView( + children: [ + _buildLine( + comment: widget.comment.toRepliedComment(), + idx: widget.comment.replyTimeline.length + 1, + ), + Container(height: 12), + if (widget.comment.replyTimeline.length > 0) + for (var i = 0; i < widget.comment.replyTimeline.length - 1; i++) ...[ + _buildLine( + comment: widget.comment.replyTimeline[i], + idx: i + 1, + ), + Container( + color: Colors.white, + padding: EdgeInsets.only(left: 2.0 * 15 + 40), + width: MediaQuery.of(context).size.width - 3 * 15 - 40, + child: Divider(height: 1, thickness: 1), + ), + ], + if (widget.comment.replyTimeline.length > 0) + _buildLine( + comment: widget.comment.replyTimeline.last, + idx: widget.comment.replyTimeline.length, + ), + ], + ), + ), + ); + } +} diff --git a/lib/page/manga.dart b/lib/page/manga.dart index 64179e9..70c1b59 100644 --- a/lib/page/manga.dart +++ b/lib/page/manga.dart @@ -126,6 +126,16 @@ class _MangaPageState extends State { chapterPage: 0, ), ).catchError((_) {}); + } else { + updateHistory( + username: AuthState.instance.username, + history: MangaHistory( + mangaId: _data.mid, + mangaTitle: _data.title ?? '?', + mangaCover: _data.cover ?? '?', + mangaUrl: _data.url ?? '', + ), + ); } }).catchError((e) { _data = null; diff --git a/lib/page/view/comment_line.dart b/lib/page/view/comment_line.dart index 84f268a..9f58a23 100644 --- a/lib/page/view/comment_line.dart +++ b/lib/page/view/comment_line.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:manhuagui_flutter/model/comment.dart'; +import 'package:manhuagui_flutter/page/comment.dart'; import 'package:manhuagui_flutter/page/view/network_image.dart'; /// View for [Comment]. @@ -29,6 +29,9 @@ class _CommentLineViewState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // **************************************************************** + // 头像 + // **************************************************************** ClipOval( child: NetworkImageView( url: widget.comment.avatar, @@ -41,11 +44,17 @@ class _CommentLineViewState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // **************************************************************** + // 第一行 + // **************************************************************** Container( width: MediaQuery.of(context).size.width - 3 * 12 - 32, // | ▢▢ ▢▢▢▢▢ | child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ + // **************************************************************** + // 用户名 性别 + // **************************************************************** Expanded( child: Row( children: [ @@ -75,6 +84,9 @@ class _CommentLineViewState extends State { ], ), ), + // **************************************************************** + // 楼层 + // **************************************************************** if (widget.comment.replyTimeline.length > 0) Container( margin: EdgeInsets.only(right: 8), @@ -98,6 +110,9 @@ class _CommentLineViewState extends State { ), ), SizedBox(height: 8), + // **************************************************************** + // 评论内容 + // **************************************************************** Container( width: MediaQuery.of(context).size.width - 3 * 12 - 32, child: Text( @@ -106,6 +121,9 @@ class _CommentLineViewState extends State { ), ), if (widget.comment.replyTimeline.length > 0) SizedBox(height: 10), + // **************************************************************** + // 楼层 + // **************************************************************** if (widget.comment.replyTimeline.length > 0) Container( width: MediaQuery.of(context).size.width - 3 * 12 - 32, @@ -117,6 +135,9 @@ class _CommentLineViewState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // **************************************************************** + // 每一楼 + // **************************************************************** for (var line in widget.comment.replyTimeline.sublist(0, widget.comment.replyTimeline.length <= 3 ? widget.comment.replyTimeline.length : 3)) Padding( padding: EdgeInsets.only(bottom: 4), @@ -169,6 +190,9 @@ class _CommentLineViewState extends State { ), ), SizedBox(height: 10), + // **************************************************************** + // 评论信息 + // **************************************************************** Container( width: MediaQuery.of(context).size.width - 3 * 12 - 32, child: Row( @@ -205,11 +229,20 @@ class _CommentLineViewState extends State { ], ), ), + // **************************************************************** + // 点击效果 + // **************************************************************** Positioned.fill( child: Material( color: Colors.transparent, child: InkWell( - onTap: () => Fluttertoast.showToast(msg: 'TODO'), + onTap: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (c) => CommentPage( + comment: widget.comment, + ), + ), + ), ), ), ), diff --git a/lib/service/database/history.dart b/lib/service/database/history.dart index a777e0b..a81bdeb 100644 --- a/lib/service/database/history.dart +++ b/lib/service/database/history.dart @@ -137,6 +137,20 @@ Future addHistory({@required String username, @required MangaHistory histo return rows >= 1; } +Future updateHistory({@required String username, @required MangaHistory history}) async { + username ??= ''; + assert(history != null && history.mangaId != null); + + var db = await DBProvider.instance.getDB(); + var rows = await db.rawUpdate( + '''UPDATE $_tblHistory + SET $_colMangaTitle = ?, $_colMangaCover = ?, $_colMangaUrl = ? + WHERE $_colUsername = ? AND $_colMangaId = ?''', + [history.mangaTitle, history.mangaCover, history.mangaUrl, username, history.mangaId], + ).catchError((_) {}); + return rows >= 1; +} + Future deleteHistory({@required String username, @required int mid}) async { username ??= ''; assert(mid != null); diff --git a/pubspec.yaml b/pubspec.yaml index ffb7a5b..315becf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: manhuagui_flutter description: An unofficial application for manhuagui written in flutter publish_to: 'none' -version: 1.0.0+1 +version: 1.0.2 environment: sdk: ">=2.7.0 <3.0.0"