mirror of
https://github.com/venera-app/venera.git
synced 2025-09-27 15:57:25 +00:00
show comments in comic details page
This commit is contained in:
@@ -880,8 +880,8 @@ function Comic({id, title, subtitle, subTitle, cover, tags, description, maxPage
|
|||||||
* @param cover {string}
|
* @param cover {string}
|
||||||
* @param description {string?}
|
* @param description {string?}
|
||||||
* @param tags {Map<string, string[]> | {} | null | undefined}
|
* @param tags {Map<string, string[]> | {} | null | undefined}
|
||||||
* @param chapters {Map<string, string> | {} | null | undefined}} - key: chapter id, value: chapter title
|
* @param chapters {Map<string, string> | {} | null | undefined} - key: chapter id, value: chapter title
|
||||||
* @param isFavorite {boolean | null | undefined}} - favorite status. If the comic source supports multiple folders, this field should be null
|
* @param isFavorite {boolean | null | undefined} - favorite status. If the comic source supports multiple folders, this field should be null
|
||||||
* @param subId {string?} - a param which is passed to comments api
|
* @param subId {string?} - a param which is passed to comments api
|
||||||
* @param thumbnails {string[]?} - for multiple page thumbnails, set this to null, and use `loadThumbnails` api to load thumbnails
|
* @param thumbnails {string[]?} - for multiple page thumbnails, set this to null, and use `loadThumbnails` api to load thumbnails
|
||||||
* @param recommend {Comic[]?} - related comics
|
* @param recommend {Comic[]?} - related comics
|
||||||
@@ -894,9 +894,10 @@ function Comic({id, title, subtitle, subTitle, cover, tags, description, maxPage
|
|||||||
* @param url {string?}
|
* @param url {string?}
|
||||||
* @param stars {number?} - 0-5, double
|
* @param stars {number?} - 0-5, double
|
||||||
* @param maxPage {number?}
|
* @param maxPage {number?}
|
||||||
|
* @param comments {Comment[]?}- `since 1.0.7` App will display comments in the details page.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ComicDetails({title, cover, description, tags, chapters, isFavorite, subId, thumbnails, recommend, commentCount, likesCount, isLiked, uploader, updateTime, uploadTime, url, stars, maxPage}) {
|
function ComicDetails({title, cover, description, tags, chapters, isFavorite, subId, thumbnails, recommend, commentCount, likesCount, isLiked, uploader, updateTime, uploadTime, url, stars, maxPage, comments}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.cover = cover;
|
this.cover = cover;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@@ -915,6 +916,7 @@ function ComicDetails({title, cover, description, tags, chapters, isFavorite, su
|
|||||||
this.url = url;
|
this.url = url;
|
||||||
this.stars = stars;
|
this.stars = stars;
|
||||||
this.maxPage = maxPage;
|
this.maxPage = maxPage;
|
||||||
|
this.comments = comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -160,6 +160,8 @@ class ComicDetails with HistoryMixin {
|
|||||||
@override
|
@override
|
||||||
final int? maxPage;
|
final int? maxPage;
|
||||||
|
|
||||||
|
final List<Comment>? comments;
|
||||||
|
|
||||||
static Map<String, List<String>> _generateMap(Map<dynamic, dynamic> map) {
|
static Map<String, List<String>> _generateMap(Map<dynamic, dynamic> map) {
|
||||||
var res = <String, List<String>>{};
|
var res = <String, List<String>>{};
|
||||||
map.forEach((key, value) {
|
map.forEach((key, value) {
|
||||||
@@ -193,7 +195,10 @@ class ComicDetails with HistoryMixin {
|
|||||||
updateTime = json["updateTime"],
|
updateTime = json["updateTime"],
|
||||||
url = json["url"],
|
url = json["url"],
|
||||||
stars = (json["stars"] as num?)?.toDouble(),
|
stars = (json["stars"] as num?)?.toDouble(),
|
||||||
maxPage = json["maxPage"];
|
maxPage = json["maxPage"],
|
||||||
|
comments = (json["comments"] as List?)
|
||||||
|
?.map((e) => Comment.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
|
@@ -115,6 +115,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
buildDescription(),
|
buildDescription(),
|
||||||
buildInfo(),
|
buildInfo(),
|
||||||
buildChapters(),
|
buildChapters(),
|
||||||
|
buildComments(),
|
||||||
buildThumbnails(),
|
buildThumbnails(),
|
||||||
buildRecommend(),
|
buildRecommend(),
|
||||||
SliverPadding(padding: EdgeInsets.only(bottom: context.padding.bottom)),
|
SliverPadding(padding: EdgeInsets.only(bottom: context.padding.bottom)),
|
||||||
@@ -287,7 +288,7 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
onLongPressed: quickFavorite,
|
onLongPressed: quickFavorite,
|
||||||
iconColor: context.useTextColor(Colors.purple),
|
iconColor: context.useTextColor(Colors.purple),
|
||||||
),
|
),
|
||||||
if (comicSource.commentsLoader != null)
|
if (comicSource.commentsLoader != null && comic.comments == null)
|
||||||
_ActionButton(
|
_ActionButton(
|
||||||
icon: const Icon(Icons.comment),
|
icon: const Icon(Icons.comment),
|
||||||
text: (comic.commentsCount ?? 'Comments'.tl).toString(),
|
text: (comic.commentsCount ?? 'Comments'.tl).toString(),
|
||||||
@@ -549,6 +550,16 @@ class _ComicPageState extends LoadingState<ComicPage, ComicDetails>
|
|||||||
SliverGridComics(comics: comic.recommend!),
|
SliverGridComics(comics: comic.recommend!),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildComments() {
|
||||||
|
if (comic.comments == null || comic.comments!.isEmpty) {
|
||||||
|
return const SliverPadding(padding: EdgeInsets.zero);
|
||||||
|
}
|
||||||
|
return _CommentsPart(
|
||||||
|
comments: comic.comments!,
|
||||||
|
showMore: showComments,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract mixin class _ComicPageActions {
|
abstract mixin class _ComicPageActions {
|
||||||
@@ -1670,3 +1681,148 @@ class _SelectDownloadChapterState extends State<_SelectDownloadChapter> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CommentsPart extends StatefulWidget {
|
||||||
|
const _CommentsPart({
|
||||||
|
required this.comments,
|
||||||
|
required this.showMore,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<Comment> comments;
|
||||||
|
|
||||||
|
final void Function() showMore;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_CommentsPart> createState() => _CommentsPartState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CommentsPartState extends State<_CommentsPart> {
|
||||||
|
final scrollController = ScrollController();
|
||||||
|
|
||||||
|
late List<Comment> comments;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
comments = widget.comments;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiSliver(
|
||||||
|
children: [
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: ListTile(
|
||||||
|
title: Text("Comments".tl),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.chevron_left),
|
||||||
|
onPressed: () {
|
||||||
|
scrollController.animateTo(
|
||||||
|
scrollController.position.pixels - 340,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.chevron_right),
|
||||||
|
onPressed: () {
|
||||||
|
scrollController.animateTo(
|
||||||
|
scrollController.position.pixels + 340,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 184,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemCount: comments.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _CommentWidget(comment: comments[index]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_ActionButton(
|
||||||
|
icon: const Icon(Icons.comment),
|
||||||
|
text: "View more".tl,
|
||||||
|
onPressed: widget.showMore,
|
||||||
|
iconColor: context.useTextColor(Colors.green),
|
||||||
|
).fixHeight(48).paddingRight(8).toAlign(Alignment.centerRight),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SliverToBoxAdapter(
|
||||||
|
child: Divider(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CommentWidget extends StatelessWidget {
|
||||||
|
const _CommentWidget({required this.comment});
|
||||||
|
|
||||||
|
final Comment comment;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: double.infinity,
|
||||||
|
margin: const EdgeInsets.fromLTRB(16, 8, 0, 8),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
width: 324,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.surfaceContainerLow,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (comment.avatar != null)
|
||||||
|
Container(
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
color: context.colorScheme.surfaceContainer,
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: Image(
|
||||||
|
image: CachedImageProvider(comment.avatar!),
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
).paddingRight(8),
|
||||||
|
Text(comment.userName, style: ts.bold),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Expanded(
|
||||||
|
child: RichCommentContent(text: comment.content).fixWidth(324),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
if (comment.time != null)
|
||||||
|
Text(comment.time!, style: ts.s12).toAlign(Alignment.centerLeft),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -510,7 +510,7 @@ class _CommentContent extends StatelessWidget {
|
|||||||
if (!text.contains('<') && !text.contains('http')) {
|
if (!text.contains('<') && !text.contains('http')) {
|
||||||
return SelectableText(text);
|
return SelectableText(text);
|
||||||
} else {
|
} else {
|
||||||
return _RichCommentContent(text: text);
|
return RichCommentContent(text: text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,16 +610,16 @@ class _CommentImage {
|
|||||||
const _CommentImage(this.url, this.link);
|
const _CommentImage(this.url, this.link);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RichCommentContent extends StatefulWidget {
|
class RichCommentContent extends StatefulWidget {
|
||||||
const _RichCommentContent({required this.text});
|
const RichCommentContent({super.key, required this.text});
|
||||||
|
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_RichCommentContent> createState() => _RichCommentContentState();
|
State<RichCommentContent> createState() => _RichCommentContentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RichCommentContentState extends State<_RichCommentContent> {
|
class _RichCommentContentState extends State<RichCommentContent> {
|
||||||
var textSpan = <InlineSpan>[];
|
var textSpan = <InlineSpan>[];
|
||||||
var images = <_CommentImage>[];
|
var images = <_CommentImage>[];
|
||||||
|
|
||||||
@@ -639,6 +639,8 @@ class _RichCommentContentState extends State<_RichCommentContent> {
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
var buffer = StringBuffer();
|
var buffer = StringBuffer();
|
||||||
var text = widget.text;
|
var text = widget.text;
|
||||||
|
text = text.replaceAll('\r\n', '\n');
|
||||||
|
text = text.replaceAll('&', '&');
|
||||||
|
|
||||||
void writeBuffer() {
|
void writeBuffer() {
|
||||||
if (buffer.isEmpty) return;
|
if (buffer.isEmpty) return;
|
||||||
|
Reference in New Issue
Block a user