From ebc5dcedbe1773aacd54e62dcaf6a193c7d6d19a Mon Sep 17 00:00:00 2001 From: John Wesley Date: Wed, 17 Jan 2024 16:40:14 -0500 Subject: [PATCH] Implement single comment view --- lib/src/api/comments.dart | 14 +++++ lib/src/api/post_comments.dart | 14 +++++ lib/src/screens/entries/entry_comment.dart | 21 +++++++ .../screens/entries/entry_comment_screen.dart | 63 +++++++++++++++++++ lib/src/screens/posts/post_comment.dart | 21 +++++++ .../screens/posts/post_comment_screen.dart | 63 +++++++++++++++++++ lib/src/widgets/content_item.dart | 11 ++++ lib/src/widgets/text_editor.dart | 1 + 8 files changed, 208 insertions(+) create mode 100644 lib/src/screens/entries/entry_comment_screen.dart create mode 100644 lib/src/screens/posts/post_comment_screen.dart diff --git a/lib/src/api/comments.dart b/lib/src/api/comments.dart index edbe6dc..42667d0 100644 --- a/lib/src/api/comments.dart +++ b/lib/src/api/comments.dart @@ -24,6 +24,20 @@ Future fetchComments( jsonDecode(response.body) as Map); } +Future fetchComment( + http.Client client, + String instanceHost, + int commentId, +) async { + final response = + await client.get(Uri.https(instanceHost, '/api/comments/$commentId')); + + httpErrorHandler(response, message: 'Failed to load comment'); + + return EntryCommentModel.fromJson( + jsonDecode(response.body) as Map); +} + Future putVote( http.Client client, String instanceHost, diff --git a/lib/src/api/post_comments.dart b/lib/src/api/post_comments.dart index a224006..c153884 100644 --- a/lib/src/api/post_comments.dart +++ b/lib/src/api/post_comments.dart @@ -24,6 +24,20 @@ Future fetchComments( jsonDecode(response.body) as Map); } +Future fetchComment( + http.Client client, + String instanceHost, + int commentId, +) async { + final response = await client + .get(Uri.https(instanceHost, '/api/post-comments/$commentId')); + + httpErrorHandler(response, message: 'Failed to load comment'); + + return PostCommentModel.fromJson( + jsonDecode(response.body) as Map); +} + Future putVote( http.Client client, String instanceHost, diff --git a/lib/src/screens/entries/entry_comment.dart b/lib/src/screens/entries/entry_comment.dart index d82c8e5..1f5e31f 100644 --- a/lib/src/screens/entries/entry_comment.dart +++ b/lib/src/screens/entries/entry_comment.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/api/comments.dart' as api_comments; import 'package:interstellar/src/models/entry_comment.dart'; +import 'package:interstellar/src/screens/entries/entry_comment_screen.dart'; import 'package:interstellar/src/screens/settings/settings_controller.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item.dart'; @@ -72,6 +73,13 @@ class _EntryCommentState extends State { children: widget.comment.children, )); }), + openContentLabel: 'Open comment', + onOpenContent: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + EntryCommentScreen(widget.comment.commentId), + ), + ), onReply: whenLoggedIn(context, (body) async { var newSubComment = await api_comments.postComment( context.read().httpClient, @@ -125,6 +133,19 @@ class _EntryCommentState extends State { : null, ), ), + if (widget.comment.childCount > 0 && + !_isCollapsed && + (widget.comment.children?.isEmpty ?? false)) + TextButton( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + EntryCommentScreen(widget.comment.commentId), + ), + ), + child: Text( + 'Open ${widget.comment.childCount} reply${widget.comment.childCount == 1 ? '' : 's'}'), + ), if (widget.comment.childCount > 0 && !_isCollapsed) Container( margin: const EdgeInsets.only(left: 1), diff --git a/lib/src/screens/entries/entry_comment_screen.dart b/lib/src/screens/entries/entry_comment_screen.dart new file mode 100644 index 0000000..d04ab40 --- /dev/null +++ b/lib/src/screens/entries/entry_comment_screen.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/comments.dart'; +import 'package:interstellar/src/models/entry_comment.dart'; +import 'package:interstellar/src/screens/entries/entry_comment.dart'; +import 'package:interstellar/src/screens/settings/settings_controller.dart'; +import 'package:provider/provider.dart'; + +class EntryCommentScreen extends StatefulWidget { + const EntryCommentScreen( + this.commentId, { + super.key, + }); + + final int commentId; + + @override + State createState() => _EntryCommentScreenState(); +} + +class _EntryCommentScreenState extends State { + EntryCommentModel? _comment; + + @override + void initState() { + super.initState(); + + fetchComment(context.read().httpClient, + context.read().instanceHost, widget.commentId) + .then((value) => setState(() { + _comment = value; + })); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Comment${_comment != null ? ' by ${_comment!.user.username}' : ''}'), + ), + body: _comment != null + ? ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + child: EntryComment( + _comment!, + (newComment) => setState(() { + _comment = newComment; + }), + ), + ) + ], + ) + : const Center( + child: CircularProgressIndicator(), + ), + ); + } +} diff --git a/lib/src/screens/posts/post_comment.dart b/lib/src/screens/posts/post_comment.dart index 4fe63a0..34fe275 100644 --- a/lib/src/screens/posts/post_comment.dart +++ b/lib/src/screens/posts/post_comment.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/api/post_comments.dart' as api_comments; import 'package:interstellar/src/models/post_comment.dart'; +import 'package:interstellar/src/screens/posts/post_comment_screen.dart'; import 'package:interstellar/src/screens/settings/settings_controller.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item.dart'; @@ -72,6 +73,13 @@ class _EntryCommentState extends State { children: widget.comment.children, )); }), + openContentLabel: 'Open comment', + onOpenContent: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + PostCommentScreen(widget.comment.commentId), + ), + ), onReply: whenLoggedIn(context, (body) async { var newSubComment = await api_comments.postComment( context.read().httpClient, @@ -125,6 +133,19 @@ class _EntryCommentState extends State { : null, ), ), + if (widget.comment.childCount > 0 && + !_isCollapsed && + (widget.comment.children?.isEmpty ?? false)) + TextButton( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + PostCommentScreen(widget.comment.commentId), + ), + ), + child: Text( + 'Open ${widget.comment.childCount} reply${widget.comment.childCount == 1 ? '' : 's'}'), + ), if (widget.comment.childCount > 0 && !_isCollapsed) Container( margin: const EdgeInsets.only(left: 1), diff --git a/lib/src/screens/posts/post_comment_screen.dart b/lib/src/screens/posts/post_comment_screen.dart new file mode 100644 index 0000000..d01043a --- /dev/null +++ b/lib/src/screens/posts/post_comment_screen.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/post_comments.dart'; +import 'package:interstellar/src/models/post_comment.dart'; +import 'package:interstellar/src/screens/posts/post_comment.dart'; +import 'package:interstellar/src/screens/settings/settings_controller.dart'; +import 'package:provider/provider.dart'; + +class PostCommentScreen extends StatefulWidget { + const PostCommentScreen( + this.commentId, { + super.key, + }); + + final int commentId; + + @override + State createState() => _PostCommentScreenState(); +} + +class _PostCommentScreenState extends State { + PostCommentModel? _comment; + + @override + void initState() { + super.initState(); + + fetchComment(context.read().httpClient, + context.read().instanceHost, widget.commentId) + .then((value) => setState(() { + _comment = value; + })); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Comment${_comment != null ? ' by ${_comment!.user.username}' : ''}'), + ), + body: _comment != null + ? ListView( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + child: PostComment( + _comment!, + (newComment) => setState(() { + _comment = newComment; + }), + ), + ) + ], + ) + : const Center( + child: CircularProgressIndicator(), + ), + ); + } +} diff --git a/lib/src/widgets/content_item.dart b/lib/src/widgets/content_item.dart index 8be999f..b7c9f9c 100644 --- a/lib/src/widgets/content_item.dart +++ b/lib/src/widgets/content_item.dart @@ -46,6 +46,8 @@ class ContentItem extends StatefulWidget { final int? numComments; final Future Function(String)? onReply; + final String? openContentLabel; + final Future Function()? onOpenContent; final Future Function(String)? onEdit; final Future Function()? onDelete; @@ -79,6 +81,8 @@ class ContentItem extends StatefulWidget { this.isDownVoted = false, this.onDownVote, this.numComments, + this.openContentLabel, + this.onOpenContent, this.onReply, this.onEdit, this.onDelete, @@ -313,6 +317,13 @@ class _ContentItemState extends State { }, controller: _menuController, menuChildren: [ + if (widget.openContentLabel != null) + MenuItemButton( + onPressed: widget.onOpenContent, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text(widget.openContentLabel!)), + ), MenuItemButton( onPressed: widget.onEdit != null ? () => setState(() { diff --git a/lib/src/widgets/text_editor.dart b/lib/src/widgets/text_editor.dart index 1d50407..1f8a8bf 100644 --- a/lib/src/widgets/text_editor.dart +++ b/lib/src/widgets/text_editor.dart @@ -22,6 +22,7 @@ class TextEditor extends StatelessWidget { controller: controller, keyboardType: keyboardType ?? (isMarkdown ? TextInputType.multiline : null), + minLines: isMarkdown ? 2 : null, maxLines: isMarkdown ? null : 1, decoration: InputDecoration( border: const OutlineInputBorder(),