From 315216714b6b3540f4a0d80050c899bc35e95a89 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 16 May 2019 18:17:27 +0200 Subject: [PATCH 01/65] :fire: remove refactored files --- lib/pages/home/pages/post_comments/post.dart | 444 ---------------- ...ts_linked.dart => post_comments_page.dart} | 490 +++++------------- 2 files changed, 141 insertions(+), 793 deletions(-) delete mode 100644 lib/pages/home/pages/post_comments/post.dart rename lib/pages/home/pages/post_comments/{post_comments_linked.dart => post_comments_page.dart} (52%) diff --git a/lib/pages/home/pages/post_comments/post.dart b/lib/pages/home/pages/post_comments/post.dart deleted file mode 100644 index 2b2708b43..000000000 --- a/lib/pages/home/pages/post_comments/post.dart +++ /dev/null @@ -1,444 +0,0 @@ -import 'package:Openbook/models/community.dart'; -import 'package:Openbook/models/post.dart'; -import 'package:Openbook/models/post_comment.dart'; -import 'package:Openbook/models/user.dart'; -import 'package:Openbook/pages/home/pages/post_comments/widgets/post-commenter.dart'; -import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; -import 'package:Openbook/services/theme.dart'; -import 'package:Openbook/services/theme_value_parser.dart'; -import 'package:Openbook/services/user_preferences.dart'; -import 'package:Openbook/widgets/alerts/alert.dart'; -import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; -import 'package:Openbook/widgets/page_scaffold.dart'; -import 'package:Openbook/provider.dart'; -import 'package:Openbook/services/toast.dart'; -import 'package:Openbook/services/user.dart'; -import 'package:Openbook/widgets/theming/primary_color_container.dart'; -import 'package:Openbook/widgets/theming/secondary_text.dart'; -import 'package:Openbook/widgets/theming/text.dart'; -import 'package:async/async.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:Openbook/widgets/load_more.dart'; -import 'package:Openbook/services/httpie.dart'; - -class OBPostCommentsPage extends StatefulWidget { - final Post post; - final bool autofocusCommentInput; - - OBPostCommentsPage( - this.post, { - this.autofocusCommentInput: false, - }); - - @override - State createState() { - return OBPostCommentsPageState(); - } -} - -class OBPostCommentsPageState extends State { - UserService _userService; - ToastService _toastService; - ThemeService _themeService; - UserPreferencesService _userPreferencesService; - ThemeValueParserService _themeValueParserService; - GlobalKey _refreshIndicatorKey; - - ScrollController _postCommentsScrollController; - List _postComments = []; - bool _noMoreItemsToLoad; - bool _needsBootstrap; - FocusNode _commentInputFocusNode; - PostCommentsSortType _currentSort; - - CancelableOperation _refreshCommentsOperation; - CancelableOperation _refreshPostOperation; - CancelableOperation _loadMoreBottomCommentsOperation; - CancelableOperation _toggleSortCommentsOperation; - - @override - void initState() { - super.initState(); - _postCommentsScrollController = ScrollController(); - _refreshIndicatorKey = GlobalKey(); - _needsBootstrap = true; - _postComments = []; - _currentSort = PostCommentsSortType.dec; - _noMoreItemsToLoad = true; - _commentInputFocusNode = FocusNode(); - } - - @override - void dispose() { - super.dispose(); - if (_refreshCommentsOperation != null) _refreshCommentsOperation.cancel(); - if (_loadMoreBottomCommentsOperation != null) - _loadMoreBottomCommentsOperation.cancel(); - if (_refreshPostOperation != null) _refreshPostOperation.cancel(); - if (_toggleSortCommentsOperation != null) - _toggleSortCommentsOperation.cancel(); - } - - @override - Widget build(BuildContext context) { - if (_needsBootstrap) { - var provider = OpenbookProvider.of(context); - _userService = provider.userService; - _toastService = provider.toastService; - _themeValueParserService = provider.themeValueParserService; - _themeService = provider.themeService; - _userPreferencesService = provider.userPreferencesService; - _bootstrap(); - _needsBootstrap = false; - } - - return OBCupertinoPageScaffold( - backgroundColor: Color.fromARGB(0, 0, 0, 0), - navigationBar: OBThemedNavigationBar( - title: 'Post comments', - ), - child: OBPrimaryColorContainer( - child: Column( - children: [ - Expanded( - child: RefreshIndicator( - key: _refreshIndicatorKey, - child: GestureDetector( - onTap: _unfocusCommentInput, - child: LoadMore( - whenEmptyLoad: false, - isFinish: _noMoreItemsToLoad, - delegate: OBInfinitePostCommentsLoadMoreDelegate(), - child: ListView.builder( - physics: const AlwaysScrollableScrollPhysics(), - controller: _postCommentsScrollController, - padding: EdgeInsets.all(0), - itemCount: _postComments.length + 1, - itemBuilder: (context, index) { - if (index == 0) { - return Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - _buildCommentsHeader(), - ], - ); - } - - int commentIndex = index - 1; - - var postComment = _postComments[commentIndex]; - - var onPostCommentDeletedCallback = () { - _removePostCommentAtIndex(commentIndex); - }; - - return OBPostComment( - key: Key('postComment#${postComment.id}'), - postComment: postComment, - post: widget.post, - onPostCommentDeletedCallback: - onPostCommentDeletedCallback); - }), - onLoadMore: _loadMoreBottomComments), - ), - onRefresh: _refreshComments), - ), - _buildPostCommenterSection() - ], - ), - )); - } - - void _bootstrap() async { - await _setPostCommentsSortTypeFromPreferences(); - await _refreshPost(); - await _refreshComments(); - } - - Future _setPostCommentsSortTypeFromPreferences() async { - PostCommentsSortType sortType = - await _userPreferencesService.getPostCommentsSortType(); - _currentSort = sortType; - } - - Widget _buildCommentsHeader() { - var theme = _themeService.getActiveTheme(); - return Container( - padding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 0.0), - child: OBSecondaryText( - _postComments.length > 0 - ? _currentSort == PostCommentsSortType.dec - ? 'Newest comments' - : 'Oldest comments' - : 'Be the first to comment!', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0), - ), - ), - FlatButton( - child: Row( - children: [ - OBText( - _postComments.length > 0 - ? _currentSort == PostCommentsSortType.dec - ? 'See oldest comments' - : 'See newest comments' - : '', - style: TextStyle( - color: _themeValueParserService - .parseGradient(theme.primaryAccentColor) - .colors[1], - fontWeight: FontWeight.bold), - ), - ], - ), - onPressed: _onWantsToToggleSortComments), - ], - ), - ); - } - - Widget _buildPostCommenterSection() { - User loggedInUser = _userService.getLoggedInUser(); - if (widget.post.areCommentsEnabled || loggedInUser.canCommentOnPostWithDisabledComments(widget.post)) { - return OBPostCommenter( - widget.post, - autofocus: widget.autofocusCommentInput, - commentTextFieldFocusNode: _commentInputFocusNode, - onPostCommentCreated: _onPostCommentCreated, - onPostCommentWillBeCreated: _onPostCommentWillBeCreated, - ); - } else { - return Container( - padding: EdgeInsets.all(10.0), - child: OBAlert( - padding: EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: OBText('Comments have been disabled for this post', textAlign: TextAlign.center,), - ), - ], - ) - ), - ); - } - } - - void _onWantsToToggleSortComments() async { - if (_toggleSortCommentsOperation != null) - _toggleSortCommentsOperation.cancel(); - PostCommentsSortType newSortType; - - if (_currentSort == PostCommentsSortType.asc) { - newSortType = PostCommentsSortType.dec; - } else { - newSortType = PostCommentsSortType.asc; - } - - try { - _toggleSortCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(widget.post, sort: newSortType)); - - _postComments = (await _toggleSortCommentsOperation.value).comments; - _setCurrentSortValue(newSortType); - _userPreferencesService.setPostCommentsSortType(newSortType); - _setPostComments(_postComments); - _scrollToTop(); - _setNoMoreItemsToLoad(false); - } catch (error) { - _onError(error); - } finally { - _toggleSortCommentsOperation = null; - } - } - - Future _refreshComments() async { - if (_refreshCommentsOperation != null) _refreshCommentsOperation.cancel(); - try { - _refreshCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(widget.post, sort: _currentSort)); - _postComments = (await _refreshCommentsOperation.value).comments; - _setPostComments(_postComments); - _scrollToTop(); - _setNoMoreItemsToLoad(false); - } catch (error) { - _onError(error); - } finally { - _refreshCommentsOperation = null; - } - } - - Future _refreshPost() async { - if (_refreshPostOperation != null) _refreshPostOperation.cancel(); - try { - // This will trigger the updateSubject of the post - _refreshPostOperation = CancelableOperation.fromFuture( - _userService.getPostWithUuid(widget.post.uuid)); - await _refreshPostOperation.value; - } catch (error) { - _onError(error); - } finally { - _refreshPostOperation = null; - } - } - - Future _loadMoreBottomComments() async { - if (_loadMoreBottomCommentsOperation != null) - _loadMoreBottomCommentsOperation.cancel(); - if (_postComments.length == 0) return true; - - var lastPost = _postComments.last; - var lastPostId = lastPost.id; - - try { - var moreComments; - - if (_currentSort == PostCommentsSortType.dec) { - _loadMoreBottomCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(widget.post, maxId: lastPostId)); - } else { - _loadMoreBottomCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(widget.post, - minId: lastPostId + 1, sort: _currentSort)); - } - - moreComments = (await _loadMoreBottomCommentsOperation.value).comments; - - if (moreComments.length == 0) { - _setNoMoreItemsToLoad(true); - } else { - _addPostComments(moreComments); - } - return true; - } catch (error) { - _onError(error); - } finally { - _loadMoreBottomCommentsOperation = null; - } - - return false; - } - - void _removePostCommentAtIndex(int index) { - setState(() { - _postComments.removeAt(index); - }); - } - - void _onPostCommentCreated(PostComment createdPostComment) { - _unfocusCommentInput(); - setState(() { - this._postComments.insert(0, createdPostComment); - }); - } - - Future _onPostCommentWillBeCreated() { - _setCurrentSortValue(PostCommentsSortType.dec); - return _refreshComments(); - } - - void _setCurrentSortValue(PostCommentsSortType newSortType) { - setState(() { - _currentSort = newSortType; - }); - } - - void _scrollToTop() { - _postCommentsScrollController.animateTo( - 0.0, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 300), - ); - } - - void _unfocusCommentInput() { - FocusScope.of(context).requestFocus(new FocusNode()); - } - - void _addPostComments(List postComments) { - setState(() { - this._postComments.addAll(postComments); - }); - } - - void _setPostComments(List postComments) { - setState(() { - this._postComments = postComments; - }); - } - - void _setNoMoreItemsToLoad(bool noMoreItemsToLoad) { - setState(() { - _noMoreItemsToLoad = noMoreItemsToLoad; - }); - } - - void _onError(error) async { - if (error is HttpieConnectionRefusedError) { - _toastService.error( - message: error.toHumanReadableMessage(), context: context); - } else if (error is HttpieRequestError) { - String errorMessage = await error.toHumanReadableMessage(); - _toastService.error(message: errorMessage, context: context); - } else { - _toastService.error(message: 'Unknown error', context: context); - throw error; - } - } -} - -class OBInfinitePostCommentsLoadMoreDelegate extends LoadMoreDelegate { - const OBInfinitePostCommentsLoadMoreDelegate(); - - @override - Widget buildChild(LoadMoreStatus status, {LoadMoreTextBuilder builder}) { - String text = builder(status); - - if (status == LoadMoreStatus.fail) { - return SizedBox( - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.refresh), - const SizedBox( - width: 10.0, - ), - Text('Tap to retry loading comments.') - ], - ), - ); - } - if (status == LoadMoreStatus.idle) { - // No clue why is this even a state. - return const SizedBox(); - } - if (status == LoadMoreStatus.loading) { - return SizedBox( - child: Center( - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: 20.0, - maxWidth: 20.0, - ), - child: CircularProgressIndicator( - strokeWidth: 2.0, - ), - ), - )); - } - if (status == LoadMoreStatus.nomore) { - return const SizedBox(); - } - - return Text(text); - } -} diff --git a/lib/pages/home/pages/post_comments/post_comments_linked.dart b/lib/pages/home/pages/post_comments/post_comments_page.dart similarity index 52% rename from lib/pages/home/pages/post_comments/post_comments_linked.dart rename to lib/pages/home/pages/post_comments/post_comments_page.dart index 5beadaf90..884682901 100644 --- a/lib/pages/home/pages/post_comments/post_comments_linked.dart +++ b/lib/pages/home/pages/post_comments/post_comments_page.dart @@ -2,46 +2,54 @@ import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post-commenter.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_preview.dart'; import 'package:Openbook/services/theme.dart'; import 'package:Openbook/services/theme_value_parser.dart'; import 'package:Openbook/services/user_preferences.dart'; -import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; -import 'package:Openbook/widgets/post/widgets/post-actions/post_actions.dart'; -import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; -import 'package:Openbook/widgets/post/widgets/post_circles.dart'; -import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; -import 'package:Openbook/widgets/post/widgets/post_reactions/post_reactions.dart'; import 'package:Openbook/widgets/theming/post_divider.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; -import 'package:Openbook/widgets/theming/secondary_text.dart'; -import 'package:Openbook/widgets/theming/text.dart'; import 'package:async/async.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:Openbook/widgets/load_more.dart'; import 'package:Openbook/services/httpie.dart'; -class OBPostCommentsLinkedPage extends StatefulWidget { - final PostComment postComment; +class OBPostCommentsPage extends StatefulWidget { + final PostComment linkedPostComment; + final Post post; + final PostCommentsPageType pageType; final bool autofocusCommentInput; - - OBPostCommentsLinkedPage( - this.postComment, { + final bool showPostPreview; + final PostComment postComment; + Function(PostComment) onCommentDeleted; + Function(PostComment) onCommentAdded; + + OBPostCommentsPage({ + @required this.pageType, + this.post, + this.linkedPostComment, + this.postComment, + this.onCommentDeleted, + this.onCommentAdded, + this.showPostPreview, this.autofocusCommentInput: false, }); @override - State createState() { - return OBPostCommentsLinkedPageState(); + State createState() { + return OBPostCommentsPageState(); } } -class OBPostCommentsLinkedPageState extends State +class OBPostCommentsPageState extends State with SingleTickerProviderStateMixin { UserService _userService; UserPreferencesService _userPreferencesService; @@ -52,7 +60,6 @@ class OBPostCommentsLinkedPageState extends State AnimationController _animationController; Animation _animation; - GlobalKey _keyPostBody = GlobalKey(); double _positionTopCommentSection; ScrollController _postCommentsScrollController; List _postComments = []; @@ -64,6 +71,9 @@ class OBPostCommentsLinkedPageState extends State PostCommentsSortType _currentSort; FocusNode _commentInputFocusNode; GlobalKey _refreshIndicatorKey; + OBPostCommentsPageController _commentsPageController; + Map _pageTextMap; + static const OFFSET_TOP_HEADER = 64.0; static const HEIGHT_POST_HEADER = 72.0; static const HEIGHT_POST_REACTIONS = 35.0; @@ -85,18 +95,31 @@ class OBPostCommentsLinkedPageState extends State static const TOTAL_COMMENTS_IN_SLICE = COUNT_MIN_INCLUDING_LINKED_COMMENT + COUNT_MAX_AFTER_LINKED_COMMENT; - CancelableOperation _refreshCommentsOperation; - CancelableOperation _refreshCommentsSliceOperation; - CancelableOperation _refreshCommentsWithCreatedPostCommentVisibleOperation; + static const PAGE_COMMENTS_TEXT_MAP = { + 'TITLE': 'Post comments', + 'NO_MORE_TO_LOAD': 'No more comments to load', + 'TAP_TO_RETRY': 'Tap to retry loading comments.', + + }; + + static const PAGE_REPLIES_TEXT_MAP = { + 'TITLE': 'Post replies', + 'NO_MORE_TO_LOAD': 'No more replies to load', + 'TAP_TO_RETRY': 'Tap to retry loading replies.', + }; + CancelableOperation _refreshPostOperation; - CancelableOperation _loadMoreBottomCommentsOperation; - CancelableOperation _loadMoreTopCommentsOperation; - CancelableOperation _toggleSortCommentsOperation; @override void initState() { super.initState(); - _post = widget.postComment.post; + if (widget.linkedPostComment != null) _post = widget.linkedPostComment.post; + if (widget.post != null) _post = widget.post; + if (widget.pageType == PostCommentsPageType.comments){ + _pageTextMap = PAGE_COMMENTS_TEXT_MAP; + } else { + _pageTextMap = PAGE_REPLIES_TEXT_MAP; + } _needsBootstrap = true; _postComments = []; _noMoreBottomItemsToLoad = true; @@ -128,7 +151,7 @@ class OBPostCommentsLinkedPageState extends State return OBCupertinoPageScaffold( backgroundColor: Color.fromARGB(0, 0, 0, 0), navigationBar: OBThemedNavigationBar( - title: 'Post comments', + title: _pageTextMap['TITLE'], ), child: OBPrimaryColorContainer( child: Stack( @@ -139,8 +162,8 @@ class OBPostCommentsLinkedPageState extends State void _bootstrap() async { await _setPostCommentsSortTypeFromPreferences(); - await _refreshPost(); - await _refreshCommentsSlice(); + _initialiseCommentsPageController(); + if (widget.post != null) await _refreshPost(); } Future _setPostCommentsSortTypeFromPreferences() async { @@ -149,21 +172,33 @@ class OBPostCommentsLinkedPageState extends State _currentSort = sortType; } + void _initialiseCommentsPageController() { + _commentsPageController = OBPostCommentsPageController( + pageType: widget.pageType, + userService: _userService, + userPreferencesService: _userPreferencesService, + currentSort: _currentSort, + post: _post, + postComment: widget.postComment, + linkedPostComment: widget.linkedPostComment, + addPostComments: _addPostComments, + addToStartPostComments: _addToStartPostComments, + setPostComments: _setPostComments, + setCurrentSortValue: _setCurrentSortValue, + setNoMoreBottomItemsToLoad: _setNoMoreBottomItemsToLoad, + setNoMoreTopItemsToLoad: _setNoMoreTopItemsToLoad, + showNoMoreTopItemsToLoadToast: _showNoMoreTopItemsToLoadToast, + scrollToNewComment: _scrollToNewComment, + scrollToTop: _scrollToTop, + unfocusCommentInput: _unfocusCommentInput, + onError: _onError + ); + } + void dispose() { super.dispose(); _animation.removeStatusListener(_onAnimationStatusChanged); - if (_refreshCommentsOperation != null) _refreshCommentsOperation.cancel(); - if (_refreshCommentsSliceOperation != null) - _refreshCommentsSliceOperation.cancel(); - if (_loadMoreBottomCommentsOperation != null) - _loadMoreBottomCommentsOperation.cancel(); - if (_refreshPostOperation != null) _refreshPostOperation.cancel(); - if (_toggleSortCommentsOperation != null) - _toggleSortCommentsOperation.cancel(); - if (_loadMoreTopCommentsOperation != null) - _loadMoreTopCommentsOperation.cancel(); - if (_refreshCommentsWithCreatedPostCommentVisibleOperation != null) - _refreshCommentsWithCreatedPostCommentVisibleOperation.cancel(); + _commentsPageController.dispose(); } void _onAnimationStatusChanged(status) { @@ -227,7 +262,7 @@ class OBPostCommentsLinkedPageState extends State child: LoadMore( whenEmptyLoad: false, isFinish: _noMoreBottomItemsToLoad, - delegate: OBInfinitePostCommentsLoadMoreDelegate(), + delegate: OBInfinitePostCommentsLoadMoreDelegate(_pageTextMap), child: ListView.builder( physics: const ClampingScrollPhysics(), controller: _postCommentsScrollController, @@ -235,31 +270,68 @@ class OBPostCommentsLinkedPageState extends State itemCount: _postComments.length + 1, itemBuilder: (context, index) { if (index == 0) { - return _getPostPreview(); + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + _getPostPreview(), + _getCommentPreview(), + _getDivider(), + OBPostCommentsHeaderBar( + pageType: widget.pageType, + noMoreTopItemsToLoad: _noMoreTopItemsToLoad, + postComments: _postComments, + currentSort: _currentSort, + onWantsToToggleSortComments:() => _commentsPageController.onWantsToToggleSortComments(), + loadMoreTopComments: () => _commentsPageController.loadMoreTopComments(), + onWantsToRefreshComments:() => _commentsPageController.onWantsToRefreshComments() + ), + ], + ); } else { return _getCommentTile(index); } }), - onLoadMore: _loadMoreBottomComments), + onLoadMore: () => _commentsPageController.loadMoreBottomComments()), ), - onRefresh: _onWantsToRefreshComments), + onRefresh: () => _commentsPageController.onWantsToRefreshComments()), ), OBPostCommenter( _post, autofocus: widget.autofocusCommentInput, commentTextFieldFocusNode: _commentInputFocusNode, - onPostCommentCreated: _refreshCommentsWithCreatedPostCommentVisible, + onPostCommentCreated:(PostComment createdPostComment) { + _commentsPageController.refreshCommentsWithCreatedPostCommentVisible(createdPostComment); + if (widget.onCommentAdded != null) { + widget.onCommentAdded(createdPostComment); + } + }, ) ]); return _columnChildren; } + Widget _getDivider() { + if (widget.postComment != null) { + return OBPostDivider(); + } + return SizedBox(); + } + + Widget _getCommentPreview() { + if (widget.postComment == null) { + return SizedBox(); + } + return OBPostCommentTile(post: widget.post, postComment: widget.postComment); + } + Widget _getCommentTile(int index) { int commentIndex = index - 1; var postComment = _postComments[commentIndex]; var onPostCommentDeletedCallback = () { _removePostCommentAtIndex(commentIndex); + if (widget.onCommentDeleted != null) widget.onCommentDeleted(postComment); }; if (_animationController.status != AnimationStatus.completed && @@ -283,7 +355,7 @@ class OBPostCommentsLinkedPageState extends State }); } - if (postComment.id == widget.postComment.id) { + if (widget.linkedPostComment != null && postComment.id == widget.linkedPostComment.id) { var theme = _themeService.getActiveTheme(); var primaryColor = _themeValueParserService.parseColor(theme.primaryColor); @@ -295,7 +367,7 @@ class OBPostCommentsLinkedPageState extends State : Color.fromARGB(10, 0, 0, 0), ), child: OBPostComment( - key: Key('postComment#${postComment.id}'), + key: Key('postComment#${widget.pageType}#${postComment.id}'), postComment: postComment, post: _post, onPostCommentDeletedCallback: onPostCommentDeletedCallback, @@ -303,7 +375,7 @@ class OBPostCommentsLinkedPageState extends State ); } else { return OBPostComment( - key: Key('postComment#${postComment.id}'), + key: Key('postComment#${widget.pageType}#${postComment.id}'), postComment: postComment, post: _post, onPostCommentDeletedCallback: onPostCommentDeletedCallback, @@ -312,53 +384,24 @@ class OBPostCommentsLinkedPageState extends State } Widget _getPostPreview() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - OBPostHeader( - post: _post, - onPostDeleted: _onPostDeleted, - ), - Container( - key: _keyPostBody, - child: OBPostBody(_post), - ), - OBPostReactions(_post), - OBPostCircles(_post), - OBPostActions( - _post, - onWantsToCommentPost: _focusCommentInput, - ), - const SizedBox( - height: 16, - ), - OBPostDivider(), - _buildLoadTopCommentsBar(), - ], + if (widget.post == null || !widget.showPostPreview) { + return SizedBox(); + } + + return OBPostPreview( + post: _post, + onPostDeleted: _onPostDeleted, + focusCommentInput: _focusCommentInput, ); + } - Future _refreshCommentsSlice() async { - if (_refreshCommentsSliceOperation != null) - _refreshCommentsSliceOperation.cancel(); - try { - _refreshCommentsSliceOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(_post, - minId: widget.postComment.id, - maxId: widget.postComment.id, - countMax: COUNT_MAX_AFTER_LINKED_COMMENT, - countMin: COUNT_MIN_INCLUDING_LINKED_COMMENT, - sort: _currentSort)); - - _postComments = (await _refreshCommentsSliceOperation.value).comments; - _setPostComments(_postComments); - _checkIfMoreTopItemsToLoad(); - _setNoMoreBottomItemsToLoad(false); - } catch (error) { - _onError(error); - } finally { - _refreshCommentsSliceOperation = null; - } + void _scrollToTop() { + _postCommentsScrollController.animateTo( + 0.0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 300), + ); } void _setPositionTopCommentSection() { @@ -383,171 +426,16 @@ class OBPostCommentsLinkedPageState extends State } } - Future _loadMoreBottomComments() async { - if (_loadMoreBottomCommentsOperation != null) - _loadMoreBottomCommentsOperation.cancel(); - if (_postComments.length == 0) return true; - - PostComment lastPost = _postComments.last; - int lastPostId = lastPost.id; - List moreComments; - try { - if (_currentSort == PostCommentsSortType.dec) { - _loadMoreBottomCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(_post, - countMax: LOAD_MORE_COMMENTS_COUNT, - maxId: lastPostId, - sort: _currentSort)); - } else { - _loadMoreBottomCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(_post, - countMin: LOAD_MORE_COMMENTS_COUNT, - minId: lastPostId + 1, - sort: _currentSort)); - } - - moreComments = (await _loadMoreBottomCommentsOperation.value).comments; - - if (moreComments.length == 0) { - _setNoMoreBottomItemsToLoad(true); - } else { - _addPostComments(moreComments); - } - return true; - } catch (error) { - _onError(error); - } finally { - _loadMoreBottomCommentsOperation = null; - } - - return false; - } - - Future _loadMoreTopComments() async { - if (_loadMoreTopCommentsOperation != null) - _loadMoreTopCommentsOperation.cancel(); - if (_postComments.length == 0) return true; - - List topComments; - PostComment firstPost = _postComments.first; - int firstPostId = firstPost.id; - try { - if (_currentSort == PostCommentsSortType.dec) { - _loadMoreTopCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(_post, - sort: PostCommentsSortType.dec, - countMin: LOAD_MORE_COMMENTS_COUNT, - minId: firstPostId + 1)); - } else if (_currentSort == PostCommentsSortType.asc) { - _loadMoreTopCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(_post, - sort: PostCommentsSortType.asc, - countMax: LOAD_MORE_COMMENTS_COUNT, - maxId: firstPostId)); - } - - topComments = (await _loadMoreTopCommentsOperation.value).comments; - - if (topComments.length < LOAD_MORE_COMMENTS_COUNT && - topComments.length != 0) { - _setNoMoreTopItemsToLoad(true); - _addToStartPostComments(topComments); - } else if (topComments.length == LOAD_MORE_COMMENTS_COUNT) { - _addToStartPostComments(topComments); - } else { - _setNoMoreTopItemsToLoad(true); - _showNoMoreTopItemsToLoadToast(); - } - return true; - } catch (error) { - _onError(error); - } finally { - _loadMoreTopCommentsOperation = null; - } - - return false; - } - void _removePostCommentAtIndex(int index) { setState(() { _postComments.removeAt(index); }); } - void _refreshCommentsWithCreatedPostCommentVisible( - PostComment createdPostComment) async { - if (_refreshCommentsWithCreatedPostCommentVisibleOperation != null) - _refreshCommentsWithCreatedPostCommentVisibleOperation.cancel(); - _unfocusCommentInput(); - List comments; - int createdCommentId = createdPostComment.id; - try { - if (_currentSort == PostCommentsSortType.dec) { - _refreshCommentsWithCreatedPostCommentVisibleOperation = - CancelableOperation.fromFuture(_userService.getCommentsForPost( - _post, - sort: PostCommentsSortType.dec, - countMin: LOAD_MORE_COMMENTS_COUNT, - minId: createdCommentId)); - } else if (_currentSort == PostCommentsSortType.asc) { - _refreshCommentsWithCreatedPostCommentVisibleOperation = - CancelableOperation.fromFuture(_userService.getCommentsForPost( - _post, - sort: PostCommentsSortType.asc, - countMax: LOAD_MORE_COMMENTS_COUNT, - maxId: createdCommentId + 1)); - } - - comments = - (await _refreshCommentsWithCreatedPostCommentVisibleOperation.value) - .comments; - - _setPostComments(comments); - _setNoMoreTopItemsToLoad(false); - _setNoMoreBottomItemsToLoad(false); - _scrollToNewComment(); - } catch (error) { - _onError(error); - } finally { - _refreshCommentsWithCreatedPostCommentVisibleOperation = null; - } - } - void _onPostDeleted(Post post) { Navigator.of(context).pop(); } - void _checkIfMoreTopItemsToLoad() { - int linkedCommentId = widget.postComment.id; - Iterable listBeforeLinkedComment = []; - if (_currentSort == PostCommentsSortType.dec) { - listBeforeLinkedComment = - _postComments.where((comment) => comment.id > linkedCommentId); - } else if (_currentSort == PostCommentsSortType.asc) { - listBeforeLinkedComment = - _postComments.where((comment) => comment.id < linkedCommentId); - } - if (listBeforeLinkedComment.length < 2) { - _setNoMoreTopItemsToLoad(true); - } - } - - Future _onWantsToRefreshComments() async { - if (_refreshCommentsOperation != null) _refreshCommentsOperation.cancel(); - try { - _refreshCommentsOperation = CancelableOperation.fromFuture( - _userService.getCommentsForPost(_post, sort: _currentSort)); - _postComments = (await _refreshCommentsOperation.value).comments; - _setPostComments(_postComments); - _setNoMoreBottomItemsToLoad(false); - _setNoMoreTopItemsToLoad(true); - } catch (error) { - _onError(error); - } finally { - _refreshCommentsOperation = null; - } - } - void _focusCommentInput() { FocusScope.of(context).requestFocus(_commentInputFocusNode); } @@ -589,7 +477,7 @@ class OBPostCommentsLinkedPageState extends State } void _showNoMoreTopItemsToLoadToast() { - _toastService.info(context: context, message: 'No more comments to load'); + _toastService.info(context: context, message: _pageTextMap['NO_MORE_TO_LOAD']); } void _setCurrentSortValue(PostCommentsSortType newSortValue) { @@ -610,19 +498,6 @@ class OBPostCommentsLinkedPageState extends State } } - void _onWantsToToggleSortComments() async { - PostCommentsSortType newSortType; - - if (_currentSort == PostCommentsSortType.asc) { - newSortType = PostCommentsSortType.dec; - } else { - newSortType = PostCommentsSortType.asc; - } - _userPreferencesService.setPostCommentsSortType(newSortType); - _setCurrentSortValue(newSortType); - _onWantsToRefreshComments(); - } - void _onError(error) async { if (error is HttpieConnectionRefusedError) { _toastService.error( @@ -641,6 +516,9 @@ class OBPostCommentsLinkedPageState extends State double finalMediaScreenHeight = 0.0; double finalTextHeight = 0.0; double totalOffsetY = 0.0; + + if (widget.post == null) return totalOffsetY; + double screenWidth = MediaQuery.of(context).size.width; if (_post.hasImage()) { aspectRatio = _post.getImageWidth() / _post.getImageHeight(); @@ -677,97 +555,11 @@ class OBPostCommentsLinkedPageState extends State return totalOffsetY; } - - Widget _buildLoadTopCommentsBar() { - var theme = _themeService.getActiveTheme(); - - if (_noMoreTopItemsToLoad) { - return Container( - padding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0), - child: OBSecondaryText( - _postComments.length > 0 - ? _currentSort == PostCommentsSortType.dec - ? 'Newest comments' - : 'Oldest comments' - : 'Be the first to comment!', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0), - ), - ), - ), - Expanded( - child: FlatButton( - child: OBText( - _postComments.length > 0 - ? _currentSort == PostCommentsSortType.dec - ? 'See oldest comments' - : 'See newest comments' - : '', - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: _themeValueParserService - .parseGradient(theme.primaryAccentColor) - .colors[1], - fontWeight: FontWeight.bold), - ), - onPressed: _onWantsToToggleSortComments), - ), - ], - ), - ); - } else { - return Container( - padding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 4, - child: FlatButton( - child: Row( - children: [ - OBIcon(OBIcons.arrowUp), - const SizedBox(width: 10.0), - OBText( - _currentSort == PostCommentsSortType.dec - ? 'Newer' - : 'Older', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ], - ), - onPressed: _loadMoreTopComments), - ), - Expanded( - flex: 6, - child: FlatButton( - child: OBText( - _currentSort == PostCommentsSortType.dec - ? 'View newest comments' - : 'View oldest comments', - style: TextStyle( - color: _themeValueParserService - .parseGradient(theme.primaryAccentColor) - .colors[1], - fontWeight: FontWeight.bold), - overflow: TextOverflow.ellipsis, - ), - onPressed: _onWantsToRefreshComments), - ), - ], - ), - ); - } - } } class OBInfinitePostCommentsLoadMoreDelegate extends LoadMoreDelegate { - const OBInfinitePostCommentsLoadMoreDelegate(); + Map pageTextMap; + OBInfinitePostCommentsLoadMoreDelegate(Map pageTextMap); @override Widget buildChild(LoadMoreStatus status, @@ -784,7 +576,7 @@ class OBInfinitePostCommentsLoadMoreDelegate extends LoadMoreDelegate { const SizedBox( width: 10.0, ), - Text('Tap to retry loading comments.') + Text(pageTextMap['TAP_TO_RETRY']) ], ), ); From e39fc694cfaad3321ce32fecade41fc4f3f775d3 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 16 May 2019 18:22:10 +0200 Subject: [PATCH 02/65] :sparkles: notification models and addditions to post comment model --- lib/models/notifications/notification.dart | 8 ++++ .../post_comment_reply_notification.dart | 25 +++++++++++ lib/models/post_comment.dart | 42 +++++++++++++++++++ lib/models/user.dart | 4 ++ 4 files changed, 79 insertions(+) create mode 100644 lib/models/notifications/post_comment_reply_notification.dart diff --git a/lib/models/notifications/notification.dart b/lib/models/notifications/notification.dart index 6e6a10184..dcfa4f6a7 100644 --- a/lib/models/notifications/notification.dart +++ b/lib/models/notifications/notification.dart @@ -3,6 +3,7 @@ import 'package:Openbook/models/notifications/connection_confirmed_notification. import 'package:Openbook/models/notifications/connection_request_notification.dart'; import 'package:Openbook/models/notifications/follow_notification.dart'; import 'package:Openbook/models/notifications/post_comment_notification.dart'; +import 'package:Openbook/models/notifications/post_comment_reply_notification.dart'; import 'package:Openbook/models/notifications/post_reaction_notification.dart'; import 'package:Openbook/models/updatable_model.dart'; import 'package:Openbook/models/user.dart'; @@ -30,6 +31,7 @@ class OBNotification extends UpdatableModel { static final factory = NotificationFactory(); static final postReaction = 'PR'; static final postComment = 'PC'; + static final postCommentReply = 'PCR'; static final connectionRequest = 'CR'; static final connectionConfirmed = 'CC'; static final follow = 'F'; @@ -105,6 +107,8 @@ class NotificationFactory extends UpdatableModelFactory { notificationType = NotificationType.postReaction; } else if (notificationTypeStr == OBNotification.postComment) { notificationType = NotificationType.postComment; + } else if (notificationTypeStr == OBNotification.postCommentReply) { + notificationType = NotificationType.postCommentReply; } else if (notificationTypeStr == OBNotification.connectionRequest) { notificationType = NotificationType.connectionRequest; } else if (notificationTypeStr == OBNotification.connectionConfirmed) { @@ -141,6 +145,9 @@ class NotificationFactory extends UpdatableModelFactory { case NotificationType.postComment: contentObject = PostCommentNotification.fromJson(contentObjectData); break; + case NotificationType.postCommentReply: + contentObject = PostCommentReplyNotification.fromJson(contentObjectData); + break; case NotificationType.postReaction: contentObject = PostReactionNotification.fromJson(contentObjectData); break; @@ -160,6 +167,7 @@ class NotificationFactory extends UpdatableModelFactory { enum NotificationType { postReaction, postComment, + postCommentReply, connectionRequest, connectionConfirmed, follow, diff --git a/lib/models/notifications/post_comment_reply_notification.dart b/lib/models/notifications/post_comment_reply_notification.dart new file mode 100644 index 000000000..7c9cc61fc --- /dev/null +++ b/lib/models/notifications/post_comment_reply_notification.dart @@ -0,0 +1,25 @@ +import 'package:Openbook/models/post_comment.dart'; + +class PostCommentReplyNotification { + final int id; + final PostComment postComment; + final PostComment parentComment; + + PostCommentReplyNotification({this.id, this.postComment, this.parentComment}); + + factory PostCommentReplyNotification.fromJson(Map json) { + return PostCommentReplyNotification( + id: json['id'], + postComment: _parsePostComment(json['post_comment']), + parentComment: _parsePostComment(json['parent_comment']) + ); + } + + static PostComment _parsePostComment(Map postCommentData) { + return PostComment.fromJSON(postCommentData); + } + + int getPostCreatorId() { + return postComment.getPostCreatorId(); + } +} diff --git a/lib/models/post_comment.dart b/lib/models/post_comment.dart index 76d751efd..001a881b5 100644 --- a/lib/models/post_comment.dart +++ b/lib/models/post_comment.dart @@ -1,4 +1,5 @@ import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment_list.dart'; import 'package:Openbook/models/user.dart'; import 'package:dcache/dcache.dart'; import 'package:timeago/timeago.dart' as timeago; @@ -7,9 +8,12 @@ import 'package:Openbook/models/updatable_model.dart'; class PostComment extends UpdatableModel { final int id; int creatorId; + int repliesCount; DateTime created; String text; User commenter; + PostComment parentComment; + PostCommentList replies; Post post; bool isEdited; @@ -47,9 +51,12 @@ class PostComment extends UpdatableModel { PostComment( {this.id, + this.repliesCount, this.created, this.text, this.creatorId, + this.parentComment, + this.replies, this.commenter, this.post, this.isEdited}); @@ -70,6 +77,10 @@ class PostComment extends UpdatableModel { creatorId = json['creator_id']; } + if (json.containsKey('replies_count')) { + repliesCount = json['replies_count']; + } + if (json.containsKey('is_edited')) { isEdited = json['is_edited']; } @@ -85,6 +96,14 @@ class PostComment extends UpdatableModel { if (json.containsKey('created')) { created = factory.parseCreated(json['created']); } + + if (json.containsKey('parent_comment')) { + parentComment = factory.parseParentComment(json['parent_comment']); + } + + if (json.containsKey('replies')) { + replies = factory.parseCommentReplies(json['replies']); + } } String getRelativeCreated() { @@ -103,6 +122,15 @@ class PostComment extends UpdatableModel { return this.commenter.getProfileAvatar(); } + bool hasReplies() { + return repliesCount != null && repliesCount > 0 && replies != null; + } + + List getPostCommentReplies() { + if (replies == null) return []; + return replies.comments; + } + int getCommenterId() { return this.commenter.id; } @@ -125,6 +153,9 @@ class PostCommentFactory extends UpdatableModelFactory { created: parseCreated(json['created']), commenter: parseUser(json['commenter']), post: parsePost(json['post']), + repliesCount: json['replies_count'], + replies: parseCommentReplies(json['replies']), + parentComment: parseParentComment(json['parent_comment']), isEdited: json['is_edited'], text: json['text']); } @@ -143,6 +174,17 @@ class PostCommentFactory extends UpdatableModelFactory { if (userData == null) return null; return User.fromJson(userData); } + + PostComment parseParentComment(Map commentData) { + if (commentData == null) return null; + return PostComment.fromJSON(commentData); + } + + PostCommentList parseCommentReplies(List repliesData) { + if (repliesData == null) return null; + return PostCommentList.fromJson(repliesData); + } + } enum PostCommentsSortType { asc, dec } diff --git a/lib/models/user.dart b/lib/models/user.dart index 5fefffed1..7e6d9fc4e 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -431,6 +431,10 @@ class User extends UpdatableModel { return loggedInUser.id == postCommenter.id; } + bool canReplyPostComment(PostComment postComment) { + return postComment.parentComment == null; + } + bool canDeletePostComment(Post post, PostComment postComment) { User loggedInUser = this; User postCommenter = postComment.commenter; From 994ca88891bdc84f27ac3462ddab951aee74bd2a Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 16 May 2019 18:23:18 +0200 Subject: [PATCH 03/65] :recycle: make post comment page into all in one single page, add widgets --- .../post_comments/post_comments_page.dart | 29 +- .../post_comments/widgets/post-commenter.dart | 14 +- .../widgets/post_comment/post_comment.dart | 223 +++++++------ .../widgets/post_comment_tile.dart | 118 +++++++ .../post_comments_page_controller.dart | 311 ++++++++++++++++++ .../widgets/post_comments_header_bar.dart | 152 +++++++++ .../post_comments/widgets/post_preview.dart | 51 +++ 7 files changed, 776 insertions(+), 122 deletions(-) create mode 100644 lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart create mode 100644 lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart create mode 100644 lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart create mode 100644 lib/pages/home/pages/post_comments/widgets/post_preview.dart diff --git a/lib/pages/home/pages/post_comments/post_comments_page.dart b/lib/pages/home/pages/post_comments/post_comments_page.dart index 884682901..ba956827c 100644 --- a/lib/pages/home/pages/post_comments/post_comments_page.dart +++ b/lib/pages/home/pages/post_comments/post_comments_page.dart @@ -89,11 +89,6 @@ class OBPostCommentsPageState extends State HEIGHT_POST_ACTIONS + HEIGHT_SIZED_BOX + HEIGHT_POST_DIVIDER; - static const LOAD_MORE_COMMENTS_COUNT = 5; - static const COUNT_MIN_INCLUDING_LINKED_COMMENT = 3; - static const COUNT_MAX_AFTER_LINKED_COMMENT = 2; - static const TOTAL_COMMENTS_IN_SLICE = - COUNT_MIN_INCLUDING_LINKED_COMMENT + COUNT_MAX_AFTER_LINKED_COMMENT; static const PAGE_COMMENTS_TEXT_MAP = { 'TITLE': 'Post comments', @@ -298,6 +293,7 @@ class OBPostCommentsPageState extends State ), OBPostCommenter( _post, + postComment: widget.postComment, autofocus: widget.autofocusCommentInput, commentTextFieldFocusNode: _commentInputFocusNode, onPostCommentCreated:(PostComment createdPostComment) { @@ -329,13 +325,13 @@ class OBPostCommentsPageState extends State Widget _getCommentTile(int index) { int commentIndex = index - 1; var postComment = _postComments[commentIndex]; - var onPostCommentDeletedCallback = () { + var onPostCommentDeletedCallback = (PostComment comment) { _removePostCommentAtIndex(commentIndex); if (widget.onCommentDeleted != null) widget.onCommentDeleted(postComment); }; if (_animationController.status != AnimationStatus.completed && - !_startScrollWasInitialised) { + !_startScrollWasInitialised && widget.linkedPostComment != null) { Future.delayed(Duration(milliseconds: 0), () { _postCommentsScrollController.animateTo( _positionTopCommentSection - 100.0, @@ -397,11 +393,13 @@ class OBPostCommentsPageState extends State } void _scrollToTop() { - _postCommentsScrollController.animateTo( - 0.0, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 300), - ); + Future.delayed(Duration(milliseconds: 0), () { + _postCommentsScrollController.animateTo( + 0.0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 300), + ); + }); } void _setPositionTopCommentSection() { @@ -448,6 +446,7 @@ class OBPostCommentsPageState extends State setState(() { this._postComments.addAll(postComments); }); + _commentsPageController.updateControllerPostComments(this._postComments); } void _addToStartPostComments(List postComments) { @@ -456,12 +455,14 @@ class OBPostCommentsPageState extends State this._postComments.insert(0, comment); }); }); + _commentsPageController.updateControllerPostComments(this._postComments); } void _setPostComments(List postComments) { setState(() { this._postComments = postComments; }); + _commentsPageController.updateControllerPostComments(this._postComments); } void _setNoMoreBottomItemsToLoad(bool noMoreItemsToLoad) { @@ -488,8 +489,8 @@ class OBPostCommentsPageState extends State void _scrollToNewComment() { if (_currentSort == PostCommentsSortType.asc) { - _postCommentsScrollController.animateTo(10000, - duration: Duration(milliseconds: 5), curve: Curves.easeIn); + _postCommentsScrollController.animateTo(_postCommentsScrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 50), curve: Curves.easeIn); } else if (_currentSort == PostCommentsSortType.dec) { _postCommentsScrollController.animateTo( _positionTopCommentSection - 200.0, diff --git a/lib/pages/home/pages/post_comments/widgets/post-commenter.dart b/lib/pages/home/pages/post_comments/widgets/post-commenter.dart index f2e627c3c..a2c06d2ff 100644 --- a/lib/pages/home/pages/post_comments/widgets/post-commenter.dart +++ b/lib/pages/home/pages/post_comments/widgets/post-commenter.dart @@ -16,13 +16,15 @@ import 'package:Openbook/services/httpie.dart'; class OBPostCommenter extends StatefulWidget { final Post post; + final PostComment postComment; final bool autofocus; final FocusNode commentTextFieldFocusNode; final OnPostCommentCreatedCallback onPostCommentCreated; final OnPostCommentWillBeCreatedCallback onPostCommentWillBeCreated; OBPostCommenter(this.post, - {this.autofocus = false, + {this.postComment, + this.autofocus = false, this.commentTextFieldFocusNode, this.onPostCommentCreated, this.onPostCommentWillBeCreated}); @@ -195,8 +197,14 @@ class OBPostCommenterState extends State { ? widget.onPostCommentWillBeCreated() : Future.value()); String commentText = _textController.text; - _submitFormOperation = CancelableOperation.fromFuture( - _userService.commentPost(text: commentText, post: widget.post)); + if (widget.postComment != null) { + _submitFormOperation = CancelableOperation.fromFuture( + _userService.replyPostComment(text: commentText, post: widget.post, postComment: widget.postComment)); + } else { + _submitFormOperation = CancelableOperation.fromFuture( + _userService.commentPost(text: commentText, post: widget.post)); + } + PostComment createdPostComment = await _submitFormOperation.value; widget.post.incrementCommentsCount(); _textController.clear(); diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart index b708f1c35..ab845a636 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart @@ -1,15 +1,13 @@ -import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/models/user.dart'; -import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_text.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/modal_service.dart'; import 'package:Openbook/services/navigation_service.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; -import 'package:Openbook/widgets/avatars/avatar.dart'; -import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/services/user_preferences.dart'; import 'package:Openbook/widgets/theming/secondary_text.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; @@ -18,7 +16,7 @@ import 'package:flutter_slidable/flutter_slidable.dart'; class OBPostComment extends StatefulWidget { final PostComment postComment; final Post post; - final VoidCallback onPostCommentDeletedCallback; + final Function(PostComment) onPostCommentDeletedCallback; OBPostComment( {@required this.post, @@ -36,9 +34,13 @@ class OBPostComment extends StatefulWidget { class OBPostCommentState extends State { NavigationService _navigationService; UserService _userService; + UserPreferencesService _userPreferencesService; ToastService _toastService; ModalService _modalService; bool _requestInProgress; + ScrollController _commentRepliesScrollController; + int _repliesCount; + List _replies; CancelableOperation _requestOperation; @@ -46,6 +48,9 @@ class OBPostCommentState extends State { void initState() { super.initState(); _requestInProgress = false; + _repliesCount = widget.postComment.repliesCount; + _replies = widget.postComment.getPostCommentReplies(); + _commentRepliesScrollController = ScrollController(); } @override @@ -59,12 +64,13 @@ class OBPostCommentState extends State { var provider = OpenbookProvider.of(context); _navigationService = provider.navigationService; _userService = provider.userService; + _userPreferencesService = provider.userPreferencesService; _toastService = provider.toastService; _modalService = provider.modalService; - Widget postTile = _buildPostCommentTile(widget.postComment); + Widget commentTile = OBPostCommentTile(post:widget.post, postComment: widget.postComment); Widget postComment = _buildPostCommentActions( - child: postTile, + child: commentTile, ); if (_requestInProgress) { @@ -76,66 +82,33 @@ class OBPostCommentState extends State { ); } - return postComment; - } - - Widget _buildPostCommentTile(PostComment postComment) { - return StreamBuilder( - stream: widget.postComment.updateSubject, - initialData: widget.postComment, - builder: (BuildContext context, AsyncSnapshot snapshot) { - PostComment postComment = snapshot.data; - - return Padding( - padding: - const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - OBAvatar( - onPressed: () { - _navigationService.navigateToUserProfile( - user: postComment.commenter, context: context); - }, - size: OBAvatarSize.small, - avatarUrl: postComment.getCommenterProfileAvatar(), - ), - const SizedBox( - width: 20.0, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - OBPostCommentText( - postComment, - badge: _getCommunityBadge(postComment), - onUsernamePressed: () { - _navigationService.navigateToUserProfile( - user: postComment.commenter, context: context); - }, - ), - const SizedBox( - height: 5.0, - ), - OBSecondaryText( - postComment.getRelativeCreated(), - style: TextStyle(fontSize: 12.0), - ) - ], - )) - ], - ), - ); - }); + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + postComment, + _buildPostCommentReplies() + ], + ); } Widget _buildPostCommentActions({@required Widget child}) { - List _editCommentActions = []; + List _commentActions = []; User loggedInUser = _userService.getLoggedInUser(); + if (loggedInUser.canReplyPostComment(widget.postComment)) { + _commentActions.add( + new IconSlideAction( + caption: 'Reply', + color: Colors.black38, + icon: Icons.reply, + onTap: _replyPostComment, + ) + ); + } + + if (loggedInUser.canEditPostComment(widget.postComment)) { - _editCommentActions.add( + _commentActions.add( new IconSlideAction( caption: 'Edit', color: Colors.blueGrey, @@ -146,7 +119,7 @@ class OBPostCommentState extends State { } if (loggedInUser.canDeletePostComment(widget.post, widget.postComment)) { - _editCommentActions.add( + _commentActions.add( new IconSlideAction( caption: 'Delete', color: Colors.red, @@ -160,15 +133,96 @@ class OBPostCommentState extends State { delegate: new SlidableDrawerDelegate(), actionExtentRatio: 0.2, child: child, - secondaryActions: _editCommentActions, + secondaryActions: _commentActions, + ); + } + + Widget _buildPostCommentReplies() { + if (widget.postComment.repliesCount == 0) return SizedBox(); + return Padding( + padding: EdgeInsets.only(left: 30.0, top: 0.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.builder( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + padding: EdgeInsets.all(0), + itemCount: widget.postComment.getPostCommentReplies().length, + itemBuilder: (context, index) { + PostComment reply = widget.postComment.getPostCommentReplies()[index]; + + return OBPostComment( + key: Key('postCommentReply#${reply.id}'), + postComment: reply, + post: widget.post, + onPostCommentDeletedCallback: _onReplyDeleted, + ); + } + ), + _buildViewAllReplies() + ], + ) + ); + } + + Widget _buildViewAllReplies() { + if (!widget.postComment.hasReplies() || (_repliesCount == _replies.length)) { + return SizedBox(); + } + + return FlatButton( + child: OBSecondaryText('View all $_repliesCount replies', + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontWeight: FontWeight.bold), + ), + onPressed: _onWantsToViewAllReplies); + } + + void _onWantsToViewAllReplies() { + _navigationService.navigateToPostCommentReplies( + post: widget.post, + postComment: widget.postComment, + context: context, + onReplyDeleted: _onReplyDeleted, + onReplyAdded: _onReplyAdded ); } + void _onReplyDeleted(PostComment postCommentReply) async { + setState(() { + _repliesCount -= 1; + _replies.removeWhere((reply) => reply.id == postCommentReply.id); + }); + } + + void _onReplyAdded(PostComment postCommentReply) async { + PostCommentsSortType sortType = await _userPreferencesService.getPostCommentsSortType(); + setState(() { + _repliesCount += 1; + if (sortType == PostCommentsSortType.dec) { + _replies.insert(0, postCommentReply); + } else if (_repliesCount <= 2) { + _replies.add(postCommentReply); + } + }); + } + void _editPostComment() async { await _modalService.openExpandedCommenter( context: context, post: widget.post, postComment: widget.postComment); } + void _replyPostComment() async { + await _modalService.openExpandedReplyCommenter( + context: context, + post: widget.post, + postComment: widget.postComment, + onReplyDeleted: _onReplyDeleted, + onReplyAdded: _onReplyAdded); + } + void _deletePostComment() async { if (_requestInProgress) return; _setRequestInProgress(true); @@ -181,7 +235,7 @@ class OBPostCommentState extends State { widget.post.decreaseCommentsCount(); _toastService.success(message: 'Comment deleted', context: context); if (widget.onPostCommentDeletedCallback != null) { - widget.onPostCommentDeletedCallback(); + widget.onPostCommentDeletedCallback(widget.postComment); } } catch (error) { _onError(error); @@ -208,47 +262,6 @@ class OBPostCommentState extends State { throw error; } } - - Widget _getCommunityBadge(PostComment postComment) { - Post post = widget.post; - User postCommenter = postComment.commenter; - - if (post.hasCommunity()) { - Community postCommunity = post.community; - - bool isCommunityAdministrator = - postCommenter.isAdministratorOfCommunity(postCommunity); - - if (isCommunityAdministrator) { - return _buildCommunityAdministratorBadge(); - } - - bool isCommunityModerator = - postCommenter.isModeratorOfCommunity(postCommunity); - - if (isCommunityModerator) { - return _buildCommunityModeratorBadge(); - } - } - - return const SizedBox(); - } - - Widget _buildCommunityAdministratorBadge() { - return const OBIcon( - OBIcons.communityAdministrators, - size: OBIconSize.small, - themeColor: OBIconThemeColor.primaryAccent, - ); - } - - Widget _buildCommunityModeratorBadge() { - return const OBIcon( - OBIcons.communityModerators, - size: OBIconSize.small, - themeColor: OBIconThemeColor.primaryAccent, - ); - } } typedef void OnWantsToSeeUserProfile(User user); diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart new file mode 100644 index 000000000..5a917d991 --- /dev/null +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart @@ -0,0 +1,118 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/models/user.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:flutter/material.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_text.dart'; +import 'package:Openbook/widgets/avatars/avatar.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; + + +class OBPostCommentTile extends StatelessWidget { + final PostComment postComment; + final Post post; + + OBPostCommentTile({ + @required this.post, + @required this.postComment}); + + @override + Widget build(BuildContext context) { + var provider = OpenbookProvider.of(context); + NavigationService _navigationService = provider.navigationService; + + return StreamBuilder( + key: Key('OBPostCommentTile#${this.postComment.id}'), + stream: this.postComment.updateSubject, + initialData: this.postComment, + builder: (BuildContext context, AsyncSnapshot snapshot) { + PostComment postComment = snapshot.data; + + return Padding( + padding: + const EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBAvatar( + onPressed: () { + _navigationService.navigateToUserProfile( + user: postComment.commenter, context: context); + }, + size: OBAvatarSize.small, + avatarUrl: postComment.getCommenterProfileAvatar(), + ), + const SizedBox( + width: 20.0, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBPostCommentText( + postComment, + badge: _getCommunityBadge(postComment), + onUsernamePressed: () { + _navigationService.navigateToUserProfile( + user: postComment.commenter, context: context); + }, + ), + const SizedBox( + height: 5.0, + ), + OBSecondaryText( + postComment.getRelativeCreated(), + style: TextStyle(fontSize: 12.0), + ) + ], + )) + ], + ), + ); + }); + } + + Widget _getCommunityBadge(PostComment postComment) { + Post post = this.post; + User postCommenter = postComment.commenter; + + if (post.hasCommunity()) { + Community postCommunity = post.community; + + bool isCommunityAdministrator = + postCommenter.isAdministratorOfCommunity(postCommunity); + + if (isCommunityAdministrator) { + return _buildCommunityAdministratorBadge(); + } + + bool isCommunityModerator = + postCommenter.isModeratorOfCommunity(postCommunity); + + if (isCommunityModerator) { + return _buildCommunityModeratorBadge(); + } + } + + return const SizedBox(); + } + + Widget _buildCommunityAdministratorBadge() { + return const OBIcon( + OBIcons.communityAdministrators, + size: OBIconSize.small, + themeColor: OBIconThemeColor.primaryAccent, + ); + } + + Widget _buildCommunityModeratorBadge() { + return const OBIcon( + OBIcons.communityModerators, + size: OBIconSize.small, + themeColor: OBIconThemeColor.primaryAccent, + ); + } +} \ No newline at end of file diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart new file mode 100644 index 000000000..80288e0fe --- /dev/null +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart @@ -0,0 +1,311 @@ +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/models/post_comment_list.dart'; +import 'package:Openbook/services/user_preferences.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:async/async.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBPostCommentsPageController { + final Post post; + final PostCommentsPageType pageType; + PostCommentsSortType currentSort; + Function(List) setPostComments; + Function(List) addPostComments; + Function(List) addToStartPostComments; + Function(PostCommentsSortType) setCurrentSortValue; + Function(bool) setNoMoreBottomItemsToLoad; + Function(bool) setNoMoreTopItemsToLoad; + VoidCallback showNoMoreTopItemsToLoadToast; + VoidCallback scrollToTop; + VoidCallback scrollToNewComment; + VoidCallback unfocusCommentInput; + Function(dynamic) onError; + UserService userService; + UserPreferencesService userPreferencesService; + + List postComments = []; + PostComment linkedPostComment; + PostComment postComment; + + static const LOAD_MORE_COMMENTS_COUNT = 5; + static const COUNT_MIN_INCLUDING_LINKED_COMMENT = 3; + static const COUNT_MAX_AFTER_LINKED_COMMENT = 2; + static const TOTAL_COMMENTS_IN_SLICE = + COUNT_MIN_INCLUDING_LINKED_COMMENT + COUNT_MAX_AFTER_LINKED_COMMENT; + + CancelableOperation _refreshCommentsOperation; + CancelableOperation _refreshCommentsSliceOperation; + CancelableOperation _refreshCommentsWithCreatedPostCommentVisibleOperation; + CancelableOperation _refreshPostOperation; + CancelableOperation _loadMoreBottomCommentsOperation; + CancelableOperation _loadMoreTopCommentsOperation; + CancelableOperation _toggleSortCommentsOperation; + + OBPostCommentsPageController({ + @required this.post, + @required this.pageType, + @required this.currentSort, + @required this.userService, + @required this.userPreferencesService, + @required this.setPostComments, + @required this.setCurrentSortValue, + @required this.setNoMoreBottomItemsToLoad, + @required this.setNoMoreTopItemsToLoad, + @required this.addPostComments, + @required this.addToStartPostComments, + @required this.showNoMoreTopItemsToLoadToast, + @required this.scrollToTop, + @required this.scrollToNewComment, + @required this.unfocusCommentInput, + @required this.onError, + this.linkedPostComment, + this.postComment + }) { + this.bootstrapController(); + } + + Future bootstrapController() async { + if (this.linkedPostComment != null) { + await this.refreshCommentsSlice(); + } else { + await this.refreshComments(); + } + } + + CancelableOperation retrieveObjects({int minId, int maxId, int countMax, + int countMin, PostCommentsSortType sort}) { + + if (this.pageType == PostCommentsPageType.comments) { + return CancelableOperation.fromFuture( + this.userService.getCommentsForPost(this.post, + sort: sort, + minId: minId, + maxId: maxId, + countMax: countMax, + countMin: countMin)); + + } else { + return CancelableOperation.fromFuture( + this.userService.getCommentRepliesForPostComment(this.post, this.postComment, + sort: sort, + minId: minId, + maxId: maxId, + countMax: countMax, + countMin: countMin)); + } + } + + void onWantsToToggleSortComments() async { + PostCommentsSortType newSortType; + if (currentSort == PostCommentsSortType.asc) { + newSortType = PostCommentsSortType.dec; + } else { + newSortType = PostCommentsSortType.asc; + } + this.userPreferencesService.setPostCommentsSortType(newSortType); + this.setNewSortValue(newSortType); + this.onWantsToRefreshComments(); + } + + Future onWantsToRefreshComments() async { + if (_refreshCommentsOperation != null) _refreshCommentsOperation.cancel(); + try { + _refreshCommentsOperation = this.retrieveObjects(sort: this.currentSort); + this.postComments = (await _refreshCommentsOperation.value).comments; + this.setPostComments(this.postComments); + this.setNoMoreBottomItemsToLoad(false); + this.setNoMoreTopItemsToLoad(true); + } catch (error) { + this.onError(error); + } finally { + _refreshCommentsOperation = null; + } + } + + Future refreshComments() async { + await this.onWantsToRefreshComments(); + this.scrollToTop(); + } + + Future loadMoreTopComments() async { + if (_loadMoreTopCommentsOperation != null) + _loadMoreTopCommentsOperation.cancel(); + if (this.postComments.length == 0) return true; + + List topComments; + PostComment firstPost = this.postComments.first; + int firstPostId = firstPost.id; + try { + if (this.currentSort == PostCommentsSortType.dec) { + _loadMoreTopCommentsOperation = this.retrieveObjects( + sort: PostCommentsSortType.dec, + countMin: LOAD_MORE_COMMENTS_COUNT, + minId: firstPostId + 1); + } else if (this.currentSort == PostCommentsSortType.asc) { + _loadMoreTopCommentsOperation = this.retrieveObjects( + sort: PostCommentsSortType.asc, + countMax: LOAD_MORE_COMMENTS_COUNT, + maxId: firstPostId); + } + + topComments = (await _loadMoreTopCommentsOperation.value).comments; + + if (topComments.length < LOAD_MORE_COMMENTS_COUNT && + topComments.length != 0) { + this.setNoMoreTopItemsToLoad(true); + this.addToStartPostComments(topComments); + } else if (topComments.length == LOAD_MORE_COMMENTS_COUNT) { + this.addToStartPostComments(topComments); + } else { + this.setNoMoreTopItemsToLoad(true); + this.showNoMoreTopItemsToLoadToast(); + } + return true; + } catch (error) { + this.onError(error); + } finally { + _loadMoreTopCommentsOperation = null; + } + + return false; + } + + Future loadMoreBottomComments() async { + if (_loadMoreBottomCommentsOperation != null) + _loadMoreBottomCommentsOperation.cancel(); + if (this.postComments.length == 0) return true; + + PostComment lastPost = this.postComments.last; + int lastPostId = lastPost.id; + List moreComments; + try { + if (this.currentSort == PostCommentsSortType.dec) { + _loadMoreBottomCommentsOperation = this.retrieveObjects( + countMax: LOAD_MORE_COMMENTS_COUNT, + maxId: lastPostId, + sort: this.currentSort); + } else { + _loadMoreBottomCommentsOperation = this.retrieveObjects( + countMin: LOAD_MORE_COMMENTS_COUNT, + minId: lastPostId + 1, + sort: this.currentSort); + } + + moreComments = (await _loadMoreBottomCommentsOperation.value).comments; + + if (moreComments.length == 0) { + this.setNoMoreBottomItemsToLoad(true); + } else { + this.addPostComments(moreComments); + } + return true; + } catch (error) { + this.onError(error); + } finally { + _loadMoreBottomCommentsOperation = null; + } + + return false; + } + + Future refreshCommentsSlice() async { + if (_refreshCommentsSliceOperation != null) + _refreshCommentsSliceOperation.cancel(); + try { + _refreshCommentsSliceOperation = this.retrieveObjects( + minId: this.linkedPostComment.id, + maxId: this.linkedPostComment.id, + countMax: COUNT_MAX_AFTER_LINKED_COMMENT, + countMin: COUNT_MIN_INCLUDING_LINKED_COMMENT, + sort: this.currentSort); + + this.postComments = (await _refreshCommentsSliceOperation.value).comments; + this.setPostComments(this.postComments); + this.checkIfMoreTopItemsToLoad(); + this.setNoMoreBottomItemsToLoad(false); + } catch (error) { + this.onError(error); + } finally { + _refreshCommentsSliceOperation = null; + } + } + + void checkIfMoreTopItemsToLoad() { + int linkedCommentId = this.linkedPostComment.id; + Iterable listBeforeLinkedComment = []; + if (this.currentSort == PostCommentsSortType.dec) { + listBeforeLinkedComment = + postComments.where((comment) => comment.id > linkedCommentId); + } else if (this.currentSort == PostCommentsSortType.asc) { + listBeforeLinkedComment = + postComments.where((comment) => comment.id < linkedCommentId); + } + if (listBeforeLinkedComment.length < 2) { + this.setNoMoreTopItemsToLoad(true); + } + } + + void refreshCommentsWithCreatedPostCommentVisible( + PostComment createdPostComment) async { + if (_refreshCommentsWithCreatedPostCommentVisibleOperation != null) + _refreshCommentsWithCreatedPostCommentVisibleOperation.cancel(); + this.unfocusCommentInput(); + List comments; + int createdCommentId = createdPostComment.id; + try { + if (this.currentSort == PostCommentsSortType.dec) { + _refreshCommentsWithCreatedPostCommentVisibleOperation = this.retrieveObjects( + sort: PostCommentsSortType.dec, + countMax: LOAD_MORE_COMMENTS_COUNT, + maxId: createdCommentId + 1); + this.setNoMoreTopItemsToLoad(true); + this.setNoMoreBottomItemsToLoad(false); + } else if (this.currentSort == PostCommentsSortType.asc) { + _refreshCommentsWithCreatedPostCommentVisibleOperation = this.retrieveObjects( + sort: PostCommentsSortType.asc, + countMax: LOAD_MORE_COMMENTS_COUNT, + maxId: createdCommentId + 1); + this.setNoMoreTopItemsToLoad(false); + this.setNoMoreBottomItemsToLoad(false); + } + comments = + (await _refreshCommentsWithCreatedPostCommentVisibleOperation.value) + .comments; + this.postComments = comments; + this.setPostComments(this.postComments); + this.scrollToNewComment(); + } catch (error) { + this.onError(error); + } finally { + _refreshCommentsWithCreatedPostCommentVisibleOperation = null; + } + } + + void setNewSortValue(PostCommentsSortType newSortType) { + this.currentSort = newSortType; + this.setCurrentSortValue(newSortType); + } + + void updateControllerPostComments(List comments) { + this.postComments = comments; + } + + void dispose() { + if (_refreshCommentsOperation != null) _refreshCommentsOperation.cancel(); + if (_refreshCommentsSliceOperation != null) + _refreshCommentsSliceOperation.cancel(); + if (_loadMoreBottomCommentsOperation != null) + _loadMoreBottomCommentsOperation.cancel(); + if (_refreshPostOperation != null) _refreshPostOperation.cancel(); + if (_toggleSortCommentsOperation != null) + _toggleSortCommentsOperation.cancel(); + if (_loadMoreTopCommentsOperation != null) + _loadMoreTopCommentsOperation.cancel(); + if (_refreshCommentsWithCreatedPostCommentVisibleOperation != null) + _refreshCommentsWithCreatedPostCommentVisibleOperation.cancel(); + } +} + +enum PostCommentsPageType { replies, comments } \ No newline at end of file diff --git a/lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart b/lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart new file mode 100644 index 000000000..d4603d2aa --- /dev/null +++ b/lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart @@ -0,0 +1,152 @@ +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/theme.dart'; +import 'package:Openbook/services/theme_value_parser.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; + + +class OBPostCommentsHeaderBar extends StatelessWidget { + PostCommentsPageType pageType; + bool noMoreTopItemsToLoad; + List postComments; + PostCommentsSortType currentSort; + VoidCallback onWantsToToggleSortComments; + VoidCallback loadMoreTopComments; + VoidCallback onWantsToRefreshComments; + + static const PAGE_COMMENTS_TEXT_MAP = { + 'NEWEST': 'Newest comments', + 'NEWER': 'Newer', + 'VIEW_NEWEST': 'View newest comments', + 'SEE_NEWEST': 'See newest comments', + 'OLDEST': 'Oldest comments', + 'OLDER': 'Older', + 'VIEW_OLDEST': 'View oldest comments', + 'SEE_OLDEST': 'See oldest comments', + 'BE_THE_FIRST': 'Be the first to comment', + }; + + static const PAGE_REPLIES_TEXT_MAP = { + 'NEWEST': 'Newest replies', + 'NEWER': 'Newer', + 'VIEW_NEWEST': 'View newest replies', + 'SEE_NEWEST': 'See newest replies', + 'OLDEST': 'Oldest replies', + 'OLDER': 'Older', + 'VIEW_OLDEST': 'View oldest replies', + 'SEE_OLDEST': 'See oldest replies', + 'BE_THE_FIRST': 'Be the first to reply', + }; + + OBPostCommentsHeaderBar({ + @required this.pageType, + @required this.noMoreTopItemsToLoad, + @required this.postComments, + @required this.currentSort, + @required this.onWantsToToggleSortComments, + @required this.loadMoreTopComments, + @required this.onWantsToRefreshComments, + }); + + @override + Widget build(BuildContext context) { + var provider = OpenbookProvider.of(context); + ThemeService _themeService = provider.themeService; + ThemeValueParserService _themeValueParserService = provider.themeValueParserService; + var theme = _themeService.getActiveTheme(); + Map _pageTextMap; + if (this.pageType == PostCommentsPageType.comments) { + _pageTextMap = PAGE_COMMENTS_TEXT_MAP; + } else { + _pageTextMap = PAGE_REPLIES_TEXT_MAP; + } + + + if (this.noMoreTopItemsToLoad) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.fromLTRB(10.0, 0.0, 0.0, 0.0), + child: OBSecondaryText( + this.postComments.length > 0 + ? this.currentSort == PostCommentsSortType.dec + ? _pageTextMap['NEWEST'] + : _pageTextMap['OLDEST'] + : _pageTextMap['BE_THE_FIRST'], + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0), + ), + ), + ), + Expanded( + child: FlatButton( + child: OBText( + this.postComments.length > 0 + ? this.currentSort == PostCommentsSortType.dec + ? _pageTextMap['SEE_OLDEST'] + : _pageTextMap['SEE_NEWEST'] + : '', + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: _themeValueParserService + .parseGradient(theme.primaryAccentColor) + .colors[1], + fontWeight: FontWeight.bold), + ), + onPressed: this.onWantsToToggleSortComments), + ), + ], + ), + ); + } else { + return Container( + padding: EdgeInsets.symmetric(horizontal: 0.0, vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 4, + child: FlatButton( + child: Row( + children: [ + OBIcon(OBIcons.arrowUp), + const SizedBox(width: 10.0), + OBText( + this.currentSort == PostCommentsSortType.dec + ? _pageTextMap['NEWER'] + : _pageTextMap['OLDER'], + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + onPressed: this.loadMoreTopComments), + ), + Expanded( + flex: 6, + child: FlatButton( + child: OBText( + this.currentSort == PostCommentsSortType.dec + ? _pageTextMap['VIEW_NEWEST'] + : _pageTextMap['VIEW_OLDEST'], + style: TextStyle( + color: _themeValueParserService + .parseGradient(theme.primaryAccentColor) + .colors[1], + fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, + ), + onPressed: this.onWantsToRefreshComments), + ), + ], + ), + ); + } + } +} diff --git a/lib/pages/home/pages/post_comments/widgets/post_preview.dart b/lib/pages/home/pages/post_comments/widgets/post_preview.dart new file mode 100644 index 000000000..73d6ff5bd --- /dev/null +++ b/lib/pages/home/pages/post_comments/widgets/post_preview.dart @@ -0,0 +1,51 @@ +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/theme.dart'; +import 'package:Openbook/services/theme_value_parser.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/post/widgets/post-actions/post_actions.dart'; +import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; +import 'package:Openbook/widgets/post/widgets/post_circles.dart'; +import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; +import 'package:Openbook/widgets/post/widgets/post_reactions/post_reactions.dart'; +import 'package:Openbook/widgets/theming/post_divider.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; + + +class OBPostPreview extends StatelessWidget { + final Post post; + final Function(Post) onPostDeleted; + final VoidCallback focusCommentInput; + GlobalKey _keyPostBody = GlobalKey(); + + OBPostPreview({this.post, this.onPostDeleted, this.focusCommentInput}); + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBPostHeader( + post: this.post, + onPostDeleted: this.onPostDeleted, + ), + Container( + key: _keyPostBody, + child: OBPostBody(this.post), + ), + OBPostReactions(this.post), + OBPostCircles(this.post), + OBPostActions( + this.post, + onWantsToCommentPost: this.focusCommentInput, + ), + const SizedBox( + height: 16, + ), + OBPostDivider() + ], + ); + } +} \ No newline at end of file From 1ab342e17f87539cfe840f1d9acc9330908e554a Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 16 May 2019 18:23:59 +0200 Subject: [PATCH 04/65] :sparkles: comment reply notification tiles --- .../notification_tile/notification_tile.dart | 9 +++ .../post_comment_reply_notification_tile.dart | 81 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 lib/widgets/tiles/notification_tile/widgets/post_comment_reply_notification_tile.dart diff --git a/lib/widgets/tiles/notification_tile/notification_tile.dart b/lib/widgets/tiles/notification_tile/notification_tile.dart index adeb99c78..b57692da8 100644 --- a/lib/widgets/tiles/notification_tile/notification_tile.dart +++ b/lib/widgets/tiles/notification_tile/notification_tile.dart @@ -4,6 +4,7 @@ import 'package:Openbook/models/notifications/connection_request_notification.da import 'package:Openbook/models/notifications/follow_notification.dart'; import 'package:Openbook/models/notifications/notification.dart'; import 'package:Openbook/models/notifications/post_comment_notification.dart'; +import 'package:Openbook/models/notifications/post_comment_reply_notification.dart'; import 'package:Openbook/models/notifications/post_reaction_notification.dart'; import 'package:Openbook/models/theme.dart'; import 'package:Openbook/provider.dart'; @@ -14,6 +15,7 @@ import 'package:Openbook/widgets/tiles/notification_tile/widgets/connection_conf import 'package:Openbook/widgets/tiles/notification_tile/widgets/connection_request_notification_tile.dart'; import 'package:Openbook/widgets/tiles/notification_tile/widgets/follow_notification_tile.dart'; import 'package:Openbook/widgets/tiles/notification_tile/widgets/post_comment_notification_tile.dart'; +import 'package:Openbook/widgets/tiles/notification_tile/widgets/post_comment_reply_notification_tile.dart'; import 'package:Openbook/widgets/tiles/notification_tile/widgets/post_reaction_notification_tile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -78,6 +80,13 @@ class OBNotificationTile extends StatelessWidget { onPressed: finalOnPressed, ); break; + case PostCommentReplyNotification: + notificationTile = OBPostCommentReplyNotificationTile( + notification: notification, + postCommentNotification: notificationContentObject, + onPressed: finalOnPressed, + ); + break; case PostReactionNotification: notificationTile = OBPostReactionNotificationTile( notification: notification, diff --git a/lib/widgets/tiles/notification_tile/widgets/post_comment_reply_notification_tile.dart b/lib/widgets/tiles/notification_tile/widgets/post_comment_reply_notification_tile.dart new file mode 100644 index 000000000..dfd7ef7b4 --- /dev/null +++ b/lib/widgets/tiles/notification_tile/widgets/post_comment_reply_notification_tile.dart @@ -0,0 +1,81 @@ +import 'package:Openbook/models/notifications/notification.dart'; +import 'package:Openbook/models/notifications/post_comment_notification.dart'; +import 'package:Openbook/models/notifications/post_comment_reply_notification.dart'; +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/avatars/avatar.dart'; +import 'package:Openbook/widgets/theming/actionable_smart_text.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_advanced_networkimage/provider.dart'; + +class OBPostCommentReplyNotificationTile extends StatelessWidget { + final OBNotification notification; + final PostCommentReplyNotification postCommentNotification; + static final double postImagePreviewSize = 40; + final VoidCallback onPressed; + + const OBPostCommentReplyNotificationTile( + {Key key, + @required this.notification, + @required this.postCommentNotification, + this.onPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + PostComment postComment = postCommentNotification.postComment; + PostComment parentComment = postCommentNotification.parentComment; + Post post = postComment.post; + String postCommenterUsername = postComment.getCommenterUsername(); + String postCommentText = postComment.text; + + int postCreatorId = postCommentNotification.getPostCreatorId(); + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + bool isOwnPostNotification = + openbookProvider.userService.getLoggedInUser().id == postCreatorId; + + Widget postImagePreview; + if (post.hasImage()) { + postImagePreview = ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: Image( + image: AdvancedNetworkImage(post.getImage(), useDiskCache: true), + height: postImagePreviewSize, + width: postImagePreviewSize, + fit: BoxFit.cover, + ), + ); + } + + Function navigateToCommenterProfile = () { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + + openbookProvider.navigationService + .navigateToUserProfile(user: postComment.commenter, context: context); + }; + + return ListTile( + onTap: () { + if (onPressed != null) onPressed(); + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + + openbookProvider.navigationService.navigateToPostCommentRepliesLinked( + postComment: postComment, context: context, parentComment: parentComment); + }, + leading: OBAvatar( + onPressed: navigateToCommenterProfile, + size: OBAvatarSize.medium, + avatarUrl: postComment.commenter.getProfileAvatar(), + ), + title: OBActionableSmartText( + text: isOwnPostNotification + ? '@$postCommenterUsername replied: $postCommentText' + : '@$postCommenterUsername also replied: $postCommentText', + ), + trailing: postImagePreview, + subtitle: OBSecondaryText(notification.getRelativeCreated()), + ); + } +} From 8bc94dcc546e96fb918936f6e2fc4b6984c20502 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 16 May 2019 18:41:21 +0200 Subject: [PATCH 05/65] :sparkles: post reply expanded commenter widget --- .../post-comment-reply-expanded.dart | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart diff --git a/lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart b/lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart new file mode 100644 index 000000000..e44002ea8 --- /dev/null +++ b/lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart @@ -0,0 +1,210 @@ +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/pages/home/modals/create_post/widgets/create_post_text.dart'; +import 'package:Openbook/pages/home/modals/create_post/widgets/remaining_post_characters.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/httpie.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/services/validation.dart'; +import 'package:Openbook/widgets/avatars/logged_in_user_avatar.dart'; +import 'package:Openbook/widgets/avatars/avatar.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/theming/post_divider.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + + +class OBPostCommentReplyExpandedModal extends StatefulWidget { + final Post post; + final PostComment postComment; + final Function(PostComment) onReplyAdded; + final Function(PostComment) onReplyDeleted; + + const OBPostCommentReplyExpandedModal({Key key, this.post, this.postComment, this.onReplyAdded, this.onReplyDeleted}) : super(key: key); + + @override + State createState() { + return OBPostCommentReplyExpandedModalState(); + } +} + +class OBPostCommentReplyExpandedModalState extends State { + ValidationService _validationService; + NavigationService _navigationService; + ToastService _toastService; + UserService _userService; + + TextEditingController _textController; + int _charactersCount; + bool _isPostCommentTextAllowedLength; + List _postCommentItemsWidgets; + + @override + void initState() { + super.initState(); + _textController = TextEditingController(); + _textController.addListener(_onPostCommentTextChanged); + _charactersCount = 0; + _isPostCommentTextAllowedLength = false; + String hintText = 'Your reply...'; + _postCommentItemsWidgets = [OBCreatePostText(controller: _textController, hintText: hintText)]; + + } + + @override + void dispose() { + super.dispose(); + _textController.removeListener(_onPostCommentTextChanged); + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _validationService = openbookProvider.validationService; + _navigationService = openbookProvider.navigationService; + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + + return CupertinoPageScaffold( + backgroundColor: Colors.transparent, + navigationBar: _buildNavigationBar(), + child: OBPrimaryColorContainer( + child: Column( + children: [_buildPostCommentContent()], + ))); + } + + Widget _buildNavigationBar() { + bool isPrimaryActionButtonIsEnabled = + (_isPostCommentTextAllowedLength && _charactersCount > 0); + + return OBThemedNavigationBar( + leading: GestureDetector( + child: const OBIcon(OBIcons.close), + onTap: () { + Navigator.pop(context); + }, + ), + title: 'Reply comment', + trailing: + _buildPrimaryActionButton(isEnabled: isPrimaryActionButtonIsEnabled), + ); + } + + Widget _buildPrimaryActionButton({bool isEnabled}) { + Widget primaryButton; + + if (isEnabled) { + primaryButton = GestureDetector( + onTap: _onWantsToReplyComment, + child: const OBText('Post'), + ); + } else { + primaryButton = Opacity( + opacity: 0.5, + child: const OBText('Post'), + ); + } + + return primaryButton; + } + + void _onWantsToReplyComment() async { + PostComment comment; + if (widget.postComment != null) { + comment = await _userService.replyPostComment( + post: widget.post, + postComment: widget.postComment, + text: _textController.text); + } + if (comment != null) { + // Remove modal + if (widget.onReplyAdded != null) widget.onReplyAdded(comment); + Navigator.pop(context, comment); + _navigationService.navigateToPostCommentReplies( + post: widget.post, + postComment: widget.postComment, + onReplyAdded: widget.onReplyAdded, + onReplyDeleted: widget.onReplyDeleted, + context: context); + } + } + + Widget _buildPostCommentContent() { + return Expanded( + child: Padding( + padding: EdgeInsets.only(left: 0.0, top: 20.0), + child: Column( + children: [ + OBPostCommentTile(post:widget.post, postComment: widget.postComment), + OBPostDivider(), + Padding( + padding: EdgeInsets.only(left: 20.0, top: 10.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + OBLoggedInUserAvatar( + size: OBAvatarSize.medium, + ), + const SizedBox( + height: 12.0, + ), + OBRemainingPostCharacters( + maxCharacters: ValidationService.POST_COMMENT_MAX_LENGTH, + currentCharacters: _charactersCount, + ), + ], + ), + Expanded( + child: SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Padding( + padding: + EdgeInsets.only(left: 20.0, right: 20.0, bottom: 30.0, top: 0.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _postCommentItemsWidgets)), + ), + ) + ], + ), + ) + ], + ) + )); + } + + void _onPostCommentTextChanged() { + String text = _textController.text; + setState(() { + _charactersCount = text.length; + _isPostCommentTextAllowedLength = + _validationService.isPostCommentAllowedLength(text); + }); + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } + + void _unfocusTextField() { + FocusScope.of(context).requestFocus(new FocusNode()); + } +} From b71618f2eded51ebe46a2b247ff6ece5c44d76e6 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 16 May 2019 18:41:44 +0200 Subject: [PATCH 06/65] :sparkles: add apis for replies, methods in services --- lib/services/modal_service.dart | 23 +++++++++++ lib/services/navigation_service.dart | 57 +++++++++++++++++++++++++--- lib/services/posts_api.dart | 37 ++++++++++++++++++ lib/services/user.dart | 32 ++++++++++++++++ 4 files changed, 144 insertions(+), 5 deletions(-) diff --git a/lib/services/modal_service.dart b/lib/services/modal_service.dart index 81c06f85b..cde037736 100644 --- a/lib/services/modal_service.dart +++ b/lib/services/modal_service.dart @@ -11,6 +11,7 @@ import 'package:Openbook/models/user_invite.dart'; import 'package:Openbook/pages/home/modals/accept_guidelines/accept_guidelines.dart'; import 'package:Openbook/pages/home/modals/edit_post/edit_post.dart'; import 'package:Openbook/pages/home/modals/invite_to_community.dart'; +import 'package:Openbook/pages/home/modals/post_comment/post-comment-reply-expanded.dart'; import 'package:Openbook/pages/home/modals/post_comment/post-commenter-expanded.dart'; import 'package:Openbook/pages/home/pages/community/pages/manage_community/pages/community_administrators/modals/add_community_administrator/add_community_administrator.dart'; import 'package:Openbook/pages/home/modals/create_post/create_post.dart'; @@ -83,6 +84,28 @@ class ModalService { return editedComment; } + Future openExpandedReplyCommenter( + {@required BuildContext context, + @required PostComment postComment, + @required Post post, + @required Function(PostComment) onReplyAdded, + @required Function(PostComment) onReplyDeleted}) async { + PostComment replyComment = await Navigator.of(context, rootNavigator: true) + .push(CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) { + return Material( + child: OBPostCommentReplyExpandedModal( + post: post, + postComment: postComment, + onReplyAdded: onReplyAdded, + onReplyDeleted: onReplyDeleted + ), + ); + })); + return replyComment; + } + Future openEditUserProfile( {@required User user, @required BuildContext context}) async { Navigator.of(context, rootNavigator: true) diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 65b33a262..d6c5d7f4f 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -44,8 +44,8 @@ import 'package:Openbook/pages/home/pages/menu/pages/user_invites/user_invites.d import 'package:Openbook/pages/home/pages/menu/pages/themes/themes.dart'; import 'package:Openbook/pages/home/pages/notifications/pages/notifications_settings.dart'; import 'package:Openbook/pages/home/pages/post/post.dart'; -import 'package:Openbook/pages/home/pages/post_comments/post.dart'; -import 'package:Openbook/pages/home/pages/post_comments/post_comments_linked.dart'; +import 'package:Openbook/pages/home/pages/post_comments/post_comments_page.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart'; import 'package:Openbook/pages/home/pages/profile/profile.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/routes/slide_right_route.dart'; @@ -230,7 +230,10 @@ class NavigationService { context, OBSlideRightRoute( key: Key('obSlidePostComments'), - widget: OBPostCommentsPage(post, autofocusCommentInput: true))); + widget: OBPostCommentsPage( + post:post, + showPostPreview: false, + autofocusCommentInput: true))); } Future navigateToPostComments( @@ -239,7 +242,32 @@ class NavigationService { context, OBSlideRightRoute( key: Key('obSlideViewComments'), - widget: OBPostCommentsPage(post, autofocusCommentInput: false))); + widget: OBPostCommentsPage( + post: post, + showPostPreview: false, + pageType: PostCommentsPageType.comments, + autofocusCommentInput: false) + )); + } + + Future navigateToPostCommentReplies( + {@required Post post, + @required PostComment postComment, + @required BuildContext context, + Function(PostComment) onReplyDeleted, + Function(PostComment) onReplyAdded + }) { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obSlideViewComments'), + widget: OBPostCommentsPage( + post: post, + showPostPreview: false, + postComment: postComment, + onCommentDeleted: onReplyDeleted, + onCommentAdded: onReplyAdded, + autofocusCommentInput: false))); } Future navigateToPostCommentsLinked( @@ -248,7 +276,26 @@ class NavigationService { context, OBSlideRightRoute( key: Key('obSlideViewCommentsLinked'), - widget: OBPostCommentsLinkedPage(postComment, + widget: OBPostCommentsPage( + post: postComment.post, + showPostPreview: true, + pageType: PostCommentsPageType.comments, + linkedPostComment: postComment, + autofocusCommentInput: false))); + } + + Future navigateToPostCommentRepliesLinked( + {@required PostComment postComment, @required PostComment parentComment, @required BuildContext context}) { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obSlideViewCommentsLinked'), + widget: OBPostCommentsPage( + post: postComment.post, + postComment: parentComment, + showPostPreview: true, + pageType: PostCommentsPageType.replies, + linkedPostComment: postComment, autofocusCommentInput: false))); } diff --git a/lib/services/posts_api.dart b/lib/services/posts_api.dart index f6515cf3a..f5be86fb3 100644 --- a/lib/services/posts_api.dart +++ b/lib/services/posts_api.dart @@ -19,6 +19,7 @@ class PostsApiService { static const CLOSE_POST_PATH = 'api/posts/{postUuid}/close/'; static const COMMENT_POST_PATH = 'api/posts/{postUuid}/comments/'; static const EDIT_COMMENT_POST_PATH = 'api/posts/{postUuid}/comments/{postCommentId}/'; + static const REPLY_COMMENT_POST_PATH = 'api/posts/{postUuid}/comments/{postCommentId}/replies/'; static const MUTE_POST_PATH = 'api/posts/{postUuid}/notifications/mute/'; static const UNMUTE_POST_PATH = 'api/posts/{postUuid}/notifications/unmute/'; static const DELETE_POST_COMMENT_PATH = @@ -145,6 +146,23 @@ class PostsApiService { queryParameters: queryParams, appendAuthorizationToken: true); } + Future getRepliesForCommentWithIdForPostWithUuid( + String postUuid, int postCommentId, + {int countMax, int maxId, int countMin, int minId, String sort}) { + Map queryParams = {}; + if (countMax != null) queryParams['count_max'] = countMax; + if (countMin != null) queryParams['count_min'] = countMin; + + if (maxId != null) queryParams['max_id'] = maxId; + if (minId != null) queryParams['min_id'] = minId; + if (sort != null) queryParams['sort'] = sort; + + String path = _makeGetReplyCommentsPostPath(postUuid, postCommentId); + + return _httpService.get(_makeApiUrl(path), + queryParameters: queryParams, appendAuthorizationToken: true); + } + Future commentPost( {@required String postUuid, @required String text}) { Map body = {'text': text}; @@ -163,6 +181,15 @@ class PostsApiService { body: body, appendAuthorizationToken: true); } + Future replyPostComment( + {@required String postUuid, @required int postCommentId, @required String text}) { + Map body = {'text': text}; + + String path = _makeReplyCommentPostPath(postUuid, postCommentId); + return _httpService.putJSON(_makeApiUrl(path), + body: body, appendAuthorizationToken: true); + } + Future deletePostComment( {@required postCommentId, @required postUuid}) { String path = _makeDeletePostCommentPath( @@ -292,6 +319,16 @@ class PostsApiService { .parse(EDIT_COMMENT_POST_PATH, {'postUuid': postUuid, 'postCommentId': postCommentId}); } + String _makeReplyCommentPostPath(String postUuid, int postCommentId) { + return _stringTemplateService + .parse(REPLY_COMMENT_POST_PATH, {'postUuid': postUuid, 'postCommentId': postCommentId}); + } + + String _makeGetReplyCommentsPostPath(String postUuid, int postCommentId) { + return _stringTemplateService + .parse(REPLY_COMMENT_POST_PATH, {'postUuid': postUuid, 'postCommentId': postCommentId}); + } + String _makeGetPostCommentsPath(String postUuid) { return _stringTemplateService .parse(GET_POST_COMMENTS_PATH, {'postUuid': postUuid}); diff --git a/lib/services/user.dart b/lib/services/user.dart index 1f87a83ea..f0fa47af3 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -477,6 +477,16 @@ class UserService { return PostComment.fromJSON(json.decode(response.body)); } + Future replyPostComment( + {@required Post post, + @required PostComment postComment, + @required String text}) async { + HttpieResponse response = await _postsApiService.replyPostComment( + postUuid: post.uuid, postCommentId: postComment.id, text: text); + _checkResponseIsCreated(response); + return PostComment.fromJSON(json.decode(response.body)); + } + Future deletePostComment( {@required PostComment postComment, @required Post post}) async { HttpieResponse response = await _postsApiService.deletePostComment( @@ -518,6 +528,28 @@ class UserService { return PostCommentList.fromJson(json.decode(response.body)); } + Future getCommentRepliesForPostComment(Post post, + PostComment postComment, + {int maxId, + int countMax, + int minId, + int countMin, + PostCommentsSortType sort}) async { + HttpieResponse response = await _postsApiService.getRepliesForCommentWithIdForPostWithUuid( + post.uuid, + postComment.id, + countMax: countMax, + maxId: maxId, + countMin: countMin, + minId: minId, + sort: sort != null + ? PostComment.convertPostCommentSortTypeToString(sort) + : null); + + _checkResponseIsOk(response); + return PostCommentList.fromJson(json.decode(response.body)); + } + Future getEmojiGroups() async { HttpieResponse response = await this._emojisApiService.getEmojiGroups(); From 0d649d29a5470fce0cb42778f668e734cfd25efd Mon Sep 17 00:00:00 2001 From: Shantanu Date: Thu, 16 May 2019 19:00:24 +0200 Subject: [PATCH 07/65] :wrench: ios files --- ios/Podfile.lock | 4 ++-- ios/Runner.xcodeproj/project.pbxproj | 2 +- pubspec.lock | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c7f60247d..9a58fd8ce 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -41,7 +41,7 @@ PODS: DEPENDENCIES: - device_info (from `.symlinks/plugins/device_info/ios`) - - Flutter (from `.symlinks/flutter/ios-release`) + - Flutter (from `.symlinks/flutter/ios`) - flutter_exif_rotation (from `.symlinks/plugins/flutter_exif_rotation/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) @@ -68,7 +68,7 @@ EXTERNAL SOURCES: device_info: :path: ".symlinks/plugins/device_info/ios" Flutter: - :path: ".symlinks/flutter/ios-release" + :path: ".symlinks/flutter/ios" flutter_exif_rotation: :path: ".symlinks/plugins/flutter_exif_rotation/ios" flutter_secure_storage: diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index be5dcb8af..81cf5eb61 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -734,7 +734,7 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", + "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( diff --git a/pubspec.lock b/pubspec.lock index 9b86ad845..36a20b472 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -395,7 +395,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.0" petitparser: dependency: transitive description: @@ -526,7 +526,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.4" + version: "1.5.5" sprintf: dependency: "direct main" description: @@ -703,5 +703,5 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.1.0 <3.0.0" + dart: ">=2.1.1-dev.0.0 <3.0.0" flutter: ">=1.2.1 <2.0.0" From f3ef03757678ad335aee57a4adacee44c7f15f67 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Sat, 18 May 2019 14:03:39 +0200 Subject: [PATCH 08/65] :bug: extend can delete comment condition and fix bug --- lib/models/user.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index 5fefffed1..15a60286e 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -436,6 +436,8 @@ class User extends UpdatableModel { User postCommenter = postComment.commenter; bool loggedInUserIsCommunityAdministrator = false; bool loggedInUserIsCommunityModerator = false; + bool loggedInUserIsPostCreator = loggedInUser.id == post.getCreatorId(); + bool userIsCreatorOfNonCommunityPost = loggedInUserIsPostCreator && !post.hasCommunity(); if (post.hasCommunity()) { Community postCommunity = post.community; @@ -449,7 +451,7 @@ class User extends UpdatableModel { return (loggedInUser.id == postCommenter.id || loggedInUserIsCommunityModerator || - loggedInUserIsCommunityAdministrator); + loggedInUserIsCommunityAdministrator || userIsCreatorOfNonCommunityPost); } bool canBlockOrUnblockUser(User user) { From 0ca69975a6cf692c4075099ef4b24f46d0730b7c Mon Sep 17 00:00:00 2001 From: Shantanu Date: Sat, 18 May 2019 14:03:54 +0200 Subject: [PATCH 09/65] :wrench: random ios auto generated files --- ios/Podfile.lock | 4 ++-- ios/Runner.xcodeproj/project.pbxproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c7f60247d..9a58fd8ce 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -41,7 +41,7 @@ PODS: DEPENDENCIES: - device_info (from `.symlinks/plugins/device_info/ios`) - - Flutter (from `.symlinks/flutter/ios-release`) + - Flutter (from `.symlinks/flutter/ios`) - flutter_exif_rotation (from `.symlinks/plugins/flutter_exif_rotation/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) @@ -68,7 +68,7 @@ EXTERNAL SOURCES: device_info: :path: ".symlinks/plugins/device_info/ios" Flutter: - :path: ".symlinks/flutter/ios-release" + :path: ".symlinks/flutter/ios" flutter_exif_rotation: :path: ".symlinks/plugins/flutter_exif_rotation/ios" flutter_secure_storage: diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index be5dcb8af..81cf5eb61 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -734,7 +734,7 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../.symlinks/flutter/ios-release/Flutter.framework", + "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( From f527491304744fa7575c9f9e53be2caeabb78b23 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Sat, 18 May 2019 19:53:46 +0200 Subject: [PATCH 10/65] :bug: fix refresh on invite create n delete --- .../menu/pages/user_invites/user_invites.dart | 32 ++++++++++++------- .../user_invites/widgets/my_invite_group.dart | 5 +++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart b/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart index 11c52c97a..4d860662b 100644 --- a/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart +++ b/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart @@ -104,18 +104,20 @@ class OBUserInvitesPageState extends State { OBPrimaryColorContainer( child: Column( children: [ - _hasAcceptedInvites || _hasPendingInvites - ? _buildInvitesList() - : _buildNoInvitesFallback() - ], - )), + _hasAcceptedInvites || _hasPendingInvites + ? _buildInvitesList() + : _buildNoInvitesFallback() + ], + ) + ), ], - )); + ), + ); } Widget _buildInvitesList() { return Expanded( - child: RefreshIndicator( + child: RefreshIndicator( key: _refreshIndicatorKey, onRefresh: _refreshInvites, child: ListView( @@ -155,8 +157,9 @@ class OBUserInvitesPageState extends State { ), ], ) - ]), - ), + ] + ), + ) ); } @@ -191,7 +194,12 @@ class OBUserInvitesPageState extends State { Widget _onUserInviteDeletedCallback( BuildContext context, UserInvite userInvite) { - _refreshUser(); + int _acceptedInvitesLength = _acceptedInvitesGroupController.getListLength(); + int _pendingInvitesLength = _pendingInvitesGroupController.getListLength(); + if (userInvite.createdUser != null) _user.inviteCount -= 1; + if (userInvite.createdUser == null) _user.inviteCount += 1; + if (_acceptedInvitesLength != null && _acceptedInvitesLength == 0) _acceptedInvitesGroupController.refresh(); + if (_pendingInvitesLength != null && _pendingInvitesLength == 0) _pendingInvitesGroupController.refresh(); } void _bootstrap() async { @@ -203,8 +211,8 @@ class OBUserInvitesPageState extends State { try { await Future.wait([ _refreshUser(), - _acceptedInvitesGroupController.refresh(), - _pendingInvitesGroupController.refresh() + _hasAcceptedInvites ?_acceptedInvitesGroupController.refresh() : _refreshAcceptedInvites(), + _hasPendingInvites ? _pendingInvitesGroupController.refresh() : _refreshPendingInvites(), ]); _scrollToTop(); } catch (error) { diff --git a/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart b/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart index 44a76bdac..dd9ea8be8 100644 --- a/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart +++ b/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart @@ -252,6 +252,11 @@ class OBMyInvitesGroupController { this._state = state; } + int getListLength() { + if (_state == null) return null; + return _state._inviteGroupList.length; + } + void detach() { this._state = null; } From 5fb2a59d0b7f6914438b44efacc3005a8aa0b041 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 21 May 2019 11:24:26 +0200 Subject: [PATCH 11/65] :bug: show proper error messages with feedback when edit/delete close post --- lib/models/user.dart | 1 + .../post_comment/post-commenter-expanded.dart | 32 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index 15a60286e..dd2619e48 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -429,6 +429,7 @@ class User extends UpdatableModel { User postCommenter = postComment.commenter; return loggedInUser.id == postCommenter.id; + } bool canDeletePostComment(Post post, PostComment postComment) { diff --git a/lib/pages/home/modals/post_comment/post-commenter-expanded.dart b/lib/pages/home/modals/post_comment/post-commenter-expanded.dart index 95dceb9a2..8cfef1e1d 100644 --- a/lib/pages/home/modals/post_comment/post-commenter-expanded.dart +++ b/lib/pages/home/modals/post_comment/post-commenter-expanded.dart @@ -117,20 +117,24 @@ class OBPostCommenterExpandedModalState extends State Date: Tue, 21 May 2019 11:25:57 +0200 Subject: [PATCH 12/65] :fire: remove linespace --- lib/models/user.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index dd2619e48..15a60286e 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -429,7 +429,6 @@ class User extends UpdatableModel { User postCommenter = postComment.commenter; return loggedInUser.id == postCommenter.id; - } bool canDeletePostComment(Post post, PostComment postComment) { From 8187d73f7862571c01d0262b8531e1b7bb90cc01 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 22 May 2019 13:09:09 +0200 Subject: [PATCH 13/65] :ok_hand: do not show actions not possible --- lib/models/user.dart | 62 +++++++++++-------- .../widgets/post_comment/post_comment.dart | 2 +- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index 15a60286e..e5a8e146c 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -392,26 +392,32 @@ class User extends UpdatableModel { return _canComment; } - bool canDeletePost(Post post) { + bool isStaffForCommunity(Community community) { User loggedInUser = this; - bool loggedInUserIsPostCreator = loggedInUser.id == post.getCreatorId(); bool loggedInUserIsCommunityAdministrator = false; bool loggedInUserIsCommunityModerator = false; - bool _canDelete = false; - if (post.hasCommunity()) { - Community postCommunity = post.community; + loggedInUserIsCommunityAdministrator = + community.isAdministrator(loggedInUser); + + loggedInUserIsCommunityModerator = + community.isModerator(loggedInUser); - loggedInUserIsCommunityAdministrator = - postCommunity.isAdministrator(loggedInUser); + return loggedInUserIsCommunityModerator || loggedInUserIsCommunityAdministrator; - loggedInUserIsCommunityModerator = - postCommunity.isModerator(loggedInUser); + } + + bool canDeletePost(Post post) { + User loggedInUser = this; + bool loggedInUserIsPostCreator = loggedInUser.id == post.getCreatorId(); + bool _canDelete = false; + bool loggedInUserIsStaffForCommunity = false; + + if (post.hasCommunity()) { + loggedInUserIsStaffForCommunity = this.isStaffForCommunity(post.community); } - if (loggedInUserIsPostCreator || - loggedInUserIsCommunityAdministrator || - loggedInUserIsCommunityModerator) { + if (loggedInUserIsPostCreator || loggedInUserIsStaffForCommunity) { _canDelete = true; } @@ -421,37 +427,39 @@ class User extends UpdatableModel { bool canEditPost(Post post) { User loggedInUser = this; bool loggedInUserIsPostCreator = loggedInUser.id == post.getCreatorId(); - return loggedInUserIsPostCreator; + + return loggedInUserIsPostCreator && !post.isClosed; } - bool canEditPostComment(PostComment postComment) { + bool canEditPostComment(PostComment postComment, Post post) { User loggedInUser = this; User postCommenter = postComment.commenter; + bool loggedInUserIsStaffForCommunity = false; + bool loggedInUserIsCommenter = loggedInUser.id == postCommenter.id; + bool loggedInUserIsCommenterForOpenPost = loggedInUserIsCommenter && !post.isClosed && post.areCommentsEnabled; + + if (post.hasCommunity()) { + loggedInUserIsStaffForCommunity = this.isStaffForCommunity(post.community); + } - return loggedInUser.id == postCommenter.id; + return loggedInUserIsCommenterForOpenPost || (loggedInUserIsStaffForCommunity && loggedInUserIsCommenter); } bool canDeletePostComment(Post post, PostComment postComment) { User loggedInUser = this; User postCommenter = postComment.commenter; - bool loggedInUserIsCommunityAdministrator = false; - bool loggedInUserIsCommunityModerator = false; bool loggedInUserIsPostCreator = loggedInUser.id == post.getCreatorId(); bool userIsCreatorOfNonCommunityPost = loggedInUserIsPostCreator && !post.hasCommunity(); + bool loggedInUserIsStaffForCommunity = false; + bool loggedInUserIsCommenterForOpenPost = (loggedInUser.id == postCommenter.id) && !post.isClosed; if (post.hasCommunity()) { - Community postCommunity = post.community; - - loggedInUserIsCommunityAdministrator = - postCommunity.isAdministrator(loggedInUser); - - loggedInUserIsCommunityModerator = - postCommunity.isModerator(loggedInUser); + loggedInUserIsStaffForCommunity = this.isStaffForCommunity(post.community); } - return (loggedInUser.id == postCommenter.id || - loggedInUserIsCommunityModerator || - loggedInUserIsCommunityAdministrator || userIsCreatorOfNonCommunityPost); + return (loggedInUserIsCommenterForOpenPost || + loggedInUserIsStaffForCommunity || + userIsCreatorOfNonCommunityPost); } bool canBlockOrUnblockUser(User user) { diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart index b708f1c35..26cafe4f9 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart @@ -134,7 +134,7 @@ class OBPostCommentState extends State { List _editCommentActions = []; User loggedInUser = _userService.getLoggedInUser(); - if (loggedInUser.canEditPostComment(widget.postComment)) { + if (loggedInUser.canEditPostComment(widget.postComment, widget.post)) { _editCommentActions.add( new IconSlideAction( caption: 'Edit', From ff7d0319d2e3451cdd7cfb55867c893daaf39a26 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Wed, 22 May 2019 13:42:17 +0200 Subject: [PATCH 14/65] :art: move refresh to group controllers --- .../home/pages/menu/pages/user_invites/user_invites.dart | 9 +++------ .../menu/pages/user_invites/widgets/my_invite_group.dart | 6 +----- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart b/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart index 4d860662b..f04e065a3 100644 --- a/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart +++ b/lib/pages/home/pages/menu/pages/user_invites/user_invites.dart @@ -194,12 +194,9 @@ class OBUserInvitesPageState extends State { Widget _onUserInviteDeletedCallback( BuildContext context, UserInvite userInvite) { - int _acceptedInvitesLength = _acceptedInvitesGroupController.getListLength(); - int _pendingInvitesLength = _pendingInvitesGroupController.getListLength(); - if (userInvite.createdUser != null) _user.inviteCount -= 1; - if (userInvite.createdUser == null) _user.inviteCount += 1; - if (_acceptedInvitesLength != null && _acceptedInvitesLength == 0) _acceptedInvitesGroupController.refresh(); - if (_pendingInvitesLength != null && _pendingInvitesLength == 0) _pendingInvitesGroupController.refresh(); + setState(() { + if (userInvite.createdUser == null) _user.inviteCount += 1; + }); } void _bootstrap() async { diff --git a/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart b/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart index dd9ea8be8..0755c375d 100644 --- a/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart +++ b/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart @@ -152,6 +152,7 @@ class OBMyInvitesGroupState extends State { var onUserInviteDeletedCallback = () { _removeUserInvite(userInvite); widget.inviteGroupListItemDeleteCallback(context, userInvite); + if (_inviteGroupList.length == 0) _refreshInvites(); }; return OBUserInviteTile( @@ -252,11 +253,6 @@ class OBMyInvitesGroupController { this._state = state; } - int getListLength() { - if (_state == null) return null; - return _state._inviteGroupList.length; - } - void detach() { this._state = null; } From a3e4dc07c5811336343b5618acb97cfa9b1914c7 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Wed, 22 May 2019 15:32:23 +0200 Subject: [PATCH 15/65] :white_check_mark: test IsSupended permission --- lib/pages/home/home.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 991201141..131053d4a 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -422,7 +422,7 @@ class OBHomePageState extends ReceiveShareState _pushNotificationSubscription = _pushNotificationsService.pushNotification .listen(_onPushNotification); - if (!newUser.areGuidelinesAccepted) { + if (!newUser.areGuidelinesAccepted != null && !newUser.areGuidelinesAccepted) { _modalService.openAcceptGuidelines(context: context); } } From a802174e412e97c19f6ad5ac0666353aa869c095 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Fri, 24 May 2019 14:33:59 +0200 Subject: [PATCH 16/65] :sparkles: add reporting methods --- lib/models/moderation/moderated_object.dart | 174 ++++++++++++++++++ .../moderation/moderation_category.dart | 51 +++++ lib/services/auth_api.dart | 58 ++++-- lib/services/communities_api.dart | 30 ++- lib/services/posts_api.dart | 66 ++++++- lib/services/user.dart | 53 +++++- 6 files changed, 400 insertions(+), 32 deletions(-) create mode 100644 lib/models/moderation/moderated_object.dart create mode 100644 lib/models/moderation/moderation_category.dart diff --git a/lib/models/moderation/moderated_object.dart b/lib/models/moderation/moderated_object.dart new file mode 100644 index 000000000..20f4ad30a --- /dev/null +++ b/lib/models/moderation/moderated_object.dart @@ -0,0 +1,174 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/models/updatable_model.dart'; +import 'package:Openbook/models/user.dart'; +import 'package:dcache/dcache.dart'; +import 'package:meta/meta.dart'; + +class ModeratedObject extends UpdatableModel { + static final factory = ModeratedObjectFactory(); + + static String objectTypePost = 'P'; + static String objectTypePostComment = 'PC'; + static String objectTypeCommunity = 'C'; + static String objectTypeUser = 'C'; + static String statusPending = 'P'; + static String statusApproved = 'A'; + static String statusRejected = 'R'; + + final int id; + final Community community; + + dynamic contentObject; + ModeratedObjectType type; + ModeratedObjectStatus status; + ModerationCategory category; + + String description; + bool verified; + + ModeratedObject( + {this.id, + this.community, + this.contentObject, + this.type, + this.status, + this.category, + this.description, + this.verified}); + + @override + void updateFromJson(Map json) { + if (json.containsKey('description')) { + description = json['description']; + } + + if (json.containsKey('verified')) { + verified = json['verified']; + } + + if (json.containsKey('status')) { + status = factory.parseStatus(json['status']); + } + + if (json.containsKey('type')) { + type = factory.parseType(json['object_type']); + } + + if (json.containsKey('content_object')) { + contentObject = factory.parseContentObject( + contentObjectData: json['content_object'], type: type); + } + } +} + +class ModeratedObjectFactory extends UpdatableModelFactory { + @override + SimpleCache cache = + SimpleCache(storage: UpdatableModelSimpleStorage(size: 120)); + + @override + ModeratedObject makeFromJson(Map json) { + ModeratedObjectType type = parseType(json['object_type']); + ModeratedObjectStatus status = parseStatus(json['status']); + ModerationCategory category = parseModerationCategory(json['category']); + Community community = parseCommunity(json['community']); + + return ModeratedObject( + id: json['id'], + community: community, + category: category, + description: json['description'], + status: status, + type: type, + contentObject: parseContentObject( + contentObjectData: json['content_object'], type: type), + verified: json['verified']); + } + + Community parseCommunity(Map communityData) { + if (communityData == null) return null; + return Community.fromJSON(communityData); + } + + ModerationCategory parseModerationCategory(Map moderationCategoryData) { + if (moderationCategoryData == null) return null; + return ModerationCategory.fromJson(moderationCategoryData); + } + + ModeratedObjectType parseType(String moderatedObjectTypeStr) { + if (moderatedObjectTypeStr == null) return null; + + ModeratedObjectType moderatedObjectType; + if (moderatedObjectTypeStr == ModeratedObject.objectTypeCommunity) { + moderatedObjectType = ModeratedObjectType.community; + } else if (moderatedObjectTypeStr == ModeratedObject.objectTypePost) { + moderatedObjectType = ModeratedObjectType.post; + } else if (moderatedObjectTypeStr == + ModeratedObject.objectTypePostComment) { + moderatedObjectType = ModeratedObjectType.postComment; + } else if (moderatedObjectTypeStr == ModeratedObject.objectTypeUser) { + moderatedObjectType = ModeratedObjectType.user; + } else { + // Don't throw as we might introduce new moderatedObjects on the API which might not be yet in code + print('Unsupported moderatedObject type'); + } + + return moderatedObjectType; + } + + ModeratedObjectStatus parseStatus(String moderatedObjectStatusStr) { + if (moderatedObjectStatusStr == null) return null; + + ModeratedObjectStatus moderatedObjectStatus; + if (moderatedObjectStatusStr == ModeratedObject.statusPending) { + moderatedObjectStatus = ModeratedObjectStatus.pending; + } else if (moderatedObjectStatusStr == ModeratedObject.statusApproved) { + moderatedObjectStatus = ModeratedObjectStatus.approved; + } else if (moderatedObjectStatusStr == ModeratedObject.statusRejected) { + moderatedObjectStatus = ModeratedObjectStatus.rejected; + } else { + // Don't throw as we might introduce new moderatedObjects on the API which might not be yet in code + print('Unsupported moderatedObject status'); + } + + return moderatedObjectStatus; + } + + dynamic parseContentObject( + {@required Map contentObjectData, @required ModeratedObjectType type}) { + if (contentObjectData == null) return null; + + dynamic contentObject; + switch (type) { + case ModeratedObjectType.post: + contentObject = Post.fromJson(contentObjectData); + break; + case ModeratedObjectType.postComment: + contentObject = PostComment.fromJSON(contentObjectData); + break; + case ModeratedObjectType.community: + contentObject = Community.fromJSON(contentObjectData); + break; + case ModeratedObjectType.user: + contentObject = User.fromJson(contentObjectData); + break; + default: + } + return contentObject; + } + + DateTime parseCreated(String created) { + return DateTime.parse(created).toLocal(); + } +} + +enum ModeratedObjectType { post, postComment, user, community } + +enum ModeratedObjectStatus { + approved, + rejected, + pending, +} diff --git a/lib/models/moderation/moderation_category.dart b/lib/models/moderation/moderation_category.dart new file mode 100644 index 000000000..49b2fb032 --- /dev/null +++ b/lib/models/moderation/moderation_category.dart @@ -0,0 +1,51 @@ +class ModerationCategory { + static final severityCritical = 'C'; + static final severityHigh = 'H'; + static final severityMedium = 'M'; + static final severityLow = 'L'; + + static ModerationCategorySeverity parseType(String notificationTypeStr) { + if (notificationTypeStr == null) return null; + + ModerationCategorySeverity notificationType; + if (notificationTypeStr == ModerationCategory.severityCritical) { + notificationType = ModerationCategorySeverity.critical; + } else if (notificationTypeStr == ModerationCategory.severityHigh) { + notificationType = ModerationCategorySeverity.high; + } else if (notificationTypeStr == ModerationCategory.severityMedium) { + notificationType = ModerationCategorySeverity.medium; + } else if (notificationTypeStr == ModerationCategory.severityLow) { + notificationType = ModerationCategorySeverity.low; + } else { + // Don't throw as we might introduce new notifications on the API which might not be yet in code + print('Unsupported notification type'); + } + + return notificationType; + } + + final int id; + final ModerationCategorySeverity severity; + final String name; + final String title; + final String description; + + ModerationCategory( + {this.id, this.severity, this.name, this.title, this.description}); + + factory ModerationCategory.fromJson(Map parsedJson) { + return ModerationCategory( + id: parsedJson['id'], + name: parsedJson['name'], + title: parsedJson['title'], + description: parsedJson['description'], + severity: parseType(parsedJson['type'])); + } +} + +enum ModerationCategorySeverity { + critical, + high, + medium, + low, +} diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index 4f7bed739..eecd8a699 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -21,6 +21,7 @@ class AuthApiService { static const GET_AUTHENTICATED_USER_PATH = 'api/auth/user/'; static const UPDATE_AUTHENTICATED_USER_PATH = 'api/auth/user/'; static const GET_USERS_PATH = 'api/auth/users/'; + static const REPORT_USER_PATH = 'api/auth/users/{userUsername}/report/'; static const GET_LINKED_USERS_PATH = 'api/auth/linked-users/'; static const SEARCH_LINKED_USERS_PATH = 'api/auth/linked-users/search/'; static const GET_BLOCKED_USERS_PATH = 'api/auth/blocked-users/'; @@ -130,13 +131,14 @@ class AuthApiService { body: body, appendAuthorizationToken: true); } - Future createUser({@required String email, - @required String token, - @required String name, - @required bool isOfLegalAge, - @required bool areGuidelinesAccepted, - @required String password, - File avatar}) { + Future createUser( + {@required String email, + @required String token, + @required String name, + @required bool isOfLegalAge, + @required bool areGuidelinesAccepted, + @required String password, + File avatar}) { Map body = { 'email': email, 'token': token, @@ -186,10 +188,11 @@ class AuthApiService { queryParameters: queryParams, appendAuthorizationToken: true); } - Future getLinkedUsers({bool authenticatedRequest = true, - int maxId, - int count, - String withCommunity}) { + Future getLinkedUsers( + {bool authenticatedRequest = true, + int maxId, + int count, + String withCommunity}) { Map queryParams = {}; if (count != null) queryParams['count'] = count; @@ -213,9 +216,11 @@ class AuthApiService { queryParameters: queryParams, appendAuthorizationToken: true); } - Future getBlockedUsers({bool authenticatedRequest = true, + Future getBlockedUsers({ + bool authenticatedRequest = true, int maxId, - int count,}) { + int count, + }) { Map queryParams = {}; if (count != null) queryParams['count'] = count; @@ -351,8 +356,26 @@ class AuthApiService { } Future acceptGuidelines() { - return this._httpService.post( - '$apiURL$ACCEPT_GUIDELINES', appendAuthorizationToken: true); + return this + ._httpService + .post('$apiURL$ACCEPT_GUIDELINES', appendAuthorizationToken: true); + } + + Future reportUserWithUsername( + {@required String userUsername, + @required int moderationCategoryId, + String description}) { + String path = _makeReportUserPath(username: userUsername); + + Map body = { + 'category_id': moderationCategoryId + }; + + if (description != null && description.isNotEmpty) { + body['description'] = description; + } + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); } String _makeBlockUserWithUsernamePath(String username) { @@ -365,6 +388,11 @@ class AuthApiService { .parse(UNBLOCK_USER_PATH, {'userUsername': username}); } + String _makeReportUserPath({@required username}) { + return _stringTemplateService + .parse(REPORT_USER_PATH, {'userUsername': username}); + } + String _makeApiUrl(String string) { return '$apiURL$string'; } diff --git a/lib/services/communities_api.dart b/lib/services/communities_api.dart index 23e8a112c..5e85e72db 100644 --- a/lib/services/communities_api.dart +++ b/lib/services/communities_api.dart @@ -20,6 +20,8 @@ class CommunitiesApiService { static const DELETE_COMMUNITY_PATH = 'api/communities/{communityName}/'; static const UPDATE_COMMUNITY_PATH = 'api/communities/{communityName}/'; static const GET_COMMUNITY_PATH = 'api/communities/{communityName}/'; + static const REPORT_COMMUNITY_PATH = + 'api/communities/{communityName}/report/'; static const JOIN_COMMUNITY_PATH = 'api/communities/{communityName}/members/join/'; static const LEAVE_COMMUNITY_PATH = @@ -144,8 +146,11 @@ class CommunitiesApiService { appendAuthorizationToken: authenticatedRequest); } - Future getClosedPostsForCommunityWithName(String communityName, - {int maxId, int count, bool authenticatedRequest = true}) { + Future getClosedPostsForCommunityWithName( + String communityName, + {int maxId, + int count, + bool authenticatedRequest = true}) { Map queryParams = {}; if (count != null) queryParams['count'] = count; @@ -159,7 +164,6 @@ class CommunitiesApiService { appendAuthorizationToken: authenticatedRequest); } - Future getCommunitiesWithQuery( {bool authenticatedRequest = true, @required String query}) { Map queryParams = {'query': query}; @@ -566,11 +570,31 @@ class CommunitiesApiService { queryParameters: {'offset': offset}); } + Future reportCommunity( + {@required String communityName, + @required int moderationCategoryId, + String description}) { + String path = _makeReportCommunityPath(communityName); + + Map body = {'category_id': moderationCategoryId}; + + if (description != null && description.isNotEmpty) { + body['description'] = description; + } + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + } + String _makeCreateCommunityPost(String communityName) { return _stringTemplateService .parse(CREATE_COMMUNITY_POST_PATH, {'communityName': communityName}); } + String _makeReportCommunityPath(String communityName) { + return _stringTemplateService + .parse(REPORT_COMMUNITY_PATH, {'communityName': communityName}); + } + String _makeClosedCommunityPostsPath(String communityName) { return _stringTemplateService .parse(CLOSED_COMMUNITY_POSTS_PATH, {'communityName': communityName}); diff --git a/lib/services/posts_api.dart b/lib/services/posts_api.dart index f6515cf3a..256fce52b 100644 --- a/lib/services/posts_api.dart +++ b/lib/services/posts_api.dart @@ -18,14 +18,20 @@ class PostsApiService { static const OPEN_POST_PATH = 'api/posts/{postUuid}/open/'; static const CLOSE_POST_PATH = 'api/posts/{postUuid}/close/'; static const COMMENT_POST_PATH = 'api/posts/{postUuid}/comments/'; - static const EDIT_COMMENT_POST_PATH = 'api/posts/{postUuid}/comments/{postCommentId}/'; + static const EDIT_COMMENT_POST_PATH = + 'api/posts/{postUuid}/comments/{postCommentId}/'; static const MUTE_POST_PATH = 'api/posts/{postUuid}/notifications/mute/'; static const UNMUTE_POST_PATH = 'api/posts/{postUuid}/notifications/unmute/'; + static const REPORT_POST_PATH = 'api/posts/{postUuid}/report/'; static const DELETE_POST_COMMENT_PATH = 'api/posts/{postUuid}/comments/{postCommentId}/'; + static const REPORT_POST_COMMENT_PATH = + 'api/posts/{postUuid}/comments/{postCommentId}/report/'; static const GET_POST_COMMENTS_PATH = 'api/posts/{postUuid}/comments/'; - static const DISABLE_POST_COMMENTS_PATH = 'api/posts/{postUuid}/comments/disable/'; - static const ENABLE_POST_COMMENTS_PATH = 'api/posts/{postUuid}/comments/enable/'; + static const DISABLE_POST_COMMENTS_PATH = + 'api/posts/{postUuid}/comments/disable/'; + static const ENABLE_POST_COMMENTS_PATH = + 'api/posts/{postUuid}/comments/enable/'; static const REACT_TO_POST_PATH = 'api/posts/{postUuid}/reactions/'; static const DELETE_POST_REACTION_PATH = 'api/posts/{postUuid}/reactions/{postReactionId}/'; @@ -155,7 +161,9 @@ class PostsApiService { } Future editPostComment( - {@required String postUuid, @required int postCommentId, @required String text}) { + {@required String postUuid, + @required int postCommentId, + @required String text}) { Map body = {'text': text}; String path = _makeEditCommentPostPath(postUuid, postCommentId); @@ -249,6 +257,38 @@ class PostsApiService { return _httpService.get(url, appendAuthorizationToken: true); } + Future reportPostComment( + {@required int postCommentId, + @required String postUuid, + @required int moderationCategoryId, + String description}) { + String path = _makeReportPostCommentPath( + postCommentId: postCommentId, postUuid: postUuid); + + Map body = {'category_id': moderationCategoryId}; + + if (description != null && description.isNotEmpty) { + body['description'] = description; + } + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + } + + Future reportPost( + {@required String postUuid, + @required int moderationCategoryId, + String description}) { + String path = _makeReportPostPath(postUuid: postUuid); + + Map body = {'category_id': moderationCategoryId}; + + if (description != null && description.isNotEmpty) { + body['description'] = description; + } + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + } + String _makePostPath(String postUuid) { return _stringTemplateService.parse(POST_PATH, {'postUuid': postUuid}); } @@ -273,8 +313,7 @@ class PostsApiService { } String _makeOpenPostPath(String postUuid) { - return _stringTemplateService - .parse(OPEN_POST_PATH, {'postUuid': postUuid}); + return _stringTemplateService.parse(OPEN_POST_PATH, {'postUuid': postUuid}); } String _makeClosePostPath(String postUuid) { @@ -288,8 +327,8 @@ class PostsApiService { } String _makeEditCommentPostPath(String postUuid, int postCommentId) { - return _stringTemplateService - .parse(EDIT_COMMENT_POST_PATH, {'postUuid': postUuid, 'postCommentId': postCommentId}); + return _stringTemplateService.parse(EDIT_COMMENT_POST_PATH, + {'postUuid': postUuid, 'postCommentId': postCommentId}); } String _makeGetPostCommentsPath(String postUuid) { @@ -324,6 +363,17 @@ class PostsApiService { .parse(GET_POST_REACTIONS_EMOJI_COUNT_PATH, {'postUuid': postUuid}); } + String _makeReportPostCommentPath( + {@required int postCommentId, @required String postUuid}) { + return _stringTemplateService.parse(REPORT_POST_COMMENT_PATH, + {'postCommentId': postCommentId, 'postUuid': postUuid}); + } + + String _makeReportPostPath({@required postUuid}) { + return _stringTemplateService + .parse(REPORT_POST_PATH, {'postUuid': postUuid}); + } + String _makeApiUrl(String string) { return '$apiURL$string'; } diff --git a/lib/services/user.dart b/lib/services/user.dart index 1f87a83ea..8efc04876 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -17,6 +17,7 @@ import 'package:Openbook/models/emoji.dart'; import 'package:Openbook/models/emoji_group_list.dart'; import 'package:Openbook/models/follow.dart'; import 'package:Openbook/models/follows_list.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/notifications/notification.dart'; import 'package:Openbook/models/notifications/notifications_list.dart'; import 'package:Openbook/models/post.dart'; @@ -403,14 +404,14 @@ class UserService { Future closePost(Post post) async { HttpieResponse response = - await _postsApiService.closePostWithUuid(post.uuid); + await _postsApiService.closePostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future openPost(Post post) async { HttpieResponse response = - await _postsApiService.openPostWithUuid(post.uuid); + await _postsApiService.openPostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } @@ -857,10 +858,9 @@ class UserService { Future getClosedPostsForCommunity(Community community, {int maxId, int count}) async { - HttpieResponse response = - await _communitiesApiService.getClosedPostsForCommunityWithName(community.name, - count: count, maxId: maxId - ); + HttpieResponse response = await _communitiesApiService + .getClosedPostsForCommunityWithName(community.name, + count: count, maxId: maxId); _checkResponseIsOk(response); return PostsList.fromJson(json.decode(response.body)); } @@ -1411,6 +1411,47 @@ class UserService { return UserNotificationsSettings.fromJSON(json.decode(response.body)); } + Future reportUser( + {@required User user, + String description, + ModerationCategory moderationCategory}) async { + HttpieResponse response = await _authApiService.reportUserWithUsername( + userUsername: user.username, + moderationCategoryId: moderationCategory.id); + _checkResponseIsCreated(response); + } + + Future reportCommunity( + {@required Community community, + String description, + ModerationCategory moderationCategory}) async { + HttpieResponse response = await _communitiesApiService.reportCommunity( + communityName: community.name, + moderationCategoryId: moderationCategory.id); + _checkResponseIsCreated(response); + } + + Future reportPost( + {@required Post post, + String description, + ModerationCategory moderationCategory}) async { + HttpieResponse response = await _postsApiService.reportPost( + postUuid: post.uuid, moderationCategoryId: moderationCategory.id); + _checkResponseIsCreated(response); + } + + Future reportPostComment( + {@required PostComment postComment, + Post post, + String description, + ModerationCategory moderationCategory}) async { + HttpieResponse response = await _postsApiService.reportPostComment( + postCommentId: postComment.id, + postUuid: post.uuid, + moderationCategoryId: moderationCategory.id); + _checkResponseIsCreated(response); + } + Future _getDeviceName() async { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); From b1b91eb76fe209495b598a63496ced203651bfb5 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Fri, 24 May 2019 18:57:46 +0200 Subject: [PATCH 17/65] :sparkles: add moderated object methods --- lib/models/community.dart | 2 + lib/models/moderation/moderated_object.dart | 36 +++++ .../moderation/moderated_object_list.dart | 18 +++ lib/models/user.dart | 13 +- .../home/bottom_sheets/post_actions.dart | 2 +- lib/provider.dart | 5 + lib/services/communities_api.dart | 32 ++++ lib/services/moderation_api.dart | 138 ++++++++++++++++++ lib/services/user.dart | 94 ++++++++++++ lib/widgets/icon.dart | 2 +- .../tiles/actions/report_user_tile.dart | 107 ++++++++++++++ 11 files changed, 444 insertions(+), 5 deletions(-) create mode 100644 lib/models/moderation/moderated_object_list.dart create mode 100644 lib/services/moderation_api.dart create mode 100644 lib/widgets/tiles/actions/report_user_tile.dart diff --git a/lib/models/community.dart b/lib/models/community.dart index 010497d8f..60866e0b7 100644 --- a/lib/models/community.dart +++ b/lib/models/community.dart @@ -63,6 +63,8 @@ class Community extends UpdatableModel { bool isFavorite; + bool isReported; + bool invitesEnabled; CategoriesList categories; diff --git a/lib/models/moderation/moderated_object.dart b/lib/models/moderation/moderated_object.dart index 20f4ad30a..b455bbca8 100644 --- a/lib/models/moderation/moderated_object.dart +++ b/lib/models/moderation/moderated_object.dart @@ -10,6 +10,10 @@ import 'package:meta/meta.dart'; class ModeratedObject extends UpdatableModel { static final factory = ModeratedObjectFactory(); + factory ModeratedObject.fromJSON(Map json) { + return factory.fromJson(json); + } + static String objectTypePost = 'P'; static String objectTypePostComment = 'PC'; static String objectTypeCommunity = 'C'; @@ -137,6 +141,38 @@ class ModeratedObjectFactory extends UpdatableModelFactory { return moderatedObjectStatus; } + String convertStatusToString(ModeratedObjectStatus moderatedObjectStatus) { + if (moderatedObjectStatus == null) return null; + + switch (moderatedObjectStatus) { + case ModeratedObjectStatus.approved: + return ModeratedObject.statusApproved; + case ModeratedObjectStatus.rejected: + return ModeratedObject.statusRejected; + case ModeratedObjectStatus.pending: + return ModeratedObject.statusPending; + default: + return ''; + } + } + + String convertTypeToString(ModeratedObjectType moderatedObjectType) { + if (moderatedObjectType == null) return null; + + switch (moderatedObjectType) { + case ModeratedObjectType.community: + return ModeratedObject.objectTypeCommunity; + case ModeratedObjectType.user: + return ModeratedObject.objectTypeUser; + case ModeratedObjectType.post: + return ModeratedObject.objectTypePost; + case ModeratedObjectType.postComment: + return ModeratedObject.objectTypePostComment; + default: + return ''; + } + } + dynamic parseContentObject( {@required Map contentObjectData, @required ModeratedObjectType type}) { if (contentObjectData == null) return null; diff --git a/lib/models/moderation/moderated_object_list.dart b/lib/models/moderation/moderated_object_list.dart new file mode 100644 index 000000000..6f4203000 --- /dev/null +++ b/lib/models/moderation/moderated_object_list.dart @@ -0,0 +1,18 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; + +class ModeratedObjectsList { + final List moderatedObjects; + + ModeratedObjectsList({ + this.moderatedObjects, + }); + + factory ModeratedObjectsList.fromJson(List parsedJson) { + List moderatedObjects = + parsedJson.map((moderatedObjectJson) => ModeratedObject.fromJSON(moderatedObjectJson)).toList(); + + return new ModeratedObjectsList( + moderatedObjects: moderatedObjects, + ); + } +} diff --git a/lib/models/user.dart b/lib/models/user.dart index 5fefffed1..b4468d0d8 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -30,6 +30,7 @@ class User extends UpdatableModel { bool areGuidelinesAccepted; bool isFollowing; bool isConnected; + bool isReported; bool isBlocked; bool isFullyConnected; bool isPendingConnectionConfirmation; @@ -86,6 +87,7 @@ class User extends UpdatableModel { this.isFollowing, this.isBlocked, this.isConnected, + this.isReported, this.isFullyConnected, this.isMemberOfCommunities, this.connectedCircles, @@ -126,6 +128,7 @@ class User extends UpdatableModel { if (json.containsKey('is_following')) isFollowing = json['is_following']; if (json.containsKey('is_connected')) isConnected = json['is_connected']; if (json.containsKey('is_blocked')) isBlocked = json['is_blocked']; + if (json.containsKey('is_reported')) isBlocked = json['is_reported']; if (json.containsKey('connections_circle_id')) connectionsCircleId = json['connections_circle_id']; if (json.containsKey('is_fully_connected')) @@ -318,7 +321,8 @@ class User extends UpdatableModel { if (post.hasCommunity()) { Community postCommunity = post.community; - if (postCommunity.isAdministrator(loggedInUser) || postCommunity.isModerator(loggedInUser)) { + if (postCommunity.isAdministrator(loggedInUser) || + postCommunity.isModerator(loggedInUser)) { _canCloseOrOpenPost = true; } } @@ -329,7 +333,8 @@ class User extends UpdatableModel { User loggedInUser = this; bool _canCloseOrOpenPost = false; - if (community.isAdministrator(loggedInUser) || community.isModerator(loggedInUser)) { + if (community.isAdministrator(loggedInUser) || + community.isModerator(loggedInUser)) { _canCloseOrOpenPost = true; } @@ -340,7 +345,8 @@ class User extends UpdatableModel { User loggedInUser = this; bool _canBanOrUnban = false; - if (community.isAdministrator(loggedInUser) || community.isModerator(loggedInUser)) { + if (community.isAdministrator(loggedInUser) || + community.isModerator(loggedInUser)) { _canBanOrUnban = true; } @@ -477,6 +483,7 @@ class UserFactory extends UpdatableModelFactory { isFollowing: json['is_following'], isConnected: json['is_connected'], isBlocked: json['is_blocked'], + isReported: json['is_reported'], isFullyConnected: json['is_fully_connected'], isMemberOfCommunities: json['is_member_of_communities'], profile: parseUserProfile(json['profile']), diff --git a/lib/pages/home/bottom_sheets/post_actions.dart b/lib/pages/home/bottom_sheets/post_actions.dart index a1eafc55a..639d32c76 100644 --- a/lib/pages/home/bottom_sheets/post_actions.dart +++ b/lib/pages/home/bottom_sheets/post_actions.dart @@ -98,7 +98,7 @@ class OBPostActionsBottomSheetState extends State { )); } else { postActions.add(ListTile( - leading: const OBIcon(OBIcons.reportPost), + leading: const OBIcon(OBIcons.report), title: const OBText( 'Report post', ), diff --git a/lib/provider.dart b/lib/provider.dart index cff9b6374..78301ddd4 100644 --- a/lib/provider.dart +++ b/lib/provider.dart @@ -10,6 +10,7 @@ import 'package:Openbook/services/devices_api.dart'; import 'package:Openbook/services/dialog.dart'; import 'package:Openbook/services/documents.dart'; import 'package:Openbook/services/intercom.dart'; +import 'package:Openbook/services/moderation_api.dart'; import 'package:Openbook/services/notifications_api.dart'; import 'package:Openbook/services/push_notifications/push_notifications.dart'; import 'package:Openbook/services/universal_links/universal_links.dart'; @@ -67,6 +68,7 @@ class OpenbookProviderState extends State { PostsApiService postsApiService = PostsApiService(); StorageService storageService = StorageService(); UserService userService = UserService(); + ModerationApiService moderationApiService = ModerationApiService(); ToastService toastService = ToastService(); StringTemplateService stringTemplateService = StringTemplateService(); EmojisApiService emojisApiService = EmojisApiService(); @@ -158,6 +160,8 @@ class OpenbookProviderState extends State { dialogService.setThemeValueParserService(themeValueParserService); imagePickerService.setValidationService(validationService); documentsService.setHttpService(httpService); + moderationApiService.setStringTemplateService(stringTemplateService); + moderationApiService.setHttpieService(httpService); } void initAsyncState() async { @@ -170,6 +174,7 @@ class OpenbookProviderState extends State { emojisApiService.setApiURL(environment.apiUrl); userInvitesApiService.setApiURL(environment.apiUrl); followsApiService.setApiURL(environment.apiUrl); + moderationApiService.setApiURL(environment.apiUrl); connectionsApiService.setApiURL(environment.apiUrl); connectionsCirclesApiService.setApiURL(environment.apiUrl); followsListsApiService.setApiURL(environment.apiUrl); diff --git a/lib/services/communities_api.dart b/lib/services/communities_api.dart index 5e85e72db..d892cff69 100644 --- a/lib/services/communities_api.dart +++ b/lib/services/communities_api.dart @@ -77,6 +77,8 @@ class CommunitiesApiService { 'api/communities/{communityName}/moderators/{username}/'; static const CREATE_COMMUNITY_POSTS_PATH = 'api/communities/{communityName}/posts/'; + static const GET_COMMUNITY_MODERATED_OBJECTS_PATH = + 'api/communities/{communityName}/moderated-objects/'; void setHttpieService(HttpieService httpService) { _httpService = httpService; @@ -585,6 +587,36 @@ class CommunitiesApiService { return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); } + Future getModeratedObjects({ + @required String communityName, + int count, + int maxId, + String type, + bool verified, + List statuses, + List types, + }) { + Map queryParams = {}; + if (count != null) queryParams['count'] = count; + + if (maxId != null) queryParams['max_id'] = maxId; + + if (statuses != null) queryParams['statuses'] = statuses; + if (types != null) queryParams['types'] = types; + + if (verified != null) queryParams['verified'] = verified; + + String path = _makeGetCommunityModeratedObjectsPath(communityName); + + return _httpService.get(_makeApiUrl(path), + queryParameters: queryParams, appendAuthorizationToken: true); + } + + String _makeGetCommunityModeratedObjectsPath(String communityName) { + return _stringTemplateService.parse( + GET_COMMUNITY_MODERATED_OBJECTS_PATH, {'communityName': communityName}); + } + String _makeCreateCommunityPost(String communityName) { return _stringTemplateService .parse(CREATE_COMMUNITY_POST_PATH, {'communityName': communityName}); diff --git a/lib/services/moderation_api.dart b/lib/services/moderation_api.dart new file mode 100644 index 000000000..2bcc140c9 --- /dev/null +++ b/lib/services/moderation_api.dart @@ -0,0 +1,138 @@ +import 'package:Openbook/services/httpie.dart'; +import 'package:Openbook/services/string_template.dart'; + +class ModerationApiService { + HttpieService _httpService; + StringTemplateService _stringTemplateService; + + String apiURL; + + static const GET_GLOBAL_MODERATED_OBJECTS_PATH = + 'api/moderation/moderated_objects/'; + static const GET_MODERATION_CATEGORIES_PATH = 'api/moderation/categories/'; + static const MODERATED_OBJECT_PATH = + 'api/moderation/moderated_objects/{moderatedObjectId}/'; + static const APPROVE_MODERATED_OBJECT_PATH = + 'api/moderation/moderated_objects/{moderatedObjectId}/approve/'; + static const REJECT_MODERATED_OBJECT_PATH = + 'api/moderation/moderated_objects/{moderatedObjectId}/reject/'; + static const VERIFY_MODERATED_OBJECT_PATH = + 'api/moderation/moderated_objects/{moderatedObjectId}/verify/'; + static const UNVERIFY_MODERATED_OBJECT_PATH = + 'api/moderation/moderated_objects/{moderatedObjectId}/unverify/'; + + void setHttpieService(HttpieService httpService) { + _httpService = httpService; + } + + void setStringTemplateService(StringTemplateService stringTemplateService) { + _stringTemplateService = stringTemplateService; + } + + void setApiURL(String newApiURL) { + apiURL = newApiURL; + } + + Future getGlobalModeratedObjects({ + int count, + int maxId, + String type, + bool verified, + List statuses, + List types, + }) { + Map queryParams = {}; + if (count != null) queryParams['count'] = count; + + if (maxId != null) queryParams['max_id'] = maxId; + + if (statuses != null) queryParams['statuses'] = statuses; + + if (types != null) queryParams['types'] = types; + + if (verified != null) queryParams['verified'] = verified; + + String path = GET_GLOBAL_MODERATED_OBJECTS_PATH; + + return _httpService.get(_makeApiUrl(path), + queryParameters: queryParams, appendAuthorizationToken: true); + } + + Future getModerationCategories( + {int count, + int maxId, + String type, + bool verified, + List statuses}) { + String path = GET_MODERATION_CATEGORIES_PATH; + + return _httpService.get(_makeApiUrl(path), appendAuthorizationToken: true); + } + + Future verifyModeratedObjectWithId(int moderatedObjectId) { + String path = _makeVerifyModeratedObjectsPath(moderatedObjectId); + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + } + + Future unverifyModeratedObjectWithId(int moderatedObjectId) { + String path = _makeUnverifyModeratedObjectsPath(moderatedObjectId); + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + } + + Future approveModeratedObjectWithId(int moderatedObjectId) { + String path = _makeApproveModeratedObjectsPath(moderatedObjectId); + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + } + + Future rejectModeratedObjectWithId(int moderatedObjectId) { + String path = _makeRejectModeratedObjectsPath(moderatedObjectId); + + return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + } + + Future updateModeratedObjectWithId(int moderatedObjectId, + {String description, int categoryId}) { + Map body = {}; + + if (description != null) body['description'] = description; + + if (categoryId != null) body['category_id'] = categoryId; + + String path = _makeModeratedObjectsPath(moderatedObjectId); + + return _httpService.post(_makeApiUrl(path), + body: body, appendAuthorizationToken: true); + } + + String _makeModeratedObjectsPath(int moderatedObjectId) { + return _stringTemplateService + .parse(MODERATED_OBJECT_PATH, {'moderatedObjectId': moderatedObjectId}); + } + + String _makeVerifyModeratedObjectsPath(int moderatedObjectId) { + return _stringTemplateService.parse( + VERIFY_MODERATED_OBJECT_PATH, {'moderatedObjectId': moderatedObjectId}); + } + + String _makeUnverifyModeratedObjectsPath(int moderatedObjectId) { + return _stringTemplateService.parse(UNVERIFY_MODERATED_OBJECT_PATH, + {'moderatedObjectId': moderatedObjectId}); + } + + String _makeApproveModeratedObjectsPath(int moderatedObjectId) { + return _stringTemplateService.parse(APPROVE_MODERATED_OBJECT_PATH, + {'moderatedObjectId': moderatedObjectId}); + } + + String _makeRejectModeratedObjectsPath(int moderatedObjectId) { + return _stringTemplateService.parse( + REJECT_MODERATED_OBJECT_PATH, {'moderatedObjectId': moderatedObjectId}); + } + + String _makeApiUrl(String string) { + return '$apiURL$string'; + } +} diff --git a/lib/services/user.dart b/lib/services/user.dart index 8efc04876..f8a3f0486 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -17,6 +17,8 @@ import 'package:Openbook/models/emoji.dart'; import 'package:Openbook/models/emoji_group_list.dart'; import 'package:Openbook/models/follow.dart'; import 'package:Openbook/models/follows_list.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderated_object_list.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/notifications/notification.dart'; import 'package:Openbook/models/notifications/notifications_list.dart'; @@ -43,6 +45,7 @@ import 'package:Openbook/services/emojis_api.dart'; import 'package:Openbook/services/follows_api.dart'; import 'package:Openbook/services/httpie.dart'; import 'package:Openbook/services/follows_lists_api.dart'; +import 'package:Openbook/services/moderation_api.dart'; import 'package:Openbook/services/notifications_api.dart'; import 'package:Openbook/services/posts_api.dart'; import 'package:Openbook/services/storage.dart'; @@ -64,6 +67,7 @@ class UserService { AuthApiService _authApiService; HttpieService _httpieService; PostsApiService _postsApiService; + ModerationApiService _moderationApiService; CommunitiesApiService _communitiesApiService; CategoriesApiService _categoriesApiService; EmojisApiService _emojisApiService; @@ -91,6 +95,10 @@ class UserService { _authApiService = authApiService; } + void setModerationApiService(ModerationApiService moderationApiService) { + _moderationApiService = moderationApiService; + } + void setPostsApiService(PostsApiService postsApiService) { _postsApiService = postsApiService; } @@ -1452,6 +1460,92 @@ class UserService { _checkResponseIsCreated(response); } + Future getGlobalModeratedObjects( + {List statuses, + List types, + int count, + int maxId, + bool verified}) async { + HttpieResponse response = + await _moderationApiService + .getGlobalModeratedObjects( + maxId: maxId, + verified: verified, + types: types != null + ? types.map((status) => + ModeratedObject.factory.convertTypeToString(status)) + : null, + statuses: + statuses != null + ? statuses.map((status) => ModeratedObject.factory + .convertStatusToString(status)) + : null, + count: count); + + _checkResponseIsOk(response); + + return ModeratedObjectsList.fromJson(json.decode(response.body)); + } + + Future getModeratedObjectsForCommunity( + {@required Community community, + List statuses, + List types, + int count, + int maxId, + bool verified}) async { + HttpieResponse response = await _communitiesApiService.getModeratedObjects( + communityName: community.name, + maxId: maxId, + verified: verified, + types: types != null + ? types.map( + (status) => ModeratedObject.factory.convertTypeToString(status)) + : null, + statuses: statuses != null + ? statuses.map((status) => + ModeratedObject.factory.convertStatusToString(status)) + : null, + count: count); + + _checkResponseIsOk(response); + + return ModeratedObjectsList.fromJson(json.decode(response.body)); + } + + Future updateModeratedObject(ModeratedObject moderatedObject, + {String description, ModerationCategory category}) async { + HttpieResponse response = await _moderationApiService + .updateModeratedObjectWithId(moderatedObject.id, + description: description, categoryId: category.id); + _checkResponseIsCreated(response); + return ModeratedObject.fromJSON(json.decode(response.body)); + } + + Future verifyModeratedObject(ModeratedObject moderatedObject) async { + HttpieResponse response = await _moderationApiService + .verifyModeratedObjectWithId(moderatedObject.id); + _checkResponseIsCreated(response); + } + + Future unverifyModeratedObject(ModeratedObject moderatedObject) async { + HttpieResponse response = await _moderationApiService + .unverifyModeratedObjectWithId(moderatedObject.id); + _checkResponseIsCreated(response); + } + + Future approveModeratedObject(ModeratedObject moderatedObject) async { + HttpieResponse response = await _moderationApiService + .approveModeratedObjectWithId(moderatedObject.id); + _checkResponseIsCreated(response); + } + + Future rejectModeratedObject(ModeratedObject moderatedObject) async { + HttpieResponse response = await _moderationApiService + .rejectModeratedObjectWithId(moderatedObject.id); + _checkResponseIsCreated(response); + } + Future _getDeviceName() async { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index e673a4621..ba6d47f4e 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -167,7 +167,7 @@ class OBIcons { static const disconnect = OBIconData(nativeIcon: Icons.remove_circle_outline); static const deletePost = OBIconData(nativeIcon: Icons.delete); static const clear = OBIconData(nativeIcon: Icons.delete); - static const reportPost = OBIconData(nativeIcon: Icons.report); + static const report = OBIconData(nativeIcon: Icons.report); static const filter = OBIconData(nativeIcon: Icons.tune); static const gallery = OBIconData(nativeIcon: Icons.apps); static const camera = OBIconData(nativeIcon: Icons.camera_alt); diff --git a/lib/widgets/tiles/actions/report_user_tile.dart b/lib/widgets/tiles/actions/report_user_tile.dart new file mode 100644 index 000000000..1cb2122d3 --- /dev/null +++ b/lib/widgets/tiles/actions/report_user_tile.dart @@ -0,0 +1,107 @@ +import 'package:Openbook/models/user.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/httpie.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tiles/loading_tile.dart'; +import 'package:flutter/material.dart'; + +class OBReportUserTile extends StatefulWidget { + final User user; + final VoidCallback onReportedUser; + final VoidCallback onUnreportedUser; + + const OBReportUserTile({ + Key key, + @required this.user, + this.onReportedUser, + this.onUnreportedUser, + }) : super(key: key); + + @override + OBReportUserTileState createState() { + return OBReportUserTileState(); + } +} + +class OBReportUserTileState extends State { + UserService _userService; + ToastService _toastService; + bool _requestInProgress; + + @override + void initState() { + super.initState(); + _requestInProgress = false; + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + + return StreamBuilder( + stream: widget.user.updateSubject, + initialData: widget.user, + builder: (BuildContext context, AsyncSnapshot snapshot) { + var user = snapshot.data; + + bool isReported = user.isReported ?? false; + + return OBLoadingTile( + isLoading: _requestInProgress || isReported, + leading: OBIcon(OBIcons.report), + title: OBText( + isReported ? 'Report account' : 'You have reported this account'), + onTap: isReported ? () {} : _reportUser, + ); + }, + ); + } + + void _reportUser() async { + _setRequestInProgress(true); + try { + await _userService.reportUser(widget.user); + if (widget.onReportedUser != null) widget.onReportedUser(); + } catch (e) { + _onError(e); + } finally { + _setRequestInProgress(false); + } + } + + void _unreportUser() async { + _setRequestInProgress(true); + try { + await _userService.unreportUser(widget.user); + if (widget.onUnreportedUser != null) widget.onUnreportedUser(); + } catch (e) { + _onError(e); + } finally { + _setRequestInProgress(false); + } + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } + + void _setRequestInProgress(bool requestInProgress) { + setState(() { + _requestInProgress = requestInProgress; + }); + } +} From edc23cefe137c8198919ab9ed11c89ecf8747ceb Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Fri, 24 May 2019 22:31:09 +0200 Subject: [PATCH 18/65] :construction: add missing methods to API and skeleton of report object widget --- lib/libs/type_to_str.dart | 17 ++ .../moderation/moderation_category_list.dart | 20 +++ .../pages/confirm_report_object.dart | 169 ++++++++++++++++++ .../modals/report_object/report_object.dart | 156 ++++++++++++++++ .../profile_action_more.dart | 18 +- lib/provider.dart | 1 + lib/services/modal_service.dart | 16 ++ lib/services/moderation_api.dart | 7 +- lib/services/navigation_service.dart | 16 ++ lib/services/user.dart | 18 +- .../tiles/actions/report_object_tile.dart | 68 +++++++ .../tiles/actions/report_user_tile.dart | 107 ----------- 12 files changed, 492 insertions(+), 121 deletions(-) create mode 100644 lib/libs/type_to_str.dart create mode 100644 lib/models/moderation/moderation_category_list.dart create mode 100644 lib/pages/home/modals/report_object/pages/confirm_report_object.dart create mode 100644 lib/pages/home/modals/report_object/report_object.dart create mode 100644 lib/widgets/tiles/actions/report_object_tile.dart delete mode 100644 lib/widgets/tiles/actions/report_user_tile.dart diff --git a/lib/libs/type_to_str.dart b/lib/libs/type_to_str.dart new file mode 100644 index 000000000..c90d61cb1 --- /dev/null +++ b/lib/libs/type_to_str.dart @@ -0,0 +1,17 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/models/user.dart'; + +String modelTypeToString(dynamic modelInstance) { + if (modelInstance is Post) { + return 'post'; + } else if (modelInstance is PostComment) { + return 'post comment'; + } else if (modelInstance is Community) { + return 'community'; + } else if (modelInstance is User) { + return 'user'; + } + return 'item'; +} diff --git a/lib/models/moderation/moderation_category_list.dart b/lib/models/moderation/moderation_category_list.dart new file mode 100644 index 000000000..df40140c7 --- /dev/null +++ b/lib/models/moderation/moderation_category_list.dart @@ -0,0 +1,20 @@ +import 'package:Openbook/models/moderation/moderation_category.dart'; + +class ModerationCategoriesList { + final List moderationCategories; + + ModerationCategoriesList({ + this.moderationCategories, + }); + + factory ModerationCategoriesList.fromJson(List parsedJson) { + List moderationCategories = parsedJson + .map((moderationCategoryJson) => + ModerationCategory.fromJson(moderationCategoryJson)) + .toList(); + + return new ModerationCategoriesList( + moderationCategories: moderationCategories, + ); + } +} diff --git a/lib/pages/home/modals/report_object/pages/confirm_report_object.dart b/lib/pages/home/modals/report_object/pages/confirm_report_object.dart new file mode 100644 index 000000000..2a90bd947 --- /dev/null +++ b/lib/pages/home/modals/report_object/pages/confirm_report_object.dart @@ -0,0 +1,169 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/models/user.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/cupertino.dart'; + +class OBConfirmReportObject extends StatefulWidget { + final dynamic object; + final ModerationCategory category; + + const OBConfirmReportObject( + {Key key, @required this.object, @required this.category}) + : super(key: key); + + @override + OBConfirmReportObjectState createState() { + return OBConfirmReportObjectState(); + } +} + +class OBConfirmReportObjectState extends State { + bool _confirmationInProgress; + UserService _userService; + ToastService _toastService; + bool _needsBootstrap; + + String description; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _confirmationInProgress = false; + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + _needsBootstrap = false; + } + + return CupertinoPageScaffold( + navigationBar: OBThemedNavigationBar(title: 'Confirmation'), + child: OBPrimaryColorContainer( + child: Column( + children: [ + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 40, vertical: 40), + child: Column( + //crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBIcon( + OBIcons.report, + themeColor: OBIconThemeColor.primaryAccent, + size: OBIconSize.extraLarge, + ), + const SizedBox( + height: 20, + ), + OBText( + 'You are about to report this item.', + textAlign: TextAlign.center, + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 40, + ), + const OBText('The report is anonymous.') + ], + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: Row( + children: [ + Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.highlight, + child: Text('No'), + onPressed: _onCancel, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + child: OBButton( + size: OBButtonSize.large, + child: Text('Yes'), + onPressed: _onConfirm, + isLoading: _confirmationInProgress, + ), + ) + ], + ), + ) + ], + ))); + } + + void _onConfirm() async { + _setConfirmationInProgress(true); + try { + Future reportFuture; + if (widget.object is Post) { + reportFuture = _userService.reportPost( + post: widget.object, moderationCategory: widget.category); + } else if (widget.object is PostComment) { + reportFuture = _userService.reportPostComment( + postComment: widget.object, moderationCategory: widget.category); + } else if (widget.object is Community) { + reportFuture = _userService.reportCommunity( + community: widget.object, moderationCategory: widget.category); + } else if (widget.object is User) { + reportFuture = _userService.reportUser( + user: widget.object, moderationCategory: widget.category); + } else { + throw 'Object type not supported'; + } + + Navigator.of(context).pop(true); + } catch (error) { + _onError(error); + } finally { + _setConfirmationInProgress(false); + } + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } + + void _onCancel() { + Navigator.of(context).pop(false); + } + + void _setConfirmationInProgress(confirmationInProgress) { + setState(() { + _confirmationInProgress = confirmationInProgress; + }); + } +} diff --git a/lib/pages/home/modals/report_object/report_object.dart b/lib/pages/home/modals/report_object/report_object.dart new file mode 100644 index 000000000..d04ee76b7 --- /dev/null +++ b/lib/pages/home/modals/report_object/report_object.dart @@ -0,0 +1,156 @@ +import 'package:Openbook/libs/type_to_str.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/moderation/moderation_category_list.dart'; +import 'package:Openbook/models/theme.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/services/theme.dart'; +import 'package:Openbook/services/theme_value_parser.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/fields/checkbox_field.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBReportObjectModal extends StatefulWidget { + final dynamic object; + final OnObjectReported onObjectReported; + + const OBReportObjectModal({ + Key key, + this.object, + this.onObjectReported, + }) : super(key: key); + + @override + OBReportObjectModalState createState() { + return OBReportObjectModalState(); + } +} + +class OBReportObjectModalState extends State { + ThemeService _themeService; + NavigationService _navigationService; + UserService _userService; + ThemeValueParserService _themeValueParserService; + List _moderationCategories = []; + ModerationCategory _pickedModerationCategory; + bool _needsBootstrap; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + var openbookProvider = OpenbookProvider.of(context); + _themeService = openbookProvider.themeService; + _userService = openbookProvider.userService; + _navigationService = openbookProvider.navigationService; + _themeValueParserService = openbookProvider.themeValueParserService; + _bootstrap(); + _needsBootstrap = false; + } + + OBTheme theme = _themeService.getActiveTheme(); + + Color textColor = _themeValueParserService + .parseGradient(theme.primaryAccentColor) + .colors[1]; + + return OBCupertinoPageScaffold( + navigationBar: _buildNavigationBar(), + child: OBPrimaryColorContainer( + child: Column( + children: [ + ListTile( + title: Text( + 'Please select a reason', + style: TextStyle(color: textColor), + )), + _moderationCategories.isEmpty + ? _buildProgressIndicator() + : _buildModerationCategories(), + ], + ), + )); + } + + Widget _buildProgressIndicator() { + return Expanded( + child: Center( + child: OBProgressIndicator(), + ), + ); + } + + Widget _buildModerationCategories() { + return Expanded( + child: ListView.separated( + padding: EdgeInsets.all(0.0), + itemBuilder: _buildModerationCategoryTile, + separatorBuilder: (context, index) { + return const Divider(); + }, + itemCount: _moderationCategories.length, + ), + ); + } + + Widget _buildModerationCategoryTile(context, index) { + ModerationCategory category = _moderationCategories[index]; + + return OBCheckboxField( + value: _pickedModerationCategory == category, + onTap: () async { + var result = await _navigationService.navigateToConfirmReportObject( + object: widget.object, category: category, context: context); + if (result != null) { + widget.onObjectReported(widget.object); + Navigator.pop(context); + } + }, + title: category.title, + subtitle: category.description, + ); + } + + Widget _buildNavigationBar() { + return OBThemedNavigationBar( + title: 'Report ' + modelTypeToString(widget.object), + trailing: GestureDetector( + onTap: _onWantsToGoNext, + child: const OBText('Next'), + ), + ); + } + + void _onWantsToGoNext() { + _navigationService.navigateToConfirmReportObject( + context: context, + object: widget.object, + category: _pickedModerationCategory); + } + + void _bootstrap() async { + var moderationCategories = await _userService.getModerationCategories(); + _setModerationCategories(moderationCategories); + } + + _setModerationCategories(ModerationCategoriesList moderationCategoriesList) { + setState(() { + print(moderationCategoriesList.moderationCategories); + _moderationCategories = moderationCategoriesList.moderationCategories; + }); + } + +} + +typedef OnObjectReported(dynamic object); diff --git a/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart b/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart index 5ffb95d28..da54367b5 100644 --- a/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart +++ b/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart @@ -10,6 +10,7 @@ import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tiles/actions/block_user_tile.dart'; +import 'package:Openbook/widgets/tiles/actions/report_object_tile.dart'; import 'package:flutter/material.dart'; class OBProfileActionMore extends StatelessWidget { @@ -87,19 +88,28 @@ class OBProfileActionMore extends StatelessWidget { if (loggedInUser.canBlockOrUnblockUser(user)) { moreTiles.add(OBBlockUserTile( user: user, - onBlockedUser: (){ + onBlockedUser: () { // Bottom sheet Navigator.pop(context); - openbookProvider.toastService.success(message: 'User blocked', context:context); + openbookProvider.toastService + .success(message: 'User blocked', context: context); }, - onUnblockedUser: (){ + onUnblockedUser: () { // Bottom sheet Navigator.pop(context); - openbookProvider.toastService.success(message: 'User unblocked', context:context); + openbookProvider.toastService + .success(message: 'User unblocked', context: context); }, )); } + moreTiles.add(OBReportUserTile( + user: user, + onWantsToReportUser: () { + Navigator.of(context).pop(); + }, + )); + showModalBottomSheet( context: context, builder: (BuildContext context) { diff --git a/lib/provider.dart b/lib/provider.dart index 78301ddd4..dc5faab5d 100644 --- a/lib/provider.dart +++ b/lib/provider.dart @@ -140,6 +140,7 @@ class OpenbookProviderState extends State { userService.setNotificationsApiService(notificationsApiService); userService.setDevicesApiService(devicesApiService); userService.setCreateAccountBlocService(createAccountBloc); + userService.setModerationApiService(moderationApiService); emojisApiService.setHttpService(httpService); categoriesApiService.setHttpService(httpService); postsApiService.setHttpieService(httpService); diff --git a/lib/services/modal_service.dart b/lib/services/modal_service.dart index 81c06f85b..af3e0d4a2 100644 --- a/lib/services/modal_service.dart +++ b/lib/services/modal_service.dart @@ -12,6 +12,8 @@ import 'package:Openbook/pages/home/modals/accept_guidelines/accept_guidelines.d import 'package:Openbook/pages/home/modals/edit_post/edit_post.dart'; import 'package:Openbook/pages/home/modals/invite_to_community.dart'; import 'package:Openbook/pages/home/modals/post_comment/post-commenter-expanded.dart'; +import 'package:Openbook/pages/home/modals/report_object/pages/confirm_report_object.dart'; +import 'package:Openbook/pages/home/modals/report_object/report_object.dart'; import 'package:Openbook/pages/home/pages/community/pages/manage_community/pages/community_administrators/modals/add_community_administrator/add_community_administrator.dart'; import 'package:Openbook/pages/home/modals/create_post/create_post.dart'; import 'package:Openbook/pages/home/modals/edit_user_profile/edit_user_profile.dart'; @@ -316,4 +318,18 @@ class ModalService { ); })); } + + Future openReportObject( + {@required BuildContext context, + @required dynamic object, + ValueChanged onObjectReported}) async { + await Navigator.of(context).push(CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) { + return OBReportObjectModal( + object: object, + onObjectReported: onObjectReported, + ); + })); + } } diff --git a/lib/services/moderation_api.dart b/lib/services/moderation_api.dart index 2bcc140c9..1743e1d79 100644 --- a/lib/services/moderation_api.dart +++ b/lib/services/moderation_api.dart @@ -58,12 +58,7 @@ class ModerationApiService { queryParameters: queryParams, appendAuthorizationToken: true); } - Future getModerationCategories( - {int count, - int maxId, - String type, - bool verified, - List statuses}) { + Future getModerationCategories() { String path = GET_MODERATION_CATEGORIES_PATH; return _httpService.get(_makeApiUrl(path), appendAuthorizationToken: true); diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 65b33a262..31590d13c 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -2,6 +2,7 @@ import 'package:Openbook/models/circle.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/emoji.dart'; import 'package:Openbook/models/follows_list.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/models/post_reactions_emoji_count.dart'; @@ -13,6 +14,7 @@ import 'package:Openbook/pages/home/modals/create_post/pages/share_post/pages/sh import 'package:Openbook/pages/home/modals/create_post/pages/share_post/pages/share_post_with_community.dart'; import 'package:Openbook/pages/home/modals/create_post/pages/share_post/share_post.dart'; import 'package:Openbook/pages/home/modals/post_reactions/post_reactions.dart'; +import 'package:Openbook/pages/home/modals/report_object/pages/confirm_report_object.dart'; import 'package:Openbook/pages/home/pages/community/community.dart'; import 'package:Openbook/pages/home/pages/community/pages/community_members.dart'; import 'package:Openbook/pages/home/pages/community/pages/manage_community/manage_community.dart'; @@ -464,6 +466,20 @@ class NavigationService { ))); } + Future navigateToConfirmReportObject( + {@required BuildContext context, + @required dynamic object, + @required ModerationCategory category}) { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obConfirmReportObject'), + widget: OBConfirmReportObject( + object: object, + category: category, + ))); + } + Future navigateToBlankPageWithWidget( {@required BuildContext context, @required String navBarTitle, diff --git a/lib/services/user.dart b/lib/services/user.dart index f8a3f0486..3192b3947 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -20,6 +20,7 @@ import 'package:Openbook/models/follows_list.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/moderation/moderated_object_list.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/moderation/moderation_category_list.dart'; import 'package:Openbook/models/notifications/notification.dart'; import 'package:Openbook/models/notifications/notifications_list.dart'; import 'package:Openbook/models/post.dart'; @@ -1422,7 +1423,7 @@ class UserService { Future reportUser( {@required User user, String description, - ModerationCategory moderationCategory}) async { + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _authApiService.reportUserWithUsername( userUsername: user.username, moderationCategoryId: moderationCategory.id); @@ -1432,7 +1433,7 @@ class UserService { Future reportCommunity( {@required Community community, String description, - ModerationCategory moderationCategory}) async { + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _communitiesApiService.reportCommunity( communityName: community.name, moderationCategoryId: moderationCategory.id); @@ -1442,7 +1443,7 @@ class UserService { Future reportPost( {@required Post post, String description, - ModerationCategory moderationCategory}) async { + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _postsApiService.reportPost( postUuid: post.uuid, moderationCategoryId: moderationCategory.id); _checkResponseIsCreated(response); @@ -1452,7 +1453,7 @@ class UserService { {@required PostComment postComment, Post post, String description, - ModerationCategory moderationCategory}) async { + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _postsApiService.reportPostComment( postCommentId: postComment.id, postUuid: post.uuid, @@ -1546,6 +1547,15 @@ class UserService { _checkResponseIsCreated(response); } + Future getModerationCategories() async { + HttpieResponse response = + await _moderationApiService.getModerationCategories(); + + _checkResponseIsOk(response); + + return ModerationCategoriesList.fromJson(json.decode(response.body)); + } + Future _getDeviceName() async { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); diff --git a/lib/widgets/tiles/actions/report_object_tile.dart b/lib/widgets/tiles/actions/report_object_tile.dart new file mode 100644 index 000000000..3314bab40 --- /dev/null +++ b/lib/widgets/tiles/actions/report_object_tile.dart @@ -0,0 +1,68 @@ +import 'package:Openbook/models/user.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/modal_service.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tiles/loading_tile.dart'; +import 'package:flutter/material.dart'; + +class OBReportUserTile extends StatefulWidget { + final User user; + final ValueChanged onUserReported; + final VoidCallback onWantsToReportUser; + + const OBReportUserTile({ + Key key, + this.onUserReported, + @required this.user, + this.onWantsToReportUser, + }) : super(key: key); + + @override + OBReportUserTileState createState() { + return OBReportUserTileState(); + } +} + +class OBReportUserTileState extends State { + ModalService _modalService; + bool _requestInProgress; + + @override + void initState() { + super.initState(); + _requestInProgress = false; + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _modalService = openbookProvider.modalService; + + return StreamBuilder( + stream: widget.user.updateSubject, + initialData: widget.user, + builder: (BuildContext context, AsyncSnapshot snapshot) { + var user = snapshot.data; + + bool isReported = user.isReported ?? false; + + return OBLoadingTile( + isLoading: _requestInProgress || isReported, + leading: OBIcon(OBIcons.report), + title: OBText( + isReported ? 'You have reported this account' : 'Report account'), + onTap: isReported ? () {} : _reportUser, + ); + }, + ); + } + + void _reportUser() { + if (widget.onWantsToReportUser != null) widget.onWantsToReportUser(); + _modalService.openReportObject( + context: context, + object: widget.user, + onObjectReported: widget.onUserReported); + } +} diff --git a/lib/widgets/tiles/actions/report_user_tile.dart b/lib/widgets/tiles/actions/report_user_tile.dart deleted file mode 100644 index 1cb2122d3..000000000 --- a/lib/widgets/tiles/actions/report_user_tile.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:Openbook/models/user.dart'; -import 'package:Openbook/provider.dart'; -import 'package:Openbook/services/httpie.dart'; -import 'package:Openbook/services/toast.dart'; -import 'package:Openbook/services/user.dart'; -import 'package:Openbook/widgets/icon.dart'; -import 'package:Openbook/widgets/theming/text.dart'; -import 'package:Openbook/widgets/tiles/loading_tile.dart'; -import 'package:flutter/material.dart'; - -class OBReportUserTile extends StatefulWidget { - final User user; - final VoidCallback onReportedUser; - final VoidCallback onUnreportedUser; - - const OBReportUserTile({ - Key key, - @required this.user, - this.onReportedUser, - this.onUnreportedUser, - }) : super(key: key); - - @override - OBReportUserTileState createState() { - return OBReportUserTileState(); - } -} - -class OBReportUserTileState extends State { - UserService _userService; - ToastService _toastService; - bool _requestInProgress; - - @override - void initState() { - super.initState(); - _requestInProgress = false; - } - - @override - Widget build(BuildContext context) { - var openbookProvider = OpenbookProvider.of(context); - _userService = openbookProvider.userService; - _toastService = openbookProvider.toastService; - - return StreamBuilder( - stream: widget.user.updateSubject, - initialData: widget.user, - builder: (BuildContext context, AsyncSnapshot snapshot) { - var user = snapshot.data; - - bool isReported = user.isReported ?? false; - - return OBLoadingTile( - isLoading: _requestInProgress || isReported, - leading: OBIcon(OBIcons.report), - title: OBText( - isReported ? 'Report account' : 'You have reported this account'), - onTap: isReported ? () {} : _reportUser, - ); - }, - ); - } - - void _reportUser() async { - _setRequestInProgress(true); - try { - await _userService.reportUser(widget.user); - if (widget.onReportedUser != null) widget.onReportedUser(); - } catch (e) { - _onError(e); - } finally { - _setRequestInProgress(false); - } - } - - void _unreportUser() async { - _setRequestInProgress(true); - try { - await _userService.unreportUser(widget.user); - if (widget.onUnreportedUser != null) widget.onUnreportedUser(); - } catch (e) { - _onError(e); - } finally { - _setRequestInProgress(false); - } - } - - void _onError(error) async { - if (error is HttpieConnectionRefusedError) { - _toastService.error( - message: error.toHumanReadableMessage(), context: context); - } else if (error is HttpieRequestError) { - String errorMessage = await error.toHumanReadableMessage(); - _toastService.error(message: errorMessage, context: context); - } else { - _toastService.error(message: 'Unknown error', context: context); - throw error; - } - } - - void _setRequestInProgress(bool requestInProgress) { - setState(() { - _requestInProgress = requestInProgress; - }); - } -} From 5eb90bb09524a6a29b9b1f257f2a88357b778fe0 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Sun, 26 May 2019 15:07:52 +0200 Subject: [PATCH 19/65] :construction: report account --- lib/models/user.dart | 2 +- .../pages/confirm_report_object.dart | 169 ------- .../pages/confirm_report_object.dart | 210 ++++++++ .../report_object/report_object.dart | 63 +-- lib/services/auth_api.dart | 7 +- lib/services/modal_service.dart | 16 - lib/services/navigation_service.dart | 18 +- lib/services/user.dart | 452 +++++++++--------- lib/widgets/fields/text_form_field.dart | 12 +- lib/widgets/icon.dart | 1 + .../tiles/actions/report_object_tile.dart | 10 +- 11 files changed, 491 insertions(+), 469 deletions(-) delete mode 100644 lib/pages/home/modals/report_object/pages/confirm_report_object.dart create mode 100644 lib/pages/home/pages/report_object/pages/confirm_report_object.dart rename lib/pages/home/{modals => pages}/report_object/report_object.dart (73%) diff --git a/lib/models/user.dart b/lib/models/user.dart index b4468d0d8..f605bf1b1 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -128,7 +128,7 @@ class User extends UpdatableModel { if (json.containsKey('is_following')) isFollowing = json['is_following']; if (json.containsKey('is_connected')) isConnected = json['is_connected']; if (json.containsKey('is_blocked')) isBlocked = json['is_blocked']; - if (json.containsKey('is_reported')) isBlocked = json['is_reported']; + if (json.containsKey('is_reported')) isReported = json['is_reported']; if (json.containsKey('connections_circle_id')) connectionsCircleId = json['connections_circle_id']; if (json.containsKey('is_fully_connected')) diff --git a/lib/pages/home/modals/report_object/pages/confirm_report_object.dart b/lib/pages/home/modals/report_object/pages/confirm_report_object.dart deleted file mode 100644 index 2a90bd947..000000000 --- a/lib/pages/home/modals/report_object/pages/confirm_report_object.dart +++ /dev/null @@ -1,169 +0,0 @@ -import 'package:Openbook/models/community.dart'; -import 'package:Openbook/models/moderation/moderation_category.dart'; -import 'package:Openbook/models/post.dart'; -import 'package:Openbook/models/post_comment.dart'; -import 'package:Openbook/models/user.dart'; -import 'package:Openbook/provider.dart'; -import 'package:Openbook/services/toast.dart'; -import 'package:Openbook/services/user.dart'; -import 'package:Openbook/widgets/buttons/button.dart'; -import 'package:Openbook/widgets/icon.dart'; -import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; -import 'package:Openbook/widgets/theming/primary_color_container.dart'; -import 'package:Openbook/widgets/theming/text.dart'; -import 'package:flutter/cupertino.dart'; - -class OBConfirmReportObject extends StatefulWidget { - final dynamic object; - final ModerationCategory category; - - const OBConfirmReportObject( - {Key key, @required this.object, @required this.category}) - : super(key: key); - - @override - OBConfirmReportObjectState createState() { - return OBConfirmReportObjectState(); - } -} - -class OBConfirmReportObjectState extends State { - bool _confirmationInProgress; - UserService _userService; - ToastService _toastService; - bool _needsBootstrap; - - String description; - - @override - void initState() { - super.initState(); - _needsBootstrap = true; - _confirmationInProgress = false; - } - - @override - Widget build(BuildContext context) { - if (_needsBootstrap) { - OpenbookProviderState openbookProvider = OpenbookProvider.of(context); - _userService = openbookProvider.userService; - _toastService = openbookProvider.toastService; - _needsBootstrap = false; - } - - return CupertinoPageScaffold( - navigationBar: OBThemedNavigationBar(title: 'Confirmation'), - child: OBPrimaryColorContainer( - child: Column( - children: [ - Expanded( - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 40, vertical: 40), - child: Column( - //crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - OBIcon( - OBIcons.report, - themeColor: OBIconThemeColor.primaryAccent, - size: OBIconSize.extraLarge, - ), - const SizedBox( - height: 20, - ), - OBText( - 'You are about to report this item.', - textAlign: TextAlign.center, - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox( - height: 40, - ), - const OBText('The report is anonymous.') - ], - ), - ), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), - child: Row( - children: [ - Expanded( - child: OBButton( - size: OBButtonSize.large, - type: OBButtonType.highlight, - child: Text('No'), - onPressed: _onCancel, - ), - ), - const SizedBox( - width: 20, - ), - Expanded( - child: OBButton( - size: OBButtonSize.large, - child: Text('Yes'), - onPressed: _onConfirm, - isLoading: _confirmationInProgress, - ), - ) - ], - ), - ) - ], - ))); - } - - void _onConfirm() async { - _setConfirmationInProgress(true); - try { - Future reportFuture; - if (widget.object is Post) { - reportFuture = _userService.reportPost( - post: widget.object, moderationCategory: widget.category); - } else if (widget.object is PostComment) { - reportFuture = _userService.reportPostComment( - postComment: widget.object, moderationCategory: widget.category); - } else if (widget.object is Community) { - reportFuture = _userService.reportCommunity( - community: widget.object, moderationCategory: widget.category); - } else if (widget.object is User) { - reportFuture = _userService.reportUser( - user: widget.object, moderationCategory: widget.category); - } else { - throw 'Object type not supported'; - } - - Navigator.of(context).pop(true); - } catch (error) { - _onError(error); - } finally { - _setConfirmationInProgress(false); - } - } - - void _onError(error) async { - if (error is HttpieConnectionRefusedError) { - _toastService.error( - message: error.toHumanReadableMessage(), context: context); - } else if (error is HttpieRequestError) { - String errorMessage = await error.toHumanReadableMessage(); - _toastService.error(message: errorMessage, context: context); - } else { - _toastService.error(message: 'Unknown error', context: context); - throw error; - } - } - - void _onCancel() { - Navigator.of(context).pop(false); - } - - void _setConfirmationInProgress(confirmationInProgress) { - setState(() { - _confirmationInProgress = confirmationInProgress; - }); - } -} diff --git a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart new file mode 100644 index 000000000..bd321957f --- /dev/null +++ b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart @@ -0,0 +1,210 @@ +import 'package:Openbook/libs/type_to_str.dart'; +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/models/user.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/alerts/alert.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/fields/text_form_field.dart'; +import 'package:Openbook/widgets/markdown.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:async/async.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBConfirmReportObject extends StatefulWidget { + final dynamic object; + final ModerationCategory category; + + const OBConfirmReportObject( + {Key key, @required this.object, @required this.category}) + : super(key: key); + + @override + OBConfirmReportObjectState createState() { + return OBConfirmReportObjectState(); + } +} + +class OBConfirmReportObjectState extends State { + bool _confirmationInProgress; + UserService _userService; + ToastService _toastService; + bool _needsBootstrap; + TextEditingController _descriptionController; + + String description; + + CancelableOperation _submitReportOperation; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _confirmationInProgress = false; + _descriptionController = TextEditingController(); + } + + @override + void dispose() { + super.dispose(); + if (_submitReportOperation != null) _submitReportOperation.cancel(); + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + _needsBootstrap = false; + } + + return OBCupertinoPageScaffold( + navigationBar: OBThemedNavigationBar(title: 'Submit report'), + child: OBPrimaryColorContainer( + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 40, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBText( + 'Can you provide extra details that might be relevant to the report?', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 10, + ), + OBSecondaryText( + '(Optional)', + ), + const SizedBox( + height: 10, + ), + OBAlert( + padding: const EdgeInsets.all(10), + child: OBTextFormField( + controller: _descriptionController, + maxLines: 3, + hasBorder: false, + decoration: const InputDecoration( + hintText: 'Type here...', + contentPadding: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 10), + ), + ), + ), + const SizedBox( + height: 40, + ), + OBText( + 'Here\'s what will happen next:', + style: + TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 20, + ), + OBMarkdown( + onlyBody: true, + data: '- Your report will be submitted anonymously. \n ' + '- If you are porting a post or comment, the report will be sent to both the Openbook staff and the community moderators if applicable and the post will be hidden from your feed \n' + '- If you are reporting an account or community, it will be sent to the Openbook staff. \n' + '- We\'ll review it, if approved, content will be deleted and penalties delivered to the people involved randing from deletion of account to hours of suspension depending on the severity of the report. \n' + '- If the report is found to be made in an attempt to damage another member or community in the platform with no infringement of the stated reason, penalties will be applied to you. \n') + ], + ), + ), + )), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: Row( + children: [ + Expanded( + child: OBButton( + size: OBButtonSize.large, + child: Text('I understand, submit.'), + onPressed: _onConfirm, + isLoading: _confirmationInProgress, + ), + ) + ], + ), + ) + ], + ))); + } + + void _onConfirm() async { + _setConfirmationInProgress(true); + try { + if (widget.object is Post) { + _submitReportOperation = CancelableOperation.fromFuture( + _userService.reportPost( + description: _descriptionController.text, + post: widget.object, + moderationCategory: widget.category)); + } else if (widget.object is PostComment) { + _submitReportOperation = CancelableOperation.fromFuture( + _userService.reportPostComment( + description: _descriptionController.text, + postComment: widget.object, + moderationCategory: widget.category)); + } else if (widget.object is Community) { + _submitReportOperation = CancelableOperation.fromFuture( + _userService.reportCommunity( + description: _descriptionController.text, + community: widget.object, + moderationCategory: widget.category)); + } else if (widget.object is User) { + _submitReportOperation = CancelableOperation.fromFuture( + _userService.reportUser( + description: _descriptionController.text, + user: widget.object, + moderationCategory: widget.category)); + } else { + throw 'Object type not supported'; + } + await _submitReportOperation.value; + Navigator.of(context).pop(true); + } catch (error) { + _onError(error); + } finally { + _setConfirmationInProgress(false); + } + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } + + void _setConfirmationInProgress(confirmationInProgress) { + setState(() { + _confirmationInProgress = confirmationInProgress; + }); + } +} diff --git a/lib/pages/home/modals/report_object/report_object.dart b/lib/pages/home/pages/report_object/report_object.dart similarity index 73% rename from lib/pages/home/modals/report_object/report_object.dart rename to lib/pages/home/pages/report_object/report_object.dart index d04ee76b7..4de949753 100644 --- a/lib/pages/home/modals/report_object/report_object.dart +++ b/lib/pages/home/pages/report_object/report_object.dart @@ -1,38 +1,37 @@ import 'package:Openbook/libs/type_to_str.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/moderation/moderation_category_list.dart'; -import 'package:Openbook/models/theme.dart'; import 'package:Openbook/services/navigation_service.dart'; import 'package:Openbook/services/theme.dart'; import 'package:Openbook/services/theme_value_parser.dart'; import 'package:Openbook/services/user.dart'; -import 'package:Openbook/widgets/fields/checkbox_field.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; import 'package:Openbook/widgets/progress_indicator.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class OBReportObjectModal extends StatefulWidget { +class OBReportObjectPage extends StatefulWidget { final dynamic object; final OnObjectReported onObjectReported; - const OBReportObjectModal({ + const OBReportObjectPage({ Key key, this.object, this.onObjectReported, }) : super(key: key); @override - OBReportObjectModalState createState() { - return OBReportObjectModalState(); + OBReportObjectPageState createState() { + return OBReportObjectPageState(); } } -class OBReportObjectModalState extends State { +class OBReportObjectPageState extends State { ThemeService _themeService; NavigationService _navigationService; UserService _userService; @@ -59,22 +58,22 @@ class OBReportObjectModalState extends State { _needsBootstrap = false; } - OBTheme theme = _themeService.getActiveTheme(); - - Color textColor = _themeValueParserService - .parseGradient(theme.primaryAccentColor) - .colors[1]; - return OBCupertinoPageScaffold( navigationBar: _buildNavigationBar(), child: OBPrimaryColorContainer( child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - ListTile( - title: Text( - 'Please select a reason', - style: TextStyle(color: textColor), - )), + Padding( + padding: + EdgeInsets.only(left: 20, right: 20, bottom: 20, top: 20), + child: OBText( + 'Why are you reporting this ' + + modelTypeToString(widget.object) + + '?', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), + ), + ), _moderationCategories.isEmpty ? _buildProgressIndicator() : _buildModerationCategories(), @@ -107,38 +106,30 @@ class OBReportObjectModalState extends State { Widget _buildModerationCategoryTile(context, index) { ModerationCategory category = _moderationCategories[index]; - return OBCheckboxField( - value: _pickedModerationCategory == category, + return ListTile( onTap: () async { var result = await _navigationService.navigateToConfirmReportObject( object: widget.object, category: category, context: context); - if (result != null) { - widget.onObjectReported(widget.object); + if (result != null && result) { + if(widget.onObjectReported != null) widget.onObjectReported(widget.object); Navigator.pop(context); } }, - title: category.title, - subtitle: category.description, + title: OBText( + category.title, + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: OBSecondaryText(category.description), + //trailing: OBIcon(OBIcons.chevronRight), ); } Widget _buildNavigationBar() { return OBThemedNavigationBar( title: 'Report ' + modelTypeToString(widget.object), - trailing: GestureDetector( - onTap: _onWantsToGoNext, - child: const OBText('Next'), - ), ); } - void _onWantsToGoNext() { - _navigationService.navigateToConfirmReportObject( - context: context, - object: widget.object, - category: _pickedModerationCategory); - } - void _bootstrap() async { var moderationCategories = await _userService.getModerationCategories(); _setModerationCategories(moderationCategories); @@ -146,11 +137,9 @@ class OBReportObjectModalState extends State { _setModerationCategories(ModerationCategoriesList moderationCategoriesList) { setState(() { - print(moderationCategoriesList.moderationCategories); _moderationCategories = moderationCategoriesList.moderationCategories; }); } - } typedef OnObjectReported(dynamic object); diff --git a/lib/services/auth_api.dart b/lib/services/auth_api.dart index eecd8a699..5064d1bb0 100644 --- a/lib/services/auth_api.dart +++ b/lib/services/auth_api.dart @@ -367,15 +367,14 @@ class AuthApiService { String description}) { String path = _makeReportUserPath(username: userUsername); - Map body = { - 'category_id': moderationCategoryId - }; + Map body = {'category_id': moderationCategoryId.toString()}; if (description != null && description.isNotEmpty) { body['description'] = description; } - return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + return _httpService.post(_makeApiUrl(path), + body: body, appendAuthorizationToken: true); } String _makeBlockUserWithUsernamePath(String username) { diff --git a/lib/services/modal_service.dart b/lib/services/modal_service.dart index af3e0d4a2..81c06f85b 100644 --- a/lib/services/modal_service.dart +++ b/lib/services/modal_service.dart @@ -12,8 +12,6 @@ import 'package:Openbook/pages/home/modals/accept_guidelines/accept_guidelines.d import 'package:Openbook/pages/home/modals/edit_post/edit_post.dart'; import 'package:Openbook/pages/home/modals/invite_to_community.dart'; import 'package:Openbook/pages/home/modals/post_comment/post-commenter-expanded.dart'; -import 'package:Openbook/pages/home/modals/report_object/pages/confirm_report_object.dart'; -import 'package:Openbook/pages/home/modals/report_object/report_object.dart'; import 'package:Openbook/pages/home/pages/community/pages/manage_community/pages/community_administrators/modals/add_community_administrator/add_community_administrator.dart'; import 'package:Openbook/pages/home/modals/create_post/create_post.dart'; import 'package:Openbook/pages/home/modals/edit_user_profile/edit_user_profile.dart'; @@ -318,18 +316,4 @@ class ModalService { ); })); } - - Future openReportObject( - {@required BuildContext context, - @required dynamic object, - ValueChanged onObjectReported}) async { - await Navigator.of(context).push(CupertinoPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) { - return OBReportObjectModal( - object: object, - onObjectReported: onObjectReported, - ); - })); - } } diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 31590d13c..0837811fb 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -14,7 +14,6 @@ import 'package:Openbook/pages/home/modals/create_post/pages/share_post/pages/sh import 'package:Openbook/pages/home/modals/create_post/pages/share_post/pages/share_post_with_community.dart'; import 'package:Openbook/pages/home/modals/create_post/pages/share_post/share_post.dart'; import 'package:Openbook/pages/home/modals/post_reactions/post_reactions.dart'; -import 'package:Openbook/pages/home/modals/report_object/pages/confirm_report_object.dart'; import 'package:Openbook/pages/home/pages/community/community.dart'; import 'package:Openbook/pages/home/pages/community/pages/community_members.dart'; import 'package:Openbook/pages/home/pages/community/pages/manage_community/manage_community.dart'; @@ -49,6 +48,8 @@ import 'package:Openbook/pages/home/pages/post/post.dart'; import 'package:Openbook/pages/home/pages/post_comments/post.dart'; import 'package:Openbook/pages/home/pages/post_comments/post_comments_linked.dart'; import 'package:Openbook/pages/home/pages/profile/profile.dart'; +import 'package:Openbook/pages/home/pages/report_object/pages/confirm_report_object.dart'; +import 'package:Openbook/pages/home/pages/report_object/report_object.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/routes/slide_right_route.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; @@ -480,6 +481,21 @@ class NavigationService { ))); } + + Future navigateToReportObject( + {@required BuildContext context, + @required dynamic object, + ValueChanged onObjectReported}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obReportObject'), + widget: OBReportObjectPage( + object: object, + onObjectReported: onObjectReported, + ))); + } + Future navigateToBlankPageWithWidget( {@required BuildContext context, @required String navBarTitle, diff --git a/lib/services/user.dart b/lib/services/user.dart index 3192b3947..0624167d9 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -161,7 +161,7 @@ class UserService { Future deleteAccountWithPassword(String password) async { HttpieResponse response = - await _authApiService.deleteUser(password: password); + await _authApiService.deleteUser(password: password); _checkResponseIsOk(response); } @@ -207,9 +207,8 @@ class UserService { _checkResponseIsOk(response); } - Future verifyPasswordReset( - {@required String newPassword, - @required String passwordResetToken}) async { + Future verifyPasswordReset({@required String newPassword, + @required String passwordResetToken}) async { HttpieResponse response = await _authApiService.verifyPasswordReset( newPassword: newPassword, passwordResetToken: passwordResetToken); _checkResponseIsOk(response); @@ -237,7 +236,7 @@ class UserService { if (_authToken == null) throw AuthTokenMissingError(); HttpieResponse response = - await _authApiService.getUserWithAuthToken(_authToken); + await _authApiService.getUserWithAuthToken(_authToken); _checkResponseIsOk(response); var userData = response.body; return _setUserWithData(userData); @@ -245,14 +244,14 @@ class UserService { Future updateUserEmail(String email) async { HttpieStreamedResponse response = - await _authApiService.updateUserEmail(email: email); + await _authApiService.updateUserEmail(email: email); _checkResponseIsOk(response); String userData = await response.readAsString(); return _makeLoggedInUser(userData); } - Future updateUserPassword( - String currentPassword, String newPassword) async { + Future updateUserPassword(String currentPassword, + String newPassword) async { HttpieStreamedResponse response = await _authApiService.updateUserPassword( currentPassword: currentPassword, newPassword: newPassword); _checkResponseIsOk(response); @@ -322,21 +321,20 @@ class UserService { Future getTrendingPosts() async { HttpieResponse response = - await _postsApiService.getTrendingPosts(authenticatedRequest: true); + await _postsApiService.getTrendingPosts(authenticatedRequest: true); _checkResponseIsOk(response); return PostsList.fromJson(json.decode(response.body)); } - Future getTimelinePosts( - {List circles = const [], - List followsLists = const [], - int maxId, - int count, - String username, - bool areFirstPosts = false, - bool cachePosts = false}) async { + Future getTimelinePosts({List circles = const [], + List followsLists = const [], + int maxId, + int count, + String username, + bool areFirstPosts = false, + bool cachePosts = false}) async { HttpieResponse response = await _postsApiService.getTimelinePosts( circleIds: circles.map((circle) => circle.id).toList(), listIds: followsLists.map((followsList) => followsList.id).toList(), @@ -361,11 +359,10 @@ class UserService { return PostsList(); } - Future createPost( - {String text, - List circles = const [], - File image, - File video}) async { + Future createPost({String text, + List circles = const [], + File image, + File video}) async { HttpieStreamedResponse response = await _postsApiService.createPost( text: text, circleIds: circles.map((circle) => circle.id).toList(), @@ -383,7 +380,7 @@ class UserService { Future editPost({String postUuid, String text}) async { HttpieStreamedResponse response = - await _postsApiService.editPost(postUuid: postUuid, text: text); + await _postsApiService.editPost(postUuid: postUuid, text: text); _checkResponseIsOk(response); @@ -393,34 +390,34 @@ class UserService { Future deletePost(Post post) async { HttpieResponse response = - await _postsApiService.deletePostWithUuid(post.uuid); + await _postsApiService.deletePostWithUuid(post.uuid); _checkResponseIsOk(response); } Future disableCommentsForPost(Post post) async { HttpieResponse response = - await _postsApiService.disableCommentsForPostWithUuidPost(post.uuid); + await _postsApiService.disableCommentsForPostWithUuidPost(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future enableCommentsForPost(Post post) async { HttpieResponse response = - await _postsApiService.enableCommentsForPostWithUuidPost(post.uuid); + await _postsApiService.enableCommentsForPostWithUuidPost(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future closePost(Post post) async { HttpieResponse response = - await _postsApiService.closePostWithUuid(post.uuid); + await _postsApiService.closePostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future openPost(Post post) async { HttpieResponse response = - await _postsApiService.openPostWithUuid(post.uuid); + await _postsApiService.openPostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } @@ -431,10 +428,9 @@ class UserService { return Post.fromJson(json.decode(response.body)); } - Future reactToPost( - {@required Post post, - @required Emoji emoji, - @required EmojiGroup emojiGroup}) async { + Future reactToPost({@required Post post, + @required Emoji emoji, + @required EmojiGroup emojiGroup}) async { HttpieResponse response = await _postsApiService.reactToPost( postUuid: post.uuid, emojiId: emoji.id, emojiGroupId: emojiGroup.id); _checkResponseIsCreated(response); @@ -451,8 +447,8 @@ class UserService { Future getReactionsForPost(Post post, {int count, int maxId, Emoji emoji}) async { HttpieResponse response = - await _postsApiService.getReactionsForPostWithUuid(post.uuid, - count: count, maxId: maxId, emojiId: emoji.id); + await _postsApiService.getReactionsForPostWithUuid(post.uuid, + count: count, maxId: maxId, emojiId: emoji.id); _checkResponseIsOk(response); @@ -462,7 +458,7 @@ class UserService { Future getReactionsEmojiCountForPost( Post post) async { HttpieResponse response = - await _postsApiService.getReactionsEmojiCountForPostWithUuid(post.uuid); + await _postsApiService.getReactionsEmojiCountForPostWithUuid(post.uuid); _checkResponseIsOk(response); @@ -472,15 +468,14 @@ class UserService { Future commentPost( {@required Post post, @required String text}) async { HttpieResponse response = - await _postsApiService.commentPost(postUuid: post.uuid, text: text); + await _postsApiService.commentPost(postUuid: post.uuid, text: text); _checkResponseIsCreated(response); return PostComment.fromJSON(json.decode(response.body)); } - Future editPostComment( - {@required Post post, - @required PostComment postComment, - @required String text}) async { + Future editPostComment({@required Post post, + @required PostComment postComment, + @required String text}) async { HttpieResponse response = await _postsApiService.editPostComment( postUuid: post.uuid, postCommentId: postComment.id, text: text); _checkResponseIsOk(response); @@ -496,24 +491,24 @@ class UserService { Future mutePost(Post post) async { HttpieResponse response = - await _postsApiService.mutePostWithUuid(post.uuid); + await _postsApiService.mutePostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future unmutePost(Post post) async { HttpieResponse response = - await _postsApiService.unmutePostWithUuid(post.uuid); + await _postsApiService.unmutePostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future getCommentsForPost(Post post, {int maxId, - int countMax, - int minId, - int countMin, - PostCommentsSortType sort}) async { + int countMax, + int minId, + int countMin, + PostCommentsSortType sort}) async { HttpieResponse response = await _postsApiService.getCommentsForPostWithUuid( post.uuid, countMax: countMax, @@ -538,7 +533,7 @@ class UserService { Future getReactionEmojiGroups() async { HttpieResponse response = - await this._postsApiService.getReactionEmojiGroups(); + await this._postsApiService.getReactionEmojiGroups(); _checkResponseIsOk(response); @@ -567,11 +562,10 @@ class UserService { return UsersList.fromJson(json.decode(response.body)); } - Future getLinkedUsers( - {bool authenticatedRequest = true, - int maxId, - int count, - Community withCommunity}) async { + Future getLinkedUsers({bool authenticatedRequest = true, + int maxId, + int count, + Community withCommunity}) async { HttpieResponse response = await _authApiService.getLinkedUsers( count: count, withCommunity: withCommunity.name, maxId: maxId); _checkResponseIsOk(response); @@ -580,14 +574,14 @@ class UserService { Future blockUser(User user) async { HttpieResponse response = - await _authApiService.blockUserWithUsername(user.username); + await _authApiService.blockUserWithUsername(user.username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } Future unblockUser(User user) async { HttpieResponse response = - await _authApiService.unblockUserWithUsername(user.username); + await _authApiService.unblockUserWithUsername(user.username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } @@ -595,32 +589,31 @@ class UserService { Future searchBlockedUsers( {@required String query, int count}) async { HttpieResponse response = - await _authApiService.searchBlockedUsers(query: query, count: count); + await _authApiService.searchBlockedUsers(query: query, count: count); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } Future getBlockedUsers({int maxId, int count}) async { HttpieResponse response = - await _authApiService.getBlockedUsers(count: count, maxId: maxId); + await _authApiService.getBlockedUsers(count: count, maxId: maxId); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } Future searchFollowers({@required String query, int count}) async { HttpieResponse response = - await _authApiService.searchFollowers(query: query, count: count); + await _authApiService.searchFollowers(query: query, count: count); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } - Future getFollowers( - {bool authenticatedRequest = true, - int maxId, - int count, - Community withCommunity}) async { + Future getFollowers({bool authenticatedRequest = true, + int maxId, + int count, + Community withCommunity}) async { HttpieResponse response = - await _authApiService.getFollowers(count: count, maxId: maxId); + await _authApiService.getFollowers(count: count, maxId: maxId); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } @@ -628,18 +621,17 @@ class UserService { Future searchFollowings( {@required String query, int count, Community withCommunity}) async { HttpieResponse response = - await _authApiService.searchFollowings(query: query, count: count); + await _authApiService.searchFollowings(query: query, count: count); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } - Future getFollowings( - {bool authenticatedRequest = true, - int maxId, - int count, - Community withCommunity}) async { + Future getFollowings({bool authenticatedRequest = true, + int maxId, + int count, + Community withCommunity}) async { HttpieResponse response = - await _authApiService.getFollowings(count: count, maxId: maxId); + await _authApiService.getFollowings(count: count, maxId: maxId); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } @@ -655,7 +647,7 @@ class UserService { Future unFollowUserWithUsername(String username) async { HttpieResponse response = - await _followsApiService.unFollowUserWithUsername(username); + await _followsApiService.unFollowUserWithUsername(username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } @@ -672,8 +664,8 @@ class UserService { Future connectWithUserWithUsername(String username, {List circles = const []}) async { HttpieResponse response = - await _connectionsApiService.connectWithUserWithUsername(username, - circlesIds: circles.map((circle) => circle.id).toList()); + await _connectionsApiService.connectWithUserWithUsername(username, + circlesIds: circles.map((circle) => circle.id).toList()); _checkResponseIsCreated(response); return Connection.fromJson(json.decode(response.body)); } @@ -682,14 +674,14 @@ class UserService { {List circles = const []}) async { HttpieResponse response = await _connectionsApiService .confirmConnectionWithUserWithUsername(username, - circlesIds: circles.map((circle) => circle.id).toList()); + circlesIds: circles.map((circle) => circle.id).toList()); _checkResponseIsOk(response); return Connection.fromJson(json.decode(response.body)); } Future disconnectFromUserWithUsername(String username) async { HttpieResponse response = - await _connectionsApiService.disconnectFromUserWithUsername(username); + await _connectionsApiService.disconnectFromUserWithUsername(username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } @@ -697,15 +689,15 @@ class UserService { Future updateConnectionWithUsername(String username, {List circles = const []}) async { HttpieResponse response = - await _connectionsApiService.updateConnectionWithUsername(username, - circlesIds: circles.map((circle) => circle.id).toList()); + await _connectionsApiService.updateConnectionWithUsername(username, + circlesIds: circles.map((circle) => circle.id).toList()); _checkResponseIsOk(response); return Connection.fromJson(json.decode(response.body)); } Future getConnectionsCircleWithId(int circleId) async { HttpieResponse response = - await _connectionsCirclesApiService.getCircleWithId(circleId); + await _connectionsCirclesApiService.getCircleWithId(circleId); _checkResponseIsOk(response); return Circle.fromJSON(json.decode(response.body)); } @@ -727,17 +719,17 @@ class UserService { Future updateConnectionsCircle(Circle circle, {String name, String color, List users = const []}) async { HttpieResponse response = - await _connectionsCirclesApiService.updateCircleWithId(circle.id, - name: name, - color: color, - usernames: users.map((user) => user.username).toList()); + await _connectionsCirclesApiService.updateCircleWithId(circle.id, + name: name, + color: color, + usernames: users.map((user) => user.username).toList()); _checkResponseIsOk(response); return Circle.fromJSON(json.decode(response.body)); } Future deleteConnectionsCircle(Circle circle) async { HttpieResponse response = - await _connectionsCirclesApiService.deleteCircleWithId(circle.id); + await _connectionsCirclesApiService.deleteCircleWithId(circle.id); _checkResponseIsOk(response); } @@ -750,7 +742,7 @@ class UserService { Future createFollowsList( {@required String name, Emoji emoji}) async { HttpieResponse response = - await _followsListsApiService.createList(name: name, emojiId: emoji.id); + await _followsListsApiService.createList(name: name, emojiId: emoji.id); _checkResponseIsCreated(response); return FollowsList.fromJSON(json.decode(response.body)); } @@ -768,20 +760,20 @@ class UserService { Future deleteFollowsList(FollowsList list) async { HttpieResponse response = - await _followsListsApiService.deleteListWithId(list.id); + await _followsListsApiService.deleteListWithId(list.id); _checkResponseIsOk(response); } Future getFollowsListWithId(int listId) async { HttpieResponse response = - await _followsListsApiService.getListWithId(listId); + await _followsListsApiService.getListWithId(listId); _checkResponseIsOk(response); return FollowsList.fromJSON(json.decode(response.body)); } Future createUserInvite({String nickname}) async { HttpieStreamedResponse response = - await _userInvitesApiService.createUserInvite(nickname: nickname); + await _userInvitesApiService.createUserInvite(nickname: nickname); _checkResponseIsCreated(response); String responseBody = await response.readAsString(); @@ -803,7 +795,7 @@ class UserService { bool isPending = status != null ? UserInvite.convertUserInviteStatusToBool(status) : UserInvite.convertUserInviteStatusToBool( - UserInviteFilterByStatus.all); + UserInviteFilterByStatus.all); HttpieResponse response = await _userInvitesApiService.getUserInvites( isStatusPending: isPending, count: count, offset: offset); @@ -816,7 +808,7 @@ class UserService { bool isPending = status != null ? UserInvite.convertUserInviteStatusToBool(status) : UserInvite.convertUserInviteStatusToBool( - UserInviteFilterByStatus.all); + UserInviteFilterByStatus.all); HttpieResponse response = await _userInvitesApiService.searchUserInvites( isStatusPending: isPending, count: count, query: query); @@ -826,7 +818,7 @@ class UserService { Future deleteUserInvite(UserInvite userInvite) async { HttpieResponse response = - await _userInvitesApiService.deleteUserInvite(userInvite.id); + await _userInvitesApiService.deleteUserInvite(userInvite.id); _checkResponseIsOk(response); } @@ -848,7 +840,7 @@ class UserService { {String text, File image, File video}) async { HttpieStreamedResponse response = await _communitiesApiService .createPostForCommunityWithId(community.name, - text: text, image: image, video: video); + text: text, image: image, video: video); _checkResponseIsCreated(response); String responseBody = await response.readAsString(); @@ -860,7 +852,7 @@ class UserService { {int maxId, int count}) async { HttpieResponse response = await _communitiesApiService .getPostsForCommunityWithName(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); return PostsList.fromJson(json.decode(response.body)); } @@ -869,45 +861,44 @@ class UserService { {int maxId, int count}) async { HttpieResponse response = await _communitiesApiService .getClosedPostsForCommunityWithName(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); return PostsList.fromJson(json.decode(response.body)); } Future getCommunitiesWithQuery(String query) async { HttpieResponse response = - await _communitiesApiService.getCommunitiesWithQuery(query: query); + await _communitiesApiService.getCommunitiesWithQuery(query: query); _checkResponseIsOk(response); return CommunitiesList.fromJson(json.decode(response.body)); } - Future createCommunity( - {@required String name, - @required String title, - @required List categories, - @required CommunityType type, - String color, - String userAdjective, - String usersAdjective, - bool invitesEnabled, - String description, - String rules, - File cover, - File avatar}) async { + Future createCommunity({@required String name, + @required String title, + @required List categories, + @required CommunityType type, + String color, + String userAdjective, + String usersAdjective, + bool invitesEnabled, + String description, + String rules, + File cover, + File avatar}) async { HttpieStreamedResponse response = - await _communitiesApiService.createCommunity( - name: name, - title: title, - categories: categories.map((category) => category.name).toList(), - type: Community.convertTypeToString(type), - color: color, - userAdjective: userAdjective, - usersAdjective: usersAdjective, - invitesEnabled: invitesEnabled, - description: description, - rules: rules, - cover: cover, - avatar: avatar); + await _communitiesApiService.createCommunity( + name: name, + title: title, + categories: categories.map((category) => category.name).toList(), + type: Community.convertTypeToString(type), + color: color, + userAdjective: userAdjective, + usersAdjective: usersAdjective, + invitesEnabled: invitesEnabled, + description: description, + rules: rules, + cover: cover, + avatar: avatar); _checkResponseIsCreated(response); @@ -918,19 +909,19 @@ class UserService { Future updateCommunity(Community community, {String name, - String title, - List categories, - CommunityType type, - String color, - String userAdjective, - String usersAdjective, - String description, - bool invitesEnabled, - String rules, - File cover, - File avatar}) async { + String title, + List categories, + CommunityType type, + String color, + String userAdjective, + String usersAdjective, + String description, + bool invitesEnabled, + String rules, + File cover, + File avatar}) async { HttpieStreamedResponse response = - await _communitiesApiService.updateCommunityWithName( + await _communitiesApiService.updateCommunityWithName( community.name, name: name, title: title, @@ -999,14 +990,14 @@ class UserService { Future getCommunityWithName(String name) async { HttpieResponse response = - await _communitiesApiService.getCommunityWithName(name); + await _communitiesApiService.getCommunityWithName(name); _checkResponseIsOk(response); return Community.fromJSON(json.decode(response.body)); } Future deleteCommunity(Community community) async { HttpieResponse response = - await _communitiesApiService.deleteCommunityWithName(community.name); + await _communitiesApiService.deleteCommunityWithName(community.name); _checkResponseIsOk(response); } @@ -1014,31 +1005,30 @@ class UserService { {int count, int maxId, List exclude}) async { HttpieResponse response = await _communitiesApiService .getMembersForCommunityWithId(community.name, - count: count, - maxId: maxId, - exclude: exclude != null - ? exclude - .map((exclude) => - Community.convertExclusionToString(exclude)) - .toList() - : null); + count: count, + maxId: maxId, + exclude: exclude != null + ? exclude + .map((exclude) => + Community.convertExclusionToString(exclude)) + .toList() + : null); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } - Future searchCommunityMembers( - {@required Community community, - @required String query, - List exclude}) async { + Future searchCommunityMembers({@required Community community, + @required String query, + List exclude}) async { HttpieResponse response = await _communitiesApiService.searchMembers( communityName: community.name, query: query, exclude: exclude != null ? exclude - .map((exclude) => Community.convertExclusionToString(exclude)) - .toList() + .map((exclude) => Community.convertExclusionToString(exclude)) + .toList() : null, ); @@ -1050,8 +1040,8 @@ class UserService { Future inviteUserToCommunity( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.inviteUserToCommunity( - communityName: community.name, username: user.username); + await _communitiesApiService.inviteUserToCommunity( + communityName: community.name, username: user.username); _checkResponseIsCreated(response); return User.fromJson(json.decode(response.body)); } @@ -1059,15 +1049,15 @@ class UserService { Future uninviteUserFromCommunity( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.uninviteUserFromCommunity( - communityName: community.name, username: user.username); + await _communitiesApiService.uninviteUserFromCommunity( + communityName: community.name, username: user.username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } Future getJoinedCommunities({int offset}) async { HttpieResponse response = - await _communitiesApiService.getJoinedCommunities(offset: offset); + await _communitiesApiService.getJoinedCommunities(offset: offset); _checkResponseIsOk(response); @@ -1084,14 +1074,14 @@ class UserService { Future joinCommunity(Community community) async { HttpieResponse response = - await _communitiesApiService.joinCommunityWithId(community.name); + await _communitiesApiService.joinCommunityWithId(community.name); _checkResponseIsCreated(response); return Community.fromJSON(json.decode(response.body)); } Future leaveCommunity(Community community) async { HttpieResponse response = - await _communitiesApiService.leaveCommunityWithId(community.name); + await _communitiesApiService.leaveCommunityWithId(community.name); _checkResponseIsOk(response); return Community.fromJSON(json.decode(response.body)); } @@ -1100,7 +1090,7 @@ class UserService { {int count, int maxId}) async { HttpieResponse response = await _communitiesApiService .getModeratorsForCommunityWithId(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); @@ -1124,16 +1114,16 @@ class UserService { Future addCommunityModerator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.addCommunityModerator( - communityName: community.name, username: user.username); + await _communitiesApiService.addCommunityModerator( + communityName: community.name, username: user.username); _checkResponseIsCreated(response); } Future removeCommunityModerator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.removeCommunityModerator( - communityName: community.name, username: user.username); + await _communitiesApiService.removeCommunityModerator( + communityName: community.name, username: user.username); _checkResponseIsOk(response); } @@ -1141,7 +1131,7 @@ class UserService { {int count, int maxId}) async { HttpieResponse response = await _communitiesApiService .getAdministratorsForCommunityWithName(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); @@ -1165,8 +1155,8 @@ class UserService { Future addCommunityAdministrator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.addCommunityAdministrator( - communityName: community.name, username: user.username); + await _communitiesApiService.addCommunityAdministrator( + communityName: community.name, username: user.username); _checkResponseIsCreated(response); return Community.fromJSON(json.decode(response.body)); } @@ -1174,8 +1164,8 @@ class UserService { Future removeCommunityAdministrator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.removeCommunityAdministrator( - communityName: community.name, username: user.username); + await _communitiesApiService.removeCommunityAdministrator( + communityName: community.name, username: user.username); _checkResponseIsOk(response); } @@ -1183,7 +1173,7 @@ class UserService { {int count, int maxId}) async { HttpieResponse response = await _communitiesApiService .getBannedUsersForCommunityWithId(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); @@ -1220,7 +1210,7 @@ class UserService { Future getFavoriteCommunities({int offset}) async { HttpieResponse response = - await _communitiesApiService.getFavoriteCommunities(offset: offset); + await _communitiesApiService.getFavoriteCommunities(offset: offset); _checkResponseIsOk(response); @@ -1252,7 +1242,7 @@ class UserService { Future getModeratedCommunities({int offset}) async { HttpieResponse response = - await _communitiesApiService.getModeratedCommunities(offset: offset); + await _communitiesApiService.getModeratedCommunities(offset: offset); _checkResponseIsOk(response); @@ -1274,20 +1264,20 @@ class UserService { Future getNotificationWithId(int notificationId) async { HttpieResponse response = - await _notificationsApiService.getNotificationWithId(notificationId); + await _notificationsApiService.getNotificationWithId(notificationId); _checkResponseIsOk(response); return OBNotification.fromJSON(json.decode(response.body)); } Future readNotifications({int maxId}) async { HttpieResponse response = - await _notificationsApiService.readNotifications(maxId: maxId); + await _notificationsApiService.readNotifications(maxId: maxId); _checkResponseIsOk(response); } Future deleteNotifications() async { HttpieResponse response = - await _notificationsApiService.deleteNotifications(); + await _notificationsApiService.deleteNotifications(); _checkResponseIsOk(response); } @@ -1299,7 +1289,7 @@ class UserService { Future readNotification(OBNotification notification) async { HttpieResponse response = - await _notificationsApiService.readNotificationWithId(notification.id); + await _notificationsApiService.readNotificationWithId(notification.id); _checkResponseIsOk(response); } @@ -1316,7 +1306,7 @@ class UserService { Future createDevice({@required String uuid, String name}) async { HttpieResponse response = - await _devicesApiService.createDevice(uuid: uuid, name: name); + await _devicesApiService.createDevice(uuid: uuid, name: name); _checkResponseIsCreated(response); return Device.fromJSON(json.decode(response.body)); } @@ -1332,13 +1322,13 @@ class UserService { Future deleteDevice(Device device) async { HttpieResponse response = - await _devicesApiService.deleteDeviceWithUuid(device.uuid); + await _devicesApiService.deleteDeviceWithUuid(device.uuid); _checkResponseIsOk(response); } Future getDeviceWithUuid(String deviceUuid) async { HttpieResponse response = - await _devicesApiService.getDeviceWithUuid(deviceUuid); + await _devicesApiService.getDeviceWithUuid(deviceUuid); _checkResponseIsOk(response); return Device.fromJSON(json.decode(response.body)); } @@ -1362,7 +1352,7 @@ class UserService { String deviceUuid = await _getDeviceUuid(); HttpieResponse response = - await _devicesApiService.getDeviceWithUuid(deviceUuid); + await _devicesApiService.getDeviceWithUuid(deviceUuid); if (response.isNotFound()) { // Device does not exists, create one. @@ -1382,7 +1372,7 @@ class UserService { Device currentDevice = await _getOrCreateCurrentDeviceCache; HttpieResponse response = - await _devicesApiService.deleteDeviceWithUuid(currentDevice.uuid); + await _devicesApiService.deleteDeviceWithUuid(currentDevice.uuid); if (!response.isOk() && !response.isNotFound()) { print('Could not delete current device'); @@ -1392,15 +1382,15 @@ class UserService { } Future - getAuthenticatedUserNotificationsSettings() async { + getAuthenticatedUserNotificationsSettings() async { HttpieResponse response = - await _authApiService.getAuthenticatedUserNotificationsSettings(); + await _authApiService.getAuthenticatedUserNotificationsSettings(); _checkResponseIsOk(response); return UserNotificationsSettings.fromJSON(json.decode(response.body)); } Future - updateAuthenticatedUserNotificationsSettings({ + updateAuthenticatedUserNotificationsSettings({ bool postCommentNotifications, bool postReactionNotifications, bool followNotifications, @@ -1409,79 +1399,81 @@ class UserService { bool communityInviteNotifications, }) async { HttpieResponse response = - await _authApiService.updateAuthenticatedUserNotificationsSettings( - postCommentNotifications: postCommentNotifications, - postReactionNotifications: postReactionNotifications, - followNotifications: followNotifications, - connectionConfirmedNotifications: connectionConfirmedNotifications, - communityInviteNotifications: communityInviteNotifications, - connectionRequestNotifications: connectionRequestNotifications); + await _authApiService.updateAuthenticatedUserNotificationsSettings( + postCommentNotifications: postCommentNotifications, + postReactionNotifications: postReactionNotifications, + followNotifications: followNotifications, + connectionConfirmedNotifications: connectionConfirmedNotifications, + communityInviteNotifications: communityInviteNotifications, + connectionRequestNotifications: connectionRequestNotifications); _checkResponseIsOk(response); return UserNotificationsSettings.fromJSON(json.decode(response.body)); } - Future reportUser( - {@required User user, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportUser({@required User user, + String description, + @required ModerationCategory moderationCategory}) async { + print(user.username); HttpieResponse response = await _authApiService.reportUserWithUsername( + description: description, userUsername: user.username, moderationCategoryId: moderationCategory.id); _checkResponseIsCreated(response); } - Future reportCommunity( - {@required Community community, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportCommunity({@required Community community, + String description, + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _communitiesApiService.reportCommunity( communityName: community.name, + description: description, moderationCategoryId: moderationCategory.id); _checkResponseIsCreated(response); } - Future reportPost( - {@required Post post, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportPost({@required Post post, + String description, + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _postsApiService.reportPost( + description: description, postUuid: post.uuid, moderationCategoryId: moderationCategory.id); _checkResponseIsCreated(response); } - Future reportPostComment( - {@required PostComment postComment, - Post post, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportPostComment({@required PostComment postComment, + Post post, + String description, + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _postsApiService.reportPostComment( postCommentId: postComment.id, postUuid: post.uuid, + description: description, moderationCategoryId: moderationCategory.id); _checkResponseIsCreated(response); } Future getGlobalModeratedObjects( {List statuses, - List types, - int count, - int maxId, - bool verified}) async { + List types, + int count, + int maxId, + bool verified}) async { HttpieResponse response = - await _moderationApiService - .getGlobalModeratedObjects( - maxId: maxId, - verified: verified, - types: types != null - ? types.map((status) => - ModeratedObject.factory.convertTypeToString(status)) - : null, - statuses: - statuses != null - ? statuses.map((status) => ModeratedObject.factory - .convertStatusToString(status)) - : null, - count: count); + await _moderationApiService + .getGlobalModeratedObjects( + maxId: maxId, + verified: verified, + types: types != null + ? types.map((status) => + ModeratedObject.factory.convertTypeToString(status)) + : null, + statuses: + statuses != null + ? statuses.map((status) => + ModeratedObject.factory + .convertStatusToString(status)) + : null, + count: count); _checkResponseIsOk(response); @@ -1490,11 +1482,11 @@ class UserService { Future getModeratedObjectsForCommunity( {@required Community community, - List statuses, - List types, - int count, - int maxId, - bool verified}) async { + List statuses, + List types, + int count, + int maxId, + bool verified}) async { HttpieResponse response = await _communitiesApiService.getModeratedObjects( communityName: community.name, maxId: maxId, @@ -1505,7 +1497,7 @@ class UserService { : null, statuses: statuses != null ? statuses.map((status) => - ModeratedObject.factory.convertStatusToString(status)) + ModeratedObject.factory.convertStatusToString(status)) : null, count: count); @@ -1518,7 +1510,7 @@ class UserService { {String description, ModerationCategory category}) async { HttpieResponse response = await _moderationApiService .updateModeratedObjectWithId(moderatedObject.id, - description: description, categoryId: category.id); + description: description, categoryId: category.id); _checkResponseIsCreated(response); return ModeratedObject.fromJSON(json.decode(response.body)); } @@ -1549,7 +1541,7 @@ class UserService { Future getModerationCategories() async { HttpieResponse response = - await _moderationApiService.getModerationCategories(); + await _moderationApiService.getModerationCategories(); _checkResponseIsOk(response); diff --git a/lib/widgets/fields/text_form_field.dart b/lib/widgets/fields/text_form_field.dart index 5f922c91e..019cbe56d 100644 --- a/lib/widgets/fields/text_form_field.dart +++ b/lib/widgets/fields/text_form_field.dart @@ -88,7 +88,7 @@ class OBTextFormField extends StatelessWidget { obscureText: obscureText, style: finalStyle, decoration: InputDecoration( - hintText: decoration.hintText, + hintText: decoration?.hintText, labelStyle: TextStyle( height: labelHeight, fontWeight: FontWeight.bold, @@ -98,13 +98,13 @@ class OBTextFormField extends StatelessWidget { hintStyle: TextStyle( color: themeValueParserService .parseColor(theme.primaryTextColor)), - contentPadding: decoration.contentPadding ?? + contentPadding: decoration?.contentPadding ?? EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0), border: InputBorder.none, - labelText: decoration.labelText, - prefixIcon: decoration.prefixIcon, - prefixText: decoration.prefixText, - errorMaxLines: decoration.errorMaxLines ?? 3 + labelText: decoration?.labelText, + prefixIcon: decoration?.prefixIcon, + prefixText: decoration?.prefixText, + errorMaxLines: decoration?.errorMaxLines ?? 3 ), ), hasBorder ? const OBDivider() : const SizedBox() diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index ba6d47f4e..ddc7f71f6 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -214,6 +214,7 @@ class OBIcons { static const closePost = OBIconData(nativeIcon: Icons.lock_outline); static const openPost = OBIconData(nativeIcon: Icons.lock_open); static const block = OBIconData(nativeIcon: Icons.block); + static const chevronRight = OBIconData(nativeIcon: Icons.chevron_right); static const success = OBIconData(filename: 'success-icon.png'); static const error = OBIconData(filename: 'error-icon.png'); static const warning = OBIconData(filename: 'warning-icon.png'); diff --git a/lib/widgets/tiles/actions/report_object_tile.dart b/lib/widgets/tiles/actions/report_object_tile.dart index 3314bab40..89fe1d9d8 100644 --- a/lib/widgets/tiles/actions/report_object_tile.dart +++ b/lib/widgets/tiles/actions/report_object_tile.dart @@ -1,6 +1,6 @@ import 'package:Openbook/models/user.dart'; import 'package:Openbook/provider.dart'; -import 'package:Openbook/services/modal_service.dart'; +import 'package:Openbook/services/navigation_service.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tiles/loading_tile.dart'; @@ -25,7 +25,7 @@ class OBReportUserTile extends StatefulWidget { } class OBReportUserTileState extends State { - ModalService _modalService; + NavigationService _navigationService; bool _requestInProgress; @override @@ -37,7 +37,7 @@ class OBReportUserTileState extends State { @override Widget build(BuildContext context) { var openbookProvider = OpenbookProvider.of(context); - _modalService = openbookProvider.modalService; + _navigationService = openbookProvider.navigationService; return StreamBuilder( stream: widget.user.updateSubject, @@ -51,7 +51,7 @@ class OBReportUserTileState extends State { isLoading: _requestInProgress || isReported, leading: OBIcon(OBIcons.report), title: OBText( - isReported ? 'You have reported this account' : 'Report account'), + isReported ? 'You have reported this account' : 'Report account'), onTap: isReported ? () {} : _reportUser, ); }, @@ -60,7 +60,7 @@ class OBReportUserTileState extends State { void _reportUser() { if (widget.onWantsToReportUser != null) widget.onWantsToReportUser(); - _modalService.openReportObject( + _navigationService.navigateToReportObject( context: context, object: widget.user, onObjectReported: widget.onUserReported); From ae3ba4e8412f84906405fc3076333aa61ed0c835 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Sun, 26 May 2019 16:44:04 +0200 Subject: [PATCH 20/65] :sparkles: report users --- lib/models/user.dart | 5 +++++ .../pages/report_object/pages/confirm_report_object.dart | 6 ++++++ lib/services/user.dart | 1 - 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index f605bf1b1..cac3cb9f9 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -299,6 +299,11 @@ class User extends UpdatableModel { } } + void setIsReported(isReported) { + this.isReported = isReported; + notifyUpdate(); + } + bool canDisableOrEnableCommentsForPost(Post post) { User loggedInUser = this; bool _canDisableOrEnableComments = false; diff --git a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart index bd321957f..0e929807e 100644 --- a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart +++ b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart @@ -181,6 +181,12 @@ class OBConfirmReportObjectState extends State { throw 'Object type not supported'; } await _submitReportOperation.value; + if (widget.object is User) { + widget.object.setIsReported(true); + } else if (widget.object is Community) { + widget.object.setIsReported(true); + } + Navigator.of(context).pop(true); } catch (error) { _onError(error); diff --git a/lib/services/user.dart b/lib/services/user.dart index 0624167d9..a906d7176 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -1413,7 +1413,6 @@ class UserService { Future reportUser({@required User user, String description, @required ModerationCategory moderationCategory}) async { - print(user.username); HttpieResponse response = await _authApiService.reportUserWithUsername( description: description, userUsername: user.username, From fad3be78093fabab48fd636203de6438f36c4f33 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Sun, 26 May 2019 16:52:32 +0200 Subject: [PATCH 21/65] :sparkles: report community --- lib/models/community.dart | 11 +++ .../home/bottom_sheets/community_actions.dart | 12 ++-- .../profile_action_more.dart | 2 +- lib/services/communities_api.dart | 4 +- .../tiles/actions/report_community_tile.dart | 70 +++++++++++++++++++ ...object_tile.dart => report_user_tile.dart} | 0 6 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 lib/widgets/tiles/actions/report_community_tile.dart rename lib/widgets/tiles/actions/{report_object_tile.dart => report_user_tile.dart} (100%) diff --git a/lib/models/community.dart b/lib/models/community.dart index 60866e0b7..a2a5dd175 100644 --- a/lib/models/community.dart +++ b/lib/models/community.dart @@ -90,6 +90,7 @@ class Community extends UpdatableModel { this.cover, this.isInvited, this.isCreator, + this.isReported, this.moderators, this.memberships, this.administrators, @@ -204,6 +205,10 @@ class Community extends UpdatableModel { usersAdjective = json['users_adjective']; } + if (json.containsKey('is_reported')) { + isReported = json['is_reported']; + } + if (json.containsKey('color')) { color = json['color']; } @@ -249,6 +254,11 @@ class Community extends UpdatableModel { notifyUpdate(); } } + + void setIsReported(isReported) { + this.isReported = isReported; + notifyUpdate(); + } } class CommunityFactory extends UpdatableModelFactory { @@ -267,6 +277,7 @@ class CommunityFactory extends UpdatableModelFactory { avatar: json['avatar'], isInvited: json['is_invited'], isCreator: json['is_creator'], + isReported: json['is_reported'], isFavorite: json['is_favorite'], invitesEnabled: json['invites_enabled'], cover: json['cover'], diff --git a/lib/pages/home/bottom_sheets/community_actions.dart b/lib/pages/home/bottom_sheets/community_actions.dart index e9128b43c..91c2184dd 100644 --- a/lib/pages/home/bottom_sheets/community_actions.dart +++ b/lib/pages/home/bottom_sheets/community_actions.dart @@ -8,6 +8,7 @@ import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tiles/actions/favorite_community_tile.dart'; +import 'package:Openbook/widgets/tiles/actions/report_community_tile.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -65,12 +66,11 @@ class OBCommunityActionsBottomSheetState } if (!isCommunityAdministrator && !isCommunityModerator) { - communityActions.add(ListTile( - leading: const OBIcon(OBIcons.reportCommunity), - title: const OBText( - 'Report community', - ), - onTap: _onWantsToReportCommunity, + communityActions.add(OBReportCommunityTile( + community: community, + onWantsToReportCommunity: () { + Navigator.of(context).pop(); + }, )); } diff --git a/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart b/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart index da54367b5..1487c7159 100644 --- a/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart +++ b/lib/pages/home/pages/profile/widgets/profile_card/widgets/profile_actions/widgets/profile_action_more/profile_action_more.dart @@ -10,7 +10,7 @@ import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tiles/actions/block_user_tile.dart'; -import 'package:Openbook/widgets/tiles/actions/report_object_tile.dart'; +import 'package:Openbook/widgets/tiles/actions/report_user_tile.dart'; import 'package:flutter/material.dart'; class OBProfileActionMore extends StatelessWidget { diff --git a/lib/services/communities_api.dart b/lib/services/communities_api.dart index d892cff69..219925e71 100644 --- a/lib/services/communities_api.dart +++ b/lib/services/communities_api.dart @@ -578,13 +578,13 @@ class CommunitiesApiService { String description}) { String path = _makeReportCommunityPath(communityName); - Map body = {'category_id': moderationCategoryId}; + Map body = {'category_id': moderationCategoryId.toString()}; if (description != null && description.isNotEmpty) { body['description'] = description; } - return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + return _httpService.post(_makeApiUrl(path), body: body, appendAuthorizationToken: true); } Future getModeratedObjects({ diff --git a/lib/widgets/tiles/actions/report_community_tile.dart b/lib/widgets/tiles/actions/report_community_tile.dart new file mode 100644 index 000000000..8d09d2908 --- /dev/null +++ b/lib/widgets/tiles/actions/report_community_tile.dart @@ -0,0 +1,70 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tiles/loading_tile.dart'; +import 'package:flutter/material.dart'; + +class OBReportCommunityTile extends StatefulWidget { + final Community community; + final ValueChanged onCommunityReported; + final VoidCallback onWantsToReportCommunity; + + const OBReportCommunityTile({ + Key key, + this.onCommunityReported, + @required this.community, + this.onWantsToReportCommunity, + }) : super(key: key); + + @override + OBReportCommunityTileState createState() { + return OBReportCommunityTileState(); + } +} + +class OBReportCommunityTileState extends State { + NavigationService _navigationService; + bool _requestInProgress; + + @override + void initState() { + super.initState(); + _requestInProgress = false; + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _navigationService = openbookProvider.navigationService; + + return StreamBuilder( + stream: widget.community.updateSubject, + initialData: widget.community, + builder: (BuildContext context, AsyncSnapshot snapshot) { + var community = snapshot.data; + + bool isReported = community.isReported ?? false; + + return OBLoadingTile( + isLoading: _requestInProgress || isReported, + leading: OBIcon(OBIcons.report), + title: OBText(isReported + ? 'You have reported this community' + : 'Report community'), + onTap: isReported ? () {} : _reportCommunity, + ); + }, + ); + } + + void _reportCommunity() { + if (widget.onWantsToReportCommunity != null) + widget.onWantsToReportCommunity(); + _navigationService.navigateToReportObject( + context: context, + object: widget.community, + onObjectReported: widget.onCommunityReported); + } +} diff --git a/lib/widgets/tiles/actions/report_object_tile.dart b/lib/widgets/tiles/actions/report_user_tile.dart similarity index 100% rename from lib/widgets/tiles/actions/report_object_tile.dart rename to lib/widgets/tiles/actions/report_user_tile.dart From 614b6b1b647c1c8d510e067569314866f9e8bd89 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Sun, 26 May 2019 18:00:19 +0200 Subject: [PATCH 22/65] :sparkles: report post comment and post --- lib/models/post.dart | 10 + lib/models/post_comment.dart | 29 +- lib/models/user.dart | 7 + .../home/bottom_sheets/post_actions.dart | 70 ++- .../widgets/post_comment/post_comment.dart | 21 + .../pages/confirm_report_object.dart | 13 +- .../pages/report_object/report_object.dart | 15 +- lib/services/navigation_service.dart | 5 +- lib/services/posts_api.dart | 14 +- lib/services/user.dart | 450 +++++++++--------- .../actions/report_post_comment_tile.dart | 69 +++ .../tiles/actions/report_post_tile.dart | 68 +++ 12 files changed, 488 insertions(+), 283 deletions(-) create mode 100644 lib/widgets/tiles/actions/report_post_comment_tile.dart create mode 100644 lib/widgets/tiles/actions/report_post_tile.dart diff --git a/lib/models/post.dart b/lib/models/post.dart index 1a02dbd2a..27f91adc8 100644 --- a/lib/models/post.dart +++ b/lib/models/post.dart @@ -38,6 +38,7 @@ class Post extends UpdatableModel { bool isEncircled; bool isEdited; bool isClosed; + bool isReported; static final factory = PostFactory(); @@ -70,6 +71,7 @@ class Post extends UpdatableModel { this.isMuted, this.isEncircled, this.isClosed, + this.isReported, this.isEdited}) : super(); @@ -102,6 +104,8 @@ class Post extends UpdatableModel { if (json.containsKey('is_closed')) isClosed = json['is_closed']; + if (json.containsKey('is_reported')) isReported = json['is_reported']; + if (json.containsKey('image')) image = factory.parseImage(json['image']); if (json.containsKey('video')) video = factory.parseVideo(json['video']); @@ -277,6 +281,11 @@ class Post extends UpdatableModel { this.notifyUpdate(); } + void setIsReported(isReported) { + this.isReported = isReported; + notifyUpdate(); + } + void _setReactionsEmojiCounts(PostReactionsEmojiCountList emojiCounts) { reactionsEmojiCounts = emojiCounts; } @@ -299,6 +308,7 @@ class PostFactory extends UpdatableModelFactory { reactionsCount: json['reactions_count'], commentsCount: json['comments_count'], isMuted: json['is_muted'], + isReported: json['is_reported'], areCommentsEnabled: json['comments_enabled'], publicReactions: json['public_reactions'], creator: parseCreator(json['creator']), diff --git a/lib/models/post_comment.dart b/lib/models/post_comment.dart index 76d751efd..03c4dc34b 100644 --- a/lib/models/post_comment.dart +++ b/lib/models/post_comment.dart @@ -12,6 +12,7 @@ class PostComment extends UpdatableModel { User commenter; Post post; bool isEdited; + bool isReported; static convertPostCommentSortTypeToString(PostCommentsSortType type) { String result; @@ -45,14 +46,16 @@ class PostComment extends UpdatableModel { factory.clearCache(); } - PostComment( - {this.id, - this.created, - this.text, - this.creatorId, - this.commenter, - this.post, - this.isEdited}); + PostComment({ + this.id, + this.created, + this.text, + this.creatorId, + this.commenter, + this.post, + this.isEdited, + this.isReported, + }); static final factory = PostCommentFactory(); @@ -74,6 +77,10 @@ class PostComment extends UpdatableModel { isEdited = json['is_edited']; } + if (json.containsKey('is_reported')) { + isReported = json['is_reported']; + } + if (json.containsKey('text')) { text = json['text']; } @@ -110,6 +117,11 @@ class PostComment extends UpdatableModel { int getPostCreatorId() { return post.getCreatorId(); } + + void setIsReported(isReported) { + this.isReported = isReported; + notifyUpdate(); + } } class PostCommentFactory extends UpdatableModelFactory { @@ -126,6 +138,7 @@ class PostCommentFactory extends UpdatableModelFactory { commenter: parseUser(json['commenter']), post: parsePost(json['post']), isEdited: json['is_edited'], + isReported: json['is_reported'], text: json['text']); } diff --git a/lib/models/user.dart b/lib/models/user.dart index cac3cb9f9..f6d320911 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -442,6 +442,13 @@ class User extends UpdatableModel { return loggedInUser.id == postCommenter.id; } + bool canReportPostComment(PostComment postComment) { + User loggedInUser = this; + User postCommenter = postComment.commenter; + + return loggedInUser.id != postCommenter.id; + } + bool canDeletePostComment(Post post, PostComment postComment) { User loggedInUser = this; User postCommenter = postComment.commenter; diff --git a/lib/pages/home/bottom_sheets/post_actions.dart b/lib/pages/home/bottom_sheets/post_actions.dart index 639d32c76..278cf6fc4 100644 --- a/lib/pages/home/bottom_sheets/post_actions.dart +++ b/lib/pages/home/bottom_sheets/post_actions.dart @@ -13,6 +13,7 @@ import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tiles/actions/close_post_tile.dart'; import 'package:Openbook/widgets/tiles/actions/disable_comments_post_tile.dart'; import 'package:Openbook/widgets/tiles/actions/mute_post_tile.dart'; +import 'package:Openbook/widgets/tiles/actions/report_post_tile.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -69,42 +70,38 @@ class OBPostActionsBottomSheetState extends State { )); } + if (loggedInUser.canCloseOrOpenPost(post)) { + postActions.add(OBClosePostTile( + post: post, + onClosePost: _dismiss, + onOpenPost: _dismiss, + )); + } - if (loggedInUser.canCloseOrOpenPost(post)) { - postActions.add(OBClosePostTile( - post: post, - onClosePost: _dismiss, - onOpenPost: _dismiss, - )); - } - - if (loggedInUser.canEditPost(post)) { - postActions.add(ListTile( - leading: const OBIcon(OBIcons.editPost), - title: const OBText( - 'Edit post', - ), - onTap: _onWantsToEditPost, - )); - } + if (loggedInUser.canEditPost(post)) { + postActions.add(ListTile( + leading: const OBIcon(OBIcons.editPost), + title: const OBText( + 'Edit post', + ), + onTap: _onWantsToEditPost, + )); + } - if (loggedInUser.canDeletePost(post)) { - postActions.add(ListTile( - leading: const OBIcon(OBIcons.deletePost), - title: const OBText( - 'Delete post', - ), - onTap: _onWantsToDeletePost, - )); - } else { - postActions.add(ListTile( - leading: const OBIcon(OBIcons.report), - title: const OBText( - 'Report post', - ), - onTap: _onWantsToReportPost, - )); - } + if (loggedInUser.canDeletePost(post)) { + postActions.add(ListTile( + leading: const OBIcon(OBIcons.deletePost), + title: const OBText( + 'Delete post', + ), + onTap: _onWantsToDeletePost, + )); + } else { + postActions.add(OBReportPostTile( + post: widget.post, + onWantsToReportPost: _dismiss, + )); + } return OBPrimaryColorContainer( mainAxisSize: MainAxisSize.min, @@ -149,11 +146,6 @@ class OBPostActionsBottomSheetState extends State { } } - void _onWantsToReportPost() async { - _toastService.error(message: 'Not implemented yet', context: context); - _dismiss(); - } - void _dismiss() { Navigator.pop(context); } diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart index b708f1c35..cec369484 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart @@ -134,6 +134,20 @@ class OBPostCommentState extends State { List _editCommentActions = []; User loggedInUser = _userService.getLoggedInUser(); + if (loggedInUser.canReportPostComment(widget.postComment)) { + _editCommentActions.add( + Opacity( + opacity: widget.postComment.isReported ? 0.5 : 1, + child: IconSlideAction( + caption: widget.postComment.isReported ? 'Reported' : 'Report', + color: Colors.red, + icon: Icons.report, + onTap: _reportPostComment, + ), + ), + ); + } + if (loggedInUser.canEditPostComment(widget.postComment)) { _editCommentActions.add( new IconSlideAction( @@ -169,6 +183,13 @@ class OBPostCommentState extends State { context: context, post: widget.post, postComment: widget.postComment); } + void _reportPostComment() async { + await _navigationService.navigateToReportObject( + context: context, + object: widget.postComment, + extraData: {'post': widget.post}); + } + void _deletePostComment() async { if (_requestInProgress) return; _setRequestInProgress(true); diff --git a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart index 0e929807e..532e7ce92 100644 --- a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart +++ b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart @@ -1,4 +1,3 @@ -import 'package:Openbook/libs/type_to_str.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/post.dart'; @@ -22,10 +21,11 @@ import 'package:flutter/material.dart'; class OBConfirmReportObject extends StatefulWidget { final dynamic object; + final Map extraData; final ModerationCategory category; const OBConfirmReportObject( - {Key key, @required this.object, @required this.category}) + {Key key, @required this.object, @required this.category, this.extraData}) : super(key: key); @override @@ -163,6 +163,7 @@ class OBConfirmReportObjectState extends State { _submitReportOperation = CancelableOperation.fromFuture( _userService.reportPostComment( description: _descriptionController.text, + post: widget.extraData['post'], postComment: widget.object, moderationCategory: widget.category)); } else if (widget.object is Community) { @@ -181,12 +182,12 @@ class OBConfirmReportObjectState extends State { throw 'Object type not supported'; } await _submitReportOperation.value; - if (widget.object is User) { - widget.object.setIsReported(true); - } else if (widget.object is Community) { + if (widget.object is User || + widget.object is Community || + widget.object is Post || + widget.object is PostComment) { widget.object.setIsReported(true); } - Navigator.of(context).pop(true); } catch (error) { _onError(error); diff --git a/lib/pages/home/pages/report_object/report_object.dart b/lib/pages/home/pages/report_object/report_object.dart index 4de949753..4d5ed1d72 100644 --- a/lib/pages/home/pages/report_object/report_object.dart +++ b/lib/pages/home/pages/report_object/report_object.dart @@ -18,11 +18,13 @@ import 'package:flutter/material.dart'; class OBReportObjectPage extends StatefulWidget { final dynamic object; final OnObjectReported onObjectReported; + final Map extraData; const OBReportObjectPage({ Key key, this.object, this.onObjectReported, + this.extraData, }) : super(key: key); @override @@ -32,12 +34,9 @@ class OBReportObjectPage extends StatefulWidget { } class OBReportObjectPageState extends State { - ThemeService _themeService; NavigationService _navigationService; UserService _userService; - ThemeValueParserService _themeValueParserService; List _moderationCategories = []; - ModerationCategory _pickedModerationCategory; bool _needsBootstrap; @override @@ -50,10 +49,8 @@ class OBReportObjectPageState extends State { Widget build(BuildContext context) { if (_needsBootstrap) { var openbookProvider = OpenbookProvider.of(context); - _themeService = openbookProvider.themeService; _userService = openbookProvider.userService; _navigationService = openbookProvider.navigationService; - _themeValueParserService = openbookProvider.themeValueParserService; _bootstrap(); _needsBootstrap = false; } @@ -109,9 +106,13 @@ class OBReportObjectPageState extends State { return ListTile( onTap: () async { var result = await _navigationService.navigateToConfirmReportObject( - object: widget.object, category: category, context: context); + extraData: widget.extraData, + object: widget.object, + category: category, + context: context); if (result != null && result) { - if(widget.onObjectReported != null) widget.onObjectReported(widget.object); + if (widget.onObjectReported != null) + widget.onObjectReported(widget.object); Navigator.pop(context); } }, diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 0837811fb..0fcccbe31 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -470,21 +470,23 @@ class NavigationService { Future navigateToConfirmReportObject( {@required BuildContext context, @required dynamic object, + Map extraData, @required ModerationCategory category}) { return Navigator.push( context, OBSlideRightRoute( key: Key('obConfirmReportObject'), widget: OBConfirmReportObject( + extraData: extraData, object: object, category: category, ))); } - Future navigateToReportObject( {@required BuildContext context, @required dynamic object, + Map extraData, ValueChanged onObjectReported}) async { return Navigator.push( context, @@ -492,6 +494,7 @@ class NavigationService { key: Key('obReportObject'), widget: OBReportObjectPage( object: object, + extraData: extraData, onObjectReported: onObjectReported, ))); } diff --git a/lib/services/posts_api.dart b/lib/services/posts_api.dart index 256fce52b..ef1253f13 100644 --- a/lib/services/posts_api.dart +++ b/lib/services/posts_api.dart @@ -265,13 +265,16 @@ class PostsApiService { String path = _makeReportPostCommentPath( postCommentId: postCommentId, postUuid: postUuid); - Map body = {'category_id': moderationCategoryId}; + Map body = { + 'category_id': moderationCategoryId.toString() + }; if (description != null && description.isNotEmpty) { body['description'] = description; } - return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + return _httpService.post(_makeApiUrl(path), + body: body, appendAuthorizationToken: true); } Future reportPost( @@ -280,13 +283,16 @@ class PostsApiService { String description}) { String path = _makeReportPostPath(postUuid: postUuid); - Map body = {'category_id': moderationCategoryId}; + Map body = { + 'category_id': moderationCategoryId.toString() + }; if (description != null && description.isNotEmpty) { body['description'] = description; } - return _httpService.post(_makeApiUrl(path), appendAuthorizationToken: true); + return _httpService.post(_makeApiUrl(path), + body: body, appendAuthorizationToken: true); } String _makePostPath(String postUuid) { diff --git a/lib/services/user.dart b/lib/services/user.dart index a906d7176..b859d4b43 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -161,7 +161,7 @@ class UserService { Future deleteAccountWithPassword(String password) async { HttpieResponse response = - await _authApiService.deleteUser(password: password); + await _authApiService.deleteUser(password: password); _checkResponseIsOk(response); } @@ -207,8 +207,9 @@ class UserService { _checkResponseIsOk(response); } - Future verifyPasswordReset({@required String newPassword, - @required String passwordResetToken}) async { + Future verifyPasswordReset( + {@required String newPassword, + @required String passwordResetToken}) async { HttpieResponse response = await _authApiService.verifyPasswordReset( newPassword: newPassword, passwordResetToken: passwordResetToken); _checkResponseIsOk(response); @@ -236,7 +237,7 @@ class UserService { if (_authToken == null) throw AuthTokenMissingError(); HttpieResponse response = - await _authApiService.getUserWithAuthToken(_authToken); + await _authApiService.getUserWithAuthToken(_authToken); _checkResponseIsOk(response); var userData = response.body; return _setUserWithData(userData); @@ -244,14 +245,14 @@ class UserService { Future updateUserEmail(String email) async { HttpieStreamedResponse response = - await _authApiService.updateUserEmail(email: email); + await _authApiService.updateUserEmail(email: email); _checkResponseIsOk(response); String userData = await response.readAsString(); return _makeLoggedInUser(userData); } - Future updateUserPassword(String currentPassword, - String newPassword) async { + Future updateUserPassword( + String currentPassword, String newPassword) async { HttpieStreamedResponse response = await _authApiService.updateUserPassword( currentPassword: currentPassword, newPassword: newPassword); _checkResponseIsOk(response); @@ -321,20 +322,21 @@ class UserService { Future getTrendingPosts() async { HttpieResponse response = - await _postsApiService.getTrendingPosts(authenticatedRequest: true); + await _postsApiService.getTrendingPosts(authenticatedRequest: true); _checkResponseIsOk(response); return PostsList.fromJson(json.decode(response.body)); } - Future getTimelinePosts({List circles = const [], - List followsLists = const [], - int maxId, - int count, - String username, - bool areFirstPosts = false, - bool cachePosts = false}) async { + Future getTimelinePosts( + {List circles = const [], + List followsLists = const [], + int maxId, + int count, + String username, + bool areFirstPosts = false, + bool cachePosts = false}) async { HttpieResponse response = await _postsApiService.getTimelinePosts( circleIds: circles.map((circle) => circle.id).toList(), listIds: followsLists.map((followsList) => followsList.id).toList(), @@ -359,10 +361,11 @@ class UserService { return PostsList(); } - Future createPost({String text, - List circles = const [], - File image, - File video}) async { + Future createPost( + {String text, + List circles = const [], + File image, + File video}) async { HttpieStreamedResponse response = await _postsApiService.createPost( text: text, circleIds: circles.map((circle) => circle.id).toList(), @@ -380,7 +383,7 @@ class UserService { Future editPost({String postUuid, String text}) async { HttpieStreamedResponse response = - await _postsApiService.editPost(postUuid: postUuid, text: text); + await _postsApiService.editPost(postUuid: postUuid, text: text); _checkResponseIsOk(response); @@ -390,34 +393,34 @@ class UserService { Future deletePost(Post post) async { HttpieResponse response = - await _postsApiService.deletePostWithUuid(post.uuid); + await _postsApiService.deletePostWithUuid(post.uuid); _checkResponseIsOk(response); } Future disableCommentsForPost(Post post) async { HttpieResponse response = - await _postsApiService.disableCommentsForPostWithUuidPost(post.uuid); + await _postsApiService.disableCommentsForPostWithUuidPost(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future enableCommentsForPost(Post post) async { HttpieResponse response = - await _postsApiService.enableCommentsForPostWithUuidPost(post.uuid); + await _postsApiService.enableCommentsForPostWithUuidPost(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future closePost(Post post) async { HttpieResponse response = - await _postsApiService.closePostWithUuid(post.uuid); + await _postsApiService.closePostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future openPost(Post post) async { HttpieResponse response = - await _postsApiService.openPostWithUuid(post.uuid); + await _postsApiService.openPostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } @@ -428,9 +431,10 @@ class UserService { return Post.fromJson(json.decode(response.body)); } - Future reactToPost({@required Post post, - @required Emoji emoji, - @required EmojiGroup emojiGroup}) async { + Future reactToPost( + {@required Post post, + @required Emoji emoji, + @required EmojiGroup emojiGroup}) async { HttpieResponse response = await _postsApiService.reactToPost( postUuid: post.uuid, emojiId: emoji.id, emojiGroupId: emojiGroup.id); _checkResponseIsCreated(response); @@ -447,8 +451,8 @@ class UserService { Future getReactionsForPost(Post post, {int count, int maxId, Emoji emoji}) async { HttpieResponse response = - await _postsApiService.getReactionsForPostWithUuid(post.uuid, - count: count, maxId: maxId, emojiId: emoji.id); + await _postsApiService.getReactionsForPostWithUuid(post.uuid, + count: count, maxId: maxId, emojiId: emoji.id); _checkResponseIsOk(response); @@ -458,7 +462,7 @@ class UserService { Future getReactionsEmojiCountForPost( Post post) async { HttpieResponse response = - await _postsApiService.getReactionsEmojiCountForPostWithUuid(post.uuid); + await _postsApiService.getReactionsEmojiCountForPostWithUuid(post.uuid); _checkResponseIsOk(response); @@ -468,14 +472,15 @@ class UserService { Future commentPost( {@required Post post, @required String text}) async { HttpieResponse response = - await _postsApiService.commentPost(postUuid: post.uuid, text: text); + await _postsApiService.commentPost(postUuid: post.uuid, text: text); _checkResponseIsCreated(response); return PostComment.fromJSON(json.decode(response.body)); } - Future editPostComment({@required Post post, - @required PostComment postComment, - @required String text}) async { + Future editPostComment( + {@required Post post, + @required PostComment postComment, + @required String text}) async { HttpieResponse response = await _postsApiService.editPostComment( postUuid: post.uuid, postCommentId: postComment.id, text: text); _checkResponseIsOk(response); @@ -491,24 +496,24 @@ class UserService { Future mutePost(Post post) async { HttpieResponse response = - await _postsApiService.mutePostWithUuid(post.uuid); + await _postsApiService.mutePostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future unmutePost(Post post) async { HttpieResponse response = - await _postsApiService.unmutePostWithUuid(post.uuid); + await _postsApiService.unmutePostWithUuid(post.uuid); _checkResponseIsOk(response); return Post.fromJson(json.decode(response.body)); } Future getCommentsForPost(Post post, {int maxId, - int countMax, - int minId, - int countMin, - PostCommentsSortType sort}) async { + int countMax, + int minId, + int countMin, + PostCommentsSortType sort}) async { HttpieResponse response = await _postsApiService.getCommentsForPostWithUuid( post.uuid, countMax: countMax, @@ -533,7 +538,7 @@ class UserService { Future getReactionEmojiGroups() async { HttpieResponse response = - await this._postsApiService.getReactionEmojiGroups(); + await this._postsApiService.getReactionEmojiGroups(); _checkResponseIsOk(response); @@ -562,10 +567,11 @@ class UserService { return UsersList.fromJson(json.decode(response.body)); } - Future getLinkedUsers({bool authenticatedRequest = true, - int maxId, - int count, - Community withCommunity}) async { + Future getLinkedUsers( + {bool authenticatedRequest = true, + int maxId, + int count, + Community withCommunity}) async { HttpieResponse response = await _authApiService.getLinkedUsers( count: count, withCommunity: withCommunity.name, maxId: maxId); _checkResponseIsOk(response); @@ -574,14 +580,14 @@ class UserService { Future blockUser(User user) async { HttpieResponse response = - await _authApiService.blockUserWithUsername(user.username); + await _authApiService.blockUserWithUsername(user.username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } Future unblockUser(User user) async { HttpieResponse response = - await _authApiService.unblockUserWithUsername(user.username); + await _authApiService.unblockUserWithUsername(user.username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } @@ -589,31 +595,32 @@ class UserService { Future searchBlockedUsers( {@required String query, int count}) async { HttpieResponse response = - await _authApiService.searchBlockedUsers(query: query, count: count); + await _authApiService.searchBlockedUsers(query: query, count: count); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } Future getBlockedUsers({int maxId, int count}) async { HttpieResponse response = - await _authApiService.getBlockedUsers(count: count, maxId: maxId); + await _authApiService.getBlockedUsers(count: count, maxId: maxId); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } Future searchFollowers({@required String query, int count}) async { HttpieResponse response = - await _authApiService.searchFollowers(query: query, count: count); + await _authApiService.searchFollowers(query: query, count: count); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } - Future getFollowers({bool authenticatedRequest = true, - int maxId, - int count, - Community withCommunity}) async { + Future getFollowers( + {bool authenticatedRequest = true, + int maxId, + int count, + Community withCommunity}) async { HttpieResponse response = - await _authApiService.getFollowers(count: count, maxId: maxId); + await _authApiService.getFollowers(count: count, maxId: maxId); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } @@ -621,17 +628,18 @@ class UserService { Future searchFollowings( {@required String query, int count, Community withCommunity}) async { HttpieResponse response = - await _authApiService.searchFollowings(query: query, count: count); + await _authApiService.searchFollowings(query: query, count: count); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } - Future getFollowings({bool authenticatedRequest = true, - int maxId, - int count, - Community withCommunity}) async { + Future getFollowings( + {bool authenticatedRequest = true, + int maxId, + int count, + Community withCommunity}) async { HttpieResponse response = - await _authApiService.getFollowings(count: count, maxId: maxId); + await _authApiService.getFollowings(count: count, maxId: maxId); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } @@ -647,7 +655,7 @@ class UserService { Future unFollowUserWithUsername(String username) async { HttpieResponse response = - await _followsApiService.unFollowUserWithUsername(username); + await _followsApiService.unFollowUserWithUsername(username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } @@ -664,8 +672,8 @@ class UserService { Future connectWithUserWithUsername(String username, {List circles = const []}) async { HttpieResponse response = - await _connectionsApiService.connectWithUserWithUsername(username, - circlesIds: circles.map((circle) => circle.id).toList()); + await _connectionsApiService.connectWithUserWithUsername(username, + circlesIds: circles.map((circle) => circle.id).toList()); _checkResponseIsCreated(response); return Connection.fromJson(json.decode(response.body)); } @@ -674,14 +682,14 @@ class UserService { {List circles = const []}) async { HttpieResponse response = await _connectionsApiService .confirmConnectionWithUserWithUsername(username, - circlesIds: circles.map((circle) => circle.id).toList()); + circlesIds: circles.map((circle) => circle.id).toList()); _checkResponseIsOk(response); return Connection.fromJson(json.decode(response.body)); } Future disconnectFromUserWithUsername(String username) async { HttpieResponse response = - await _connectionsApiService.disconnectFromUserWithUsername(username); + await _connectionsApiService.disconnectFromUserWithUsername(username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } @@ -689,15 +697,15 @@ class UserService { Future updateConnectionWithUsername(String username, {List circles = const []}) async { HttpieResponse response = - await _connectionsApiService.updateConnectionWithUsername(username, - circlesIds: circles.map((circle) => circle.id).toList()); + await _connectionsApiService.updateConnectionWithUsername(username, + circlesIds: circles.map((circle) => circle.id).toList()); _checkResponseIsOk(response); return Connection.fromJson(json.decode(response.body)); } Future getConnectionsCircleWithId(int circleId) async { HttpieResponse response = - await _connectionsCirclesApiService.getCircleWithId(circleId); + await _connectionsCirclesApiService.getCircleWithId(circleId); _checkResponseIsOk(response); return Circle.fromJSON(json.decode(response.body)); } @@ -719,17 +727,17 @@ class UserService { Future updateConnectionsCircle(Circle circle, {String name, String color, List users = const []}) async { HttpieResponse response = - await _connectionsCirclesApiService.updateCircleWithId(circle.id, - name: name, - color: color, - usernames: users.map((user) => user.username).toList()); + await _connectionsCirclesApiService.updateCircleWithId(circle.id, + name: name, + color: color, + usernames: users.map((user) => user.username).toList()); _checkResponseIsOk(response); return Circle.fromJSON(json.decode(response.body)); } Future deleteConnectionsCircle(Circle circle) async { HttpieResponse response = - await _connectionsCirclesApiService.deleteCircleWithId(circle.id); + await _connectionsCirclesApiService.deleteCircleWithId(circle.id); _checkResponseIsOk(response); } @@ -742,7 +750,7 @@ class UserService { Future createFollowsList( {@required String name, Emoji emoji}) async { HttpieResponse response = - await _followsListsApiService.createList(name: name, emojiId: emoji.id); + await _followsListsApiService.createList(name: name, emojiId: emoji.id); _checkResponseIsCreated(response); return FollowsList.fromJSON(json.decode(response.body)); } @@ -760,20 +768,20 @@ class UserService { Future deleteFollowsList(FollowsList list) async { HttpieResponse response = - await _followsListsApiService.deleteListWithId(list.id); + await _followsListsApiService.deleteListWithId(list.id); _checkResponseIsOk(response); } Future getFollowsListWithId(int listId) async { HttpieResponse response = - await _followsListsApiService.getListWithId(listId); + await _followsListsApiService.getListWithId(listId); _checkResponseIsOk(response); return FollowsList.fromJSON(json.decode(response.body)); } Future createUserInvite({String nickname}) async { HttpieStreamedResponse response = - await _userInvitesApiService.createUserInvite(nickname: nickname); + await _userInvitesApiService.createUserInvite(nickname: nickname); _checkResponseIsCreated(response); String responseBody = await response.readAsString(); @@ -795,7 +803,7 @@ class UserService { bool isPending = status != null ? UserInvite.convertUserInviteStatusToBool(status) : UserInvite.convertUserInviteStatusToBool( - UserInviteFilterByStatus.all); + UserInviteFilterByStatus.all); HttpieResponse response = await _userInvitesApiService.getUserInvites( isStatusPending: isPending, count: count, offset: offset); @@ -808,7 +816,7 @@ class UserService { bool isPending = status != null ? UserInvite.convertUserInviteStatusToBool(status) : UserInvite.convertUserInviteStatusToBool( - UserInviteFilterByStatus.all); + UserInviteFilterByStatus.all); HttpieResponse response = await _userInvitesApiService.searchUserInvites( isStatusPending: isPending, count: count, query: query); @@ -818,7 +826,7 @@ class UserService { Future deleteUserInvite(UserInvite userInvite) async { HttpieResponse response = - await _userInvitesApiService.deleteUserInvite(userInvite.id); + await _userInvitesApiService.deleteUserInvite(userInvite.id); _checkResponseIsOk(response); } @@ -840,7 +848,7 @@ class UserService { {String text, File image, File video}) async { HttpieStreamedResponse response = await _communitiesApiService .createPostForCommunityWithId(community.name, - text: text, image: image, video: video); + text: text, image: image, video: video); _checkResponseIsCreated(response); String responseBody = await response.readAsString(); @@ -852,7 +860,7 @@ class UserService { {int maxId, int count}) async { HttpieResponse response = await _communitiesApiService .getPostsForCommunityWithName(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); return PostsList.fromJson(json.decode(response.body)); } @@ -861,44 +869,45 @@ class UserService { {int maxId, int count}) async { HttpieResponse response = await _communitiesApiService .getClosedPostsForCommunityWithName(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); return PostsList.fromJson(json.decode(response.body)); } Future getCommunitiesWithQuery(String query) async { HttpieResponse response = - await _communitiesApiService.getCommunitiesWithQuery(query: query); + await _communitiesApiService.getCommunitiesWithQuery(query: query); _checkResponseIsOk(response); return CommunitiesList.fromJson(json.decode(response.body)); } - Future createCommunity({@required String name, - @required String title, - @required List categories, - @required CommunityType type, - String color, - String userAdjective, - String usersAdjective, - bool invitesEnabled, - String description, - String rules, - File cover, - File avatar}) async { + Future createCommunity( + {@required String name, + @required String title, + @required List categories, + @required CommunityType type, + String color, + String userAdjective, + String usersAdjective, + bool invitesEnabled, + String description, + String rules, + File cover, + File avatar}) async { HttpieStreamedResponse response = - await _communitiesApiService.createCommunity( - name: name, - title: title, - categories: categories.map((category) => category.name).toList(), - type: Community.convertTypeToString(type), - color: color, - userAdjective: userAdjective, - usersAdjective: usersAdjective, - invitesEnabled: invitesEnabled, - description: description, - rules: rules, - cover: cover, - avatar: avatar); + await _communitiesApiService.createCommunity( + name: name, + title: title, + categories: categories.map((category) => category.name).toList(), + type: Community.convertTypeToString(type), + color: color, + userAdjective: userAdjective, + usersAdjective: usersAdjective, + invitesEnabled: invitesEnabled, + description: description, + rules: rules, + cover: cover, + avatar: avatar); _checkResponseIsCreated(response); @@ -909,19 +918,19 @@ class UserService { Future updateCommunity(Community community, {String name, - String title, - List categories, - CommunityType type, - String color, - String userAdjective, - String usersAdjective, - String description, - bool invitesEnabled, - String rules, - File cover, - File avatar}) async { + String title, + List categories, + CommunityType type, + String color, + String userAdjective, + String usersAdjective, + String description, + bool invitesEnabled, + String rules, + File cover, + File avatar}) async { HttpieStreamedResponse response = - await _communitiesApiService.updateCommunityWithName( + await _communitiesApiService.updateCommunityWithName( community.name, name: name, title: title, @@ -990,14 +999,14 @@ class UserService { Future getCommunityWithName(String name) async { HttpieResponse response = - await _communitiesApiService.getCommunityWithName(name); + await _communitiesApiService.getCommunityWithName(name); _checkResponseIsOk(response); return Community.fromJSON(json.decode(response.body)); } Future deleteCommunity(Community community) async { HttpieResponse response = - await _communitiesApiService.deleteCommunityWithName(community.name); + await _communitiesApiService.deleteCommunityWithName(community.name); _checkResponseIsOk(response); } @@ -1005,30 +1014,31 @@ class UserService { {int count, int maxId, List exclude}) async { HttpieResponse response = await _communitiesApiService .getMembersForCommunityWithId(community.name, - count: count, - maxId: maxId, - exclude: exclude != null - ? exclude - .map((exclude) => - Community.convertExclusionToString(exclude)) - .toList() - : null); + count: count, + maxId: maxId, + exclude: exclude != null + ? exclude + .map((exclude) => + Community.convertExclusionToString(exclude)) + .toList() + : null); _checkResponseIsOk(response); return UsersList.fromJson(json.decode(response.body)); } - Future searchCommunityMembers({@required Community community, - @required String query, - List exclude}) async { + Future searchCommunityMembers( + {@required Community community, + @required String query, + List exclude}) async { HttpieResponse response = await _communitiesApiService.searchMembers( communityName: community.name, query: query, exclude: exclude != null ? exclude - .map((exclude) => Community.convertExclusionToString(exclude)) - .toList() + .map((exclude) => Community.convertExclusionToString(exclude)) + .toList() : null, ); @@ -1040,8 +1050,8 @@ class UserService { Future inviteUserToCommunity( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.inviteUserToCommunity( - communityName: community.name, username: user.username); + await _communitiesApiService.inviteUserToCommunity( + communityName: community.name, username: user.username); _checkResponseIsCreated(response); return User.fromJson(json.decode(response.body)); } @@ -1049,15 +1059,15 @@ class UserService { Future uninviteUserFromCommunity( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.uninviteUserFromCommunity( - communityName: community.name, username: user.username); + await _communitiesApiService.uninviteUserFromCommunity( + communityName: community.name, username: user.username); _checkResponseIsOk(response); return User.fromJson(json.decode(response.body)); } Future getJoinedCommunities({int offset}) async { HttpieResponse response = - await _communitiesApiService.getJoinedCommunities(offset: offset); + await _communitiesApiService.getJoinedCommunities(offset: offset); _checkResponseIsOk(response); @@ -1074,14 +1084,14 @@ class UserService { Future joinCommunity(Community community) async { HttpieResponse response = - await _communitiesApiService.joinCommunityWithId(community.name); + await _communitiesApiService.joinCommunityWithId(community.name); _checkResponseIsCreated(response); return Community.fromJSON(json.decode(response.body)); } Future leaveCommunity(Community community) async { HttpieResponse response = - await _communitiesApiService.leaveCommunityWithId(community.name); + await _communitiesApiService.leaveCommunityWithId(community.name); _checkResponseIsOk(response); return Community.fromJSON(json.decode(response.body)); } @@ -1090,7 +1100,7 @@ class UserService { {int count, int maxId}) async { HttpieResponse response = await _communitiesApiService .getModeratorsForCommunityWithId(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); @@ -1114,16 +1124,16 @@ class UserService { Future addCommunityModerator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.addCommunityModerator( - communityName: community.name, username: user.username); + await _communitiesApiService.addCommunityModerator( + communityName: community.name, username: user.username); _checkResponseIsCreated(response); } Future removeCommunityModerator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.removeCommunityModerator( - communityName: community.name, username: user.username); + await _communitiesApiService.removeCommunityModerator( + communityName: community.name, username: user.username); _checkResponseIsOk(response); } @@ -1131,7 +1141,7 @@ class UserService { {int count, int maxId}) async { HttpieResponse response = await _communitiesApiService .getAdministratorsForCommunityWithName(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); @@ -1155,8 +1165,8 @@ class UserService { Future addCommunityAdministrator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.addCommunityAdministrator( - communityName: community.name, username: user.username); + await _communitiesApiService.addCommunityAdministrator( + communityName: community.name, username: user.username); _checkResponseIsCreated(response); return Community.fromJSON(json.decode(response.body)); } @@ -1164,8 +1174,8 @@ class UserService { Future removeCommunityAdministrator( {@required Community community, @required User user}) async { HttpieResponse response = - await _communitiesApiService.removeCommunityAdministrator( - communityName: community.name, username: user.username); + await _communitiesApiService.removeCommunityAdministrator( + communityName: community.name, username: user.username); _checkResponseIsOk(response); } @@ -1173,7 +1183,7 @@ class UserService { {int count, int maxId}) async { HttpieResponse response = await _communitiesApiService .getBannedUsersForCommunityWithId(community.name, - count: count, maxId: maxId); + count: count, maxId: maxId); _checkResponseIsOk(response); @@ -1210,7 +1220,7 @@ class UserService { Future getFavoriteCommunities({int offset}) async { HttpieResponse response = - await _communitiesApiService.getFavoriteCommunities(offset: offset); + await _communitiesApiService.getFavoriteCommunities(offset: offset); _checkResponseIsOk(response); @@ -1242,7 +1252,7 @@ class UserService { Future getModeratedCommunities({int offset}) async { HttpieResponse response = - await _communitiesApiService.getModeratedCommunities(offset: offset); + await _communitiesApiService.getModeratedCommunities(offset: offset); _checkResponseIsOk(response); @@ -1264,20 +1274,20 @@ class UserService { Future getNotificationWithId(int notificationId) async { HttpieResponse response = - await _notificationsApiService.getNotificationWithId(notificationId); + await _notificationsApiService.getNotificationWithId(notificationId); _checkResponseIsOk(response); return OBNotification.fromJSON(json.decode(response.body)); } Future readNotifications({int maxId}) async { HttpieResponse response = - await _notificationsApiService.readNotifications(maxId: maxId); + await _notificationsApiService.readNotifications(maxId: maxId); _checkResponseIsOk(response); } Future deleteNotifications() async { HttpieResponse response = - await _notificationsApiService.deleteNotifications(); + await _notificationsApiService.deleteNotifications(); _checkResponseIsOk(response); } @@ -1289,7 +1299,7 @@ class UserService { Future readNotification(OBNotification notification) async { HttpieResponse response = - await _notificationsApiService.readNotificationWithId(notification.id); + await _notificationsApiService.readNotificationWithId(notification.id); _checkResponseIsOk(response); } @@ -1306,7 +1316,7 @@ class UserService { Future createDevice({@required String uuid, String name}) async { HttpieResponse response = - await _devicesApiService.createDevice(uuid: uuid, name: name); + await _devicesApiService.createDevice(uuid: uuid, name: name); _checkResponseIsCreated(response); return Device.fromJSON(json.decode(response.body)); } @@ -1322,13 +1332,13 @@ class UserService { Future deleteDevice(Device device) async { HttpieResponse response = - await _devicesApiService.deleteDeviceWithUuid(device.uuid); + await _devicesApiService.deleteDeviceWithUuid(device.uuid); _checkResponseIsOk(response); } Future getDeviceWithUuid(String deviceUuid) async { HttpieResponse response = - await _devicesApiService.getDeviceWithUuid(deviceUuid); + await _devicesApiService.getDeviceWithUuid(deviceUuid); _checkResponseIsOk(response); return Device.fromJSON(json.decode(response.body)); } @@ -1352,7 +1362,7 @@ class UserService { String deviceUuid = await _getDeviceUuid(); HttpieResponse response = - await _devicesApiService.getDeviceWithUuid(deviceUuid); + await _devicesApiService.getDeviceWithUuid(deviceUuid); if (response.isNotFound()) { // Device does not exists, create one. @@ -1372,7 +1382,7 @@ class UserService { Device currentDevice = await _getOrCreateCurrentDeviceCache; HttpieResponse response = - await _devicesApiService.deleteDeviceWithUuid(currentDevice.uuid); + await _devicesApiService.deleteDeviceWithUuid(currentDevice.uuid); if (!response.isOk() && !response.isNotFound()) { print('Could not delete current device'); @@ -1382,15 +1392,15 @@ class UserService { } Future - getAuthenticatedUserNotificationsSettings() async { + getAuthenticatedUserNotificationsSettings() async { HttpieResponse response = - await _authApiService.getAuthenticatedUserNotificationsSettings(); + await _authApiService.getAuthenticatedUserNotificationsSettings(); _checkResponseIsOk(response); return UserNotificationsSettings.fromJSON(json.decode(response.body)); } Future - updateAuthenticatedUserNotificationsSettings({ + updateAuthenticatedUserNotificationsSettings({ bool postCommentNotifications, bool postReactionNotifications, bool followNotifications, @@ -1399,20 +1409,21 @@ class UserService { bool communityInviteNotifications, }) async { HttpieResponse response = - await _authApiService.updateAuthenticatedUserNotificationsSettings( - postCommentNotifications: postCommentNotifications, - postReactionNotifications: postReactionNotifications, - followNotifications: followNotifications, - connectionConfirmedNotifications: connectionConfirmedNotifications, - communityInviteNotifications: communityInviteNotifications, - connectionRequestNotifications: connectionRequestNotifications); + await _authApiService.updateAuthenticatedUserNotificationsSettings( + postCommentNotifications: postCommentNotifications, + postReactionNotifications: postReactionNotifications, + followNotifications: followNotifications, + connectionConfirmedNotifications: connectionConfirmedNotifications, + communityInviteNotifications: communityInviteNotifications, + connectionRequestNotifications: connectionRequestNotifications); _checkResponseIsOk(response); return UserNotificationsSettings.fromJSON(json.decode(response.body)); } - Future reportUser({@required User user, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportUser( + {@required User user, + String description, + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _authApiService.reportUserWithUsername( description: description, userUsername: user.username, @@ -1420,9 +1431,10 @@ class UserService { _checkResponseIsCreated(response); } - Future reportCommunity({@required Community community, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportCommunity( + {@required Community community, + String description, + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _communitiesApiService.reportCommunity( communityName: community.name, description: description, @@ -1430,19 +1442,22 @@ class UserService { _checkResponseIsCreated(response); } - Future reportPost({@required Post post, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportPost( + {@required Post post, + String description, + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _postsApiService.reportPost( description: description, - postUuid: post.uuid, moderationCategoryId: moderationCategory.id); + postUuid: post.uuid, + moderationCategoryId: moderationCategory.id); _checkResponseIsCreated(response); } - Future reportPostComment({@required PostComment postComment, - Post post, - String description, - @required ModerationCategory moderationCategory}) async { + Future reportPostComment( + {@required PostComment postComment, + @required Post post, + String description, + @required ModerationCategory moderationCategory}) async { HttpieResponse response = await _postsApiService.reportPostComment( postCommentId: postComment.id, postUuid: post.uuid, @@ -1453,26 +1468,25 @@ class UserService { Future getGlobalModeratedObjects( {List statuses, - List types, - int count, - int maxId, - bool verified}) async { + List types, + int count, + int maxId, + bool verified}) async { HttpieResponse response = - await _moderationApiService - .getGlobalModeratedObjects( - maxId: maxId, - verified: verified, - types: types != null - ? types.map((status) => - ModeratedObject.factory.convertTypeToString(status)) - : null, - statuses: - statuses != null - ? statuses.map((status) => - ModeratedObject.factory - .convertStatusToString(status)) - : null, - count: count); + await _moderationApiService + .getGlobalModeratedObjects( + maxId: maxId, + verified: verified, + types: types != null + ? types.map((status) => + ModeratedObject.factory.convertTypeToString(status)) + : null, + statuses: + statuses != null + ? statuses.map((status) => ModeratedObject.factory + .convertStatusToString(status)) + : null, + count: count); _checkResponseIsOk(response); @@ -1481,11 +1495,11 @@ class UserService { Future getModeratedObjectsForCommunity( {@required Community community, - List statuses, - List types, - int count, - int maxId, - bool verified}) async { + List statuses, + List types, + int count, + int maxId, + bool verified}) async { HttpieResponse response = await _communitiesApiService.getModeratedObjects( communityName: community.name, maxId: maxId, @@ -1496,7 +1510,7 @@ class UserService { : null, statuses: statuses != null ? statuses.map((status) => - ModeratedObject.factory.convertStatusToString(status)) + ModeratedObject.factory.convertStatusToString(status)) : null, count: count); @@ -1509,7 +1523,7 @@ class UserService { {String description, ModerationCategory category}) async { HttpieResponse response = await _moderationApiService .updateModeratedObjectWithId(moderatedObject.id, - description: description, categoryId: category.id); + description: description, categoryId: category.id); _checkResponseIsCreated(response); return ModeratedObject.fromJSON(json.decode(response.body)); } @@ -1540,7 +1554,7 @@ class UserService { Future getModerationCategories() async { HttpieResponse response = - await _moderationApiService.getModerationCategories(); + await _moderationApiService.getModerationCategories(); _checkResponseIsOk(response); diff --git a/lib/widgets/tiles/actions/report_post_comment_tile.dart b/lib/widgets/tiles/actions/report_post_comment_tile.dart new file mode 100644 index 000000000..3b66511e1 --- /dev/null +++ b/lib/widgets/tiles/actions/report_post_comment_tile.dart @@ -0,0 +1,69 @@ +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tiles/loading_tile.dart'; +import 'package:flutter/material.dart'; + +class OBReportPostCommentTile extends StatefulWidget { + final PostComment postComment; + final ValueChanged onPostCommentReported; + final VoidCallback onWantsToReportPostComment; + + const OBReportPostCommentTile({ + Key key, + this.onPostCommentReported, + @required this.postComment, + this.onWantsToReportPostComment, + }) : super(key: key); + + @override + OBReportPostCommentTileState createState() { + return OBReportPostCommentTileState(); + } +} + +class OBReportPostCommentTileState extends State { + NavigationService _navigationService; + bool _requestInProgress; + + @override + void initState() { + super.initState(); + _requestInProgress = false; + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _navigationService = openbookProvider.navigationService; + + return StreamBuilder( + stream: widget.postComment.updateSubject, + initialData: widget.postComment, + builder: (BuildContext context, AsyncSnapshot snapshot) { + var postComment = snapshot.data; + + bool isReported = postComment.isReported ?? false; + + return OBLoadingTile( + isLoading: _requestInProgress || isReported, + leading: OBIcon(OBIcons.report), + title: OBText( + isReported ? 'You have reported this comment' : 'Report comment'), + onTap: isReported ? () {} : _reportPostComment, + ); + }, + ); + } + + void _reportPostComment() { + if (widget.onWantsToReportPostComment != null) + widget.onWantsToReportPostComment(); + _navigationService.navigateToReportObject( + context: context, + object: widget.postComment, + onObjectReported: widget.onPostCommentReported); + } +} diff --git a/lib/widgets/tiles/actions/report_post_tile.dart b/lib/widgets/tiles/actions/report_post_tile.dart new file mode 100644 index 000000000..f99ac9a24 --- /dev/null +++ b/lib/widgets/tiles/actions/report_post_tile.dart @@ -0,0 +1,68 @@ +import 'package:Openbook/models/post.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tiles/loading_tile.dart'; +import 'package:flutter/material.dart'; + +class OBReportPostTile extends StatefulWidget { + final Post post; + final ValueChanged onPostReported; + final VoidCallback onWantsToReportPost; + + const OBReportPostTile({ + Key key, + this.onPostReported, + @required this.post, + this.onWantsToReportPost, + }) : super(key: key); + + @override + OBReportPostTileState createState() { + return OBReportPostTileState(); + } +} + +class OBReportPostTileState extends State { + NavigationService _navigationService; + bool _requestInProgress; + + @override + void initState() { + super.initState(); + _requestInProgress = false; + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _navigationService = openbookProvider.navigationService; + + return StreamBuilder( + stream: widget.post.updateSubject, + initialData: widget.post, + builder: (BuildContext context, AsyncSnapshot snapshot) { + var post = snapshot.data; + + bool isReported = post.isReported ?? false; + + return OBLoadingTile( + isLoading: _requestInProgress || isReported, + leading: OBIcon(OBIcons.report), + title: OBText( + isReported ? 'You have reported this post' : 'Report post'), + onTap: isReported ? () {} : _reportPost, + ); + }, + ); + } + + void _reportPost() { + if (widget.onWantsToReportPost != null) widget.onWantsToReportPost(); + _navigationService.navigateToReportObject( + context: context, + object: widget.post, + onObjectReported: widget.onPostReported); + } +} From c4a2596020e18627768c13d209d9e46ddb82bf5e Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Sun, 26 May 2019 19:55:43 +0200 Subject: [PATCH 23/65] :sparkles: hide post after reporting --- lib/pages/home/bottom_sheets/post_actions.dart | 4 ++-- .../report_object/pages/confirm_report_object.dart | 2 +- lib/services/bottom_sheet.dart | 10 ++++------ lib/widgets/post/post.dart | 3 ++- lib/widgets/post/widgets/post_header/post_header.dart | 11 ++++++----- .../post_header/widgets/community_post_header.dart | 5 +++-- .../widgets/post_header/widgets/user_post_header.dart | 6 ++++-- lib/widgets/tiles/actions/report_post_tile.dart | 7 +++++-- 8 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/pages/home/bottom_sheets/post_actions.dart b/lib/pages/home/bottom_sheets/post_actions.dart index 278cf6fc4..e807df978 100644 --- a/lib/pages/home/bottom_sheets/post_actions.dart +++ b/lib/pages/home/bottom_sheets/post_actions.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart'; class OBPostActionsBottomSheet extends StatefulWidget { final Post post; - final OnPostReported onPostReported; + final ValueChanged onPostReported; final OnPostDeleted onPostDeleted; const OBPostActionsBottomSheet( @@ -100,6 +100,7 @@ class OBPostActionsBottomSheetState extends State { postActions.add(OBReportPostTile( post: widget.post, onWantsToReportPost: _dismiss, + onPostReported: widget.onPostReported, )); } @@ -151,5 +152,4 @@ class OBPostActionsBottomSheetState extends State { } } -typedef OnPostReported(Post post); typedef OnPostDeleted(Post post); diff --git a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart index 532e7ce92..072898d45 100644 --- a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart +++ b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart @@ -123,7 +123,7 @@ class OBConfirmReportObjectState extends State { OBMarkdown( onlyBody: true, data: '- Your report will be submitted anonymously. \n ' - '- If you are porting a post or comment, the report will be sent to both the Openbook staff and the community moderators if applicable and the post will be hidden from your feed \n' + '- If you are reporting a post or comment, the report will be sent to the Openbook staff and the community moderators if applicable and the post will be hidden from your feed \n' '- If you are reporting an account or community, it will be sent to the Openbook staff. \n' '- We\'ll review it, if approved, content will be deleted and penalties delivered to the people involved randing from deletion of account to hours of suspension depending on the severity of the report. \n' '- If the report is found to be made in an attempt to damage another member or community in the platform with no infringement of the stated reason, penalties will be applied to you. \n') diff --git a/lib/services/bottom_sheet.dart b/lib/services/bottom_sheet.dart index 3b98f6d25..6016ef77e 100644 --- a/lib/services/bottom_sheet.dart +++ b/lib/services/bottom_sheet.dart @@ -82,7 +82,7 @@ class BottomSheetService { {@required BuildContext context, @required Post post, @required OnPostDeleted onPostDeleted, - @required OnPostReported onPostReported, + @required ValueChanged onPostReported, List initialPickedFollowsLists}) { return showModalBottomSheetApp( context: context, @@ -111,15 +111,13 @@ class BottomSheetService { Future showMoreCommentActions( {@required BuildContext context, - @required Post post, - @required PostComment postComment}) { + @required Post post, + @required PostComment postComment}) { return showModalBottomSheetApp( context: context, builder: (BuildContext context) { return OBCommentMoreActionsBottomSheet( - post: post, - postComment: postComment - ); + post: post, postComment: postComment); }); } diff --git a/lib/widgets/post/post.dart b/lib/widgets/post/post.dart index 11ac7c4e9..69ddffce9 100644 --- a/lib/widgets/post/post.dart +++ b/lib/widgets/post/post.dart @@ -12,7 +12,7 @@ import 'package:flutter/material.dart'; class OBPost extends StatelessWidget { final Post post; - final OnPostDeleted onPostDeleted; + final ValueChanged onPostDeleted; const OBPost(this.post, {Key key, @required this.onPostDeleted}) : super(key: key); @@ -27,6 +27,7 @@ class OBPost extends StatelessWidget { OBPostHeader( post: post, onPostDeleted: onPostDeleted, + onPostReported: onPostDeleted, ), OBPostBody(post), OBPostReactions(post), diff --git a/lib/widgets/post/widgets/post_header/post_header.dart b/lib/widgets/post/widgets/post_header/post_header.dart index 9352bfb5f..5b9eb9a98 100644 --- a/lib/widgets/post/widgets/post_header/post_header.dart +++ b/lib/widgets/post/widgets/post_header/post_header.dart @@ -7,8 +7,10 @@ import 'package:flutter/material.dart'; class OBPostHeader extends StatelessWidget { final Post post; final OnPostDeleted onPostDeleted; + final ValueChanged onPostReported; - const OBPostHeader({Key key, this.onPostDeleted, this.post}) + const OBPostHeader( + {Key key, this.onPostDeleted, this.post, this.onPostReported}) : super(key: key); @override @@ -17,10 +19,9 @@ class OBPostHeader extends StatelessWidget { ? OBCommunityPostHeader( post, onPostDeleted: onPostDeleted, + onPostReported: onPostReported, ) - : OBUserPostHeader( - post, - onPostDeleted: onPostDeleted, - ); + : OBUserPostHeader(post, + onPostDeleted: onPostDeleted, onPostReported: onPostReported); } } diff --git a/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart b/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart index fbadd6b43..9ac3de342 100644 --- a/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart +++ b/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart @@ -14,9 +14,10 @@ import 'package:flutter/material.dart'; class OBCommunityPostHeader extends StatelessWidget { final Post _post; final OnPostDeleted onPostDeleted; + final ValueChanged onPostReported; const OBCommunityPostHeader(this._post, - {Key key, @required this.onPostDeleted}) + {Key key, @required this.onPostDeleted, this.onPostReported}) : super(key: key); @override @@ -47,7 +48,7 @@ class OBCommunityPostHeader extends StatelessWidget { context: context, post: _post, onPostDeleted: onPostDeleted, - onPostReported: null); + onPostReported: onPostReported); }), title: GestureDetector( onTap: () { diff --git a/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart b/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart index a42ba6992..b93d4a370 100644 --- a/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart +++ b/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart @@ -14,8 +14,10 @@ import 'package:flutter/material.dart'; class OBUserPostHeader extends StatelessWidget { final Post _post; final OnPostDeleted onPostDeleted; + final ValueChanged onPostReported; - const OBUserPostHeader(this._post, {Key key, @required this.onPostDeleted}) + const OBUserPostHeader(this._post, + {Key key, @required this.onPostDeleted, this.onPostReported}) : super(key: key); @override @@ -51,7 +53,7 @@ class OBUserPostHeader extends StatelessWidget { context: context, post: _post, onPostDeleted: onPostDeleted, - onPostReported: null); + onPostReported: onPostReported); }), title: GestureDetector( onTap: () { diff --git a/lib/widgets/tiles/actions/report_post_tile.dart b/lib/widgets/tiles/actions/report_post_tile.dart index f99ac9a24..41f5a397d 100644 --- a/lib/widgets/tiles/actions/report_post_tile.dart +++ b/lib/widgets/tiles/actions/report_post_tile.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; class OBReportPostTile extends StatefulWidget { final Post post; - final ValueChanged onPostReported; + final ValueChanged onPostReported; final VoidCallback onWantsToReportPost; const OBReportPostTile({ @@ -63,6 +63,9 @@ class OBReportPostTileState extends State { _navigationService.navigateToReportObject( context: context, object: widget.post, - onObjectReported: widget.onPostReported); + onObjectReported: (dynamic reportedObject) { + if (reportedObject != null && widget.onPostReported != null) + widget.onPostReported(reportedObject as Post); + }); } } From d0aa5d84e4a5630d018fa427e2b4f53f55fa49ff Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Sun, 26 May 2019 20:10:50 +0200 Subject: [PATCH 24/65] :sparkles: delete post comments on report --- lib/pages/home/pages/post_comments/post.dart | 37 +++++++++++-------- .../widgets/post_comment/post_comment.dart | 13 +++++-- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/pages/home/pages/post_comments/post.dart b/lib/pages/home/pages/post_comments/post.dart index 2b2708b43..ba7e96b5a 100644 --- a/lib/pages/home/pages/post_comments/post.dart +++ b/lib/pages/home/pages/post_comments/post.dart @@ -135,11 +135,15 @@ class OBPostCommentsPageState extends State { }; return OBPostComment( - key: Key('postComment#${postComment.id}'), - postComment: postComment, - post: widget.post, - onPostCommentDeletedCallback: - onPostCommentDeletedCallback); + key: Key('postComment#${postComment.id}'), + postComment: postComment, + post: widget.post, + onPostCommentDeletedCallback: + onPostCommentDeletedCallback, + onPostCommentReported: + (PostComment postComment) => + onPostCommentDeletedCallback(), + ); }), onLoadMore: _loadMoreBottomComments), ), @@ -206,7 +210,8 @@ class OBPostCommentsPageState extends State { Widget _buildPostCommenterSection() { User loggedInUser = _userService.getLoggedInUser(); - if (widget.post.areCommentsEnabled || loggedInUser.canCommentOnPostWithDisabledComments(widget.post)) { + if (widget.post.areCommentsEnabled || + loggedInUser.canCommentOnPostWithDisabledComments(widget.post)) { return OBPostCommenter( widget.post, autofocus: widget.autofocusCommentInput, @@ -219,15 +224,17 @@ class OBPostCommentsPageState extends State { padding: EdgeInsets.all(10.0), child: OBAlert( padding: EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Flexible( - child: OBText('Comments have been disabled for this post', textAlign: TextAlign.center,), - ), - ], - ) - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: OBText( + 'Comments have been disabled for this post', + textAlign: TextAlign.center, + ), + ), + ], + )), ); } } diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart index cec369484..18761a935 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart @@ -19,11 +19,13 @@ class OBPostComment extends StatefulWidget { final PostComment postComment; final Post post; final VoidCallback onPostCommentDeletedCallback; + final ValueChanged onPostCommentReported; OBPostComment( {@required this.post, @required this.postComment, this.onPostCommentDeletedCallback, + this.onPostCommentReported, Key key}) : super(key: key); @@ -137,9 +139,10 @@ class OBPostCommentState extends State { if (loggedInUser.canReportPostComment(widget.postComment)) { _editCommentActions.add( Opacity( - opacity: widget.postComment.isReported ? 0.5 : 1, + opacity: widget.postComment.isReported ?? false ? 0.5 : 1, child: IconSlideAction( - caption: widget.postComment.isReported ? 'Reported' : 'Report', + caption: + widget.postComment.isReported ?? false ? 'Reported' : 'Report', color: Colors.red, icon: Icons.report, onTap: _reportPostComment, @@ -187,7 +190,11 @@ class OBPostCommentState extends State { await _navigationService.navigateToReportObject( context: context, object: widget.postComment, - extraData: {'post': widget.post}); + extraData: {'post': widget.post}, + onObjectReported: (dynamic reportedObject) { + if (widget.onPostCommentReported != null && reportedObject != null) + widget.onPostCommentReported(reportedObject as PostComment); + }); } void _deletePostComment() async { From ca122682edab5f986373665283d7efe2a98c357d Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 27 May 2019 12:38:00 +0200 Subject: [PATCH 25/65] :sparkles: show toast on report --- lib/libs/str_utils.dart | 2 +- lib/libs/type_to_str.dart | 17 +++++++++++------ .../widgets/my_communities_group.dart | 2 +- .../user_invites/widgets/my_invite_group.dart | 2 +- .../pages/confirm_report_object.dart | 5 +++++ 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/libs/str_utils.dart b/lib/libs/str_utils.dart index c8bbde1ef..a08591b72 100644 --- a/lib/libs/str_utils.dart +++ b/lib/libs/str_utils.dart @@ -1 +1 @@ -String capitalize(String s) => s[0].toUpperCase() + s.substring(1); +String toCapital(String s) => s[0].toUpperCase() + s.substring(1); diff --git a/lib/libs/type_to_str.dart b/lib/libs/type_to_str.dart index c90d61cb1..c52d1809a 100644 --- a/lib/libs/type_to_str.dart +++ b/lib/libs/type_to_str.dart @@ -1,17 +1,22 @@ +import 'package:Openbook/libs/str_utils.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/models/user.dart'; -String modelTypeToString(dynamic modelInstance) { +String modelTypeToString(dynamic modelInstance, {bool capitalize = false}) { + String result; if (modelInstance is Post) { - return 'post'; + result = 'post'; } else if (modelInstance is PostComment) { - return 'post comment'; + result = 'post comment'; } else if (modelInstance is Community) { - return 'community'; + result = 'community'; } else if (modelInstance is User) { - return 'user'; + result = 'user'; + } else { + result = 'item'; } - return 'item'; + + return capitalize ? toCapital(result) : result; } diff --git a/lib/pages/home/pages/communities/widgets/my_communities/widgets/my_communities_group.dart b/lib/pages/home/pages/communities/widgets/my_communities/widgets/my_communities_group.dart index fa28969cd..c5712f4eb 100644 --- a/lib/pages/home/pages/communities/widgets/my_communities/widgets/my_communities_group.dart +++ b/lib/pages/home/pages/communities/widgets/my_communities/widgets/my_communities_group.dart @@ -190,7 +190,7 @@ class OBMyCommunitiesGroupState extends State { _navigationService.navigateToBlankPageWithWidget( context: context, key: Key('obMyCommunitiesGroup' + widget.groupItemName), - navBarTitle: capitalize(widget.groupName), + navBarTitle: toCapital(widget.groupName), widget: _buildSeeAllGroupItemsPage()); } diff --git a/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart b/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart index 44a76bdac..ac79e7af3 100644 --- a/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart +++ b/lib/pages/home/pages/menu/pages/user_invites/widgets/my_invite_group.dart @@ -209,7 +209,7 @@ class OBMyInvitesGroupState extends State { _navigationService.navigateToBlankPageWithWidget( context: context, key: Key('obMyUserInvitesGroup' + widget.groupItemName), - navBarTitle: capitalize(widget.groupName), + navBarTitle: toCapital(widget.groupName), widget: _buildSeeAllGroupItemsPage()); } diff --git a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart index 072898d45..b1b147aee 100644 --- a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart +++ b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart @@ -1,3 +1,4 @@ +import 'package:Openbook/libs/type_to_str.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/post.dart'; @@ -188,6 +189,10 @@ class OBConfirmReportObjectState extends State { widget.object is PostComment) { widget.object.setIsReported(true); } + _toastService.success( + message: + modelTypeToString(widget.object, capitalize: true) + ' reported', + context: context); Navigator.of(context).pop(true); } catch (error) { _onError(error); From c4ad512f3b9b55b7dc915c2b1c252c6b38534a84 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 27 May 2019 12:46:25 +0200 Subject: [PATCH 26/65] :art: format code --- lib/models/user.dart | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index 05ac3d127..2f9d7f443 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -411,11 +411,10 @@ class User extends UpdatableModel { loggedInUserIsCommunityAdministrator = community.isAdministrator(loggedInUser); - loggedInUserIsCommunityModerator = - community.isModerator(loggedInUser); - - return loggedInUserIsCommunityModerator || loggedInUserIsCommunityAdministrator; + loggedInUserIsCommunityModerator = community.isModerator(loggedInUser); + return loggedInUserIsCommunityModerator || + loggedInUserIsCommunityAdministrator; } bool canDeletePost(Post post) { @@ -425,7 +424,8 @@ class User extends UpdatableModel { bool loggedInUserIsStaffForCommunity = false; if (post.hasCommunity()) { - loggedInUserIsStaffForCommunity = this.isStaffForCommunity(post.community); + loggedInUserIsStaffForCommunity = + this.isStaffForCommunity(post.community); } if (loggedInUserIsPostCreator || loggedInUserIsStaffForCommunity) { @@ -447,13 +447,15 @@ class User extends UpdatableModel { User postCommenter = postComment.commenter; bool loggedInUserIsStaffForCommunity = false; bool loggedInUserIsCommenter = loggedInUser.id == postCommenter.id; - bool loggedInUserIsCommenterForOpenPost = loggedInUserIsCommenter && !post.isClosed && post.areCommentsEnabled; + bool loggedInUserIsCommenterForOpenPost = + loggedInUserIsCommenter && !post.isClosed && post.areCommentsEnabled; if (post.hasCommunity()) { - loggedInUserIsStaffForCommunity = this.isStaffForCommunity(post.community); + loggedInUserIsStaffForCommunity = isStaffForCommunity(post.community); } - return loggedInUserIsCommenterForOpenPost || (loggedInUserIsStaffForCommunity && loggedInUserIsCommenter); + return loggedInUserIsCommenterForOpenPost || + (loggedInUserIsStaffForCommunity && loggedInUserIsCommenter); } bool canReportPostComment(PostComment postComment) { @@ -467,12 +469,15 @@ class User extends UpdatableModel { User loggedInUser = this; User postCommenter = postComment.commenter; bool loggedInUserIsPostCreator = loggedInUser.id == post.getCreatorId(); - bool userIsCreatorOfNonCommunityPost = loggedInUserIsPostCreator && !post.hasCommunity(); + bool userIsCreatorOfNonCommunityPost = + loggedInUserIsPostCreator && !post.hasCommunity(); bool loggedInUserIsStaffForCommunity = false; - bool loggedInUserIsCommenterForOpenPost = (loggedInUser.id == postCommenter.id) && !post.isClosed; + bool loggedInUserIsCommenterForOpenPost = + (loggedInUser.id == postCommenter.id) && !post.isClosed; if (post.hasCommunity()) { - loggedInUserIsStaffForCommunity = this.isStaffForCommunity(post.community); + loggedInUserIsStaffForCommunity = + this.isStaffForCommunity(post.community); } return (loggedInUserIsCommenterForOpenPost || From 58d1972640400476cdb29a0091db7cdf49f9d08b Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 27 May 2019 13:13:05 +0200 Subject: [PATCH 27/65] :bug: add missing post comments type on nav service --- .../home/bottom_sheets/post_actions.dart | 2 -- lib/services/navigation_service.dart | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/pages/home/bottom_sheets/post_actions.dart b/lib/pages/home/bottom_sheets/post_actions.dart index e807df978..884399acf 100644 --- a/lib/pages/home/bottom_sheets/post_actions.dart +++ b/lib/pages/home/bottom_sheets/post_actions.dart @@ -1,5 +1,3 @@ -import 'dart:io'; -import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/user.dart'; import 'package:Openbook/provider.dart'; diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 1b0708326..7693ff76a 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -234,7 +234,8 @@ class NavigationService { OBSlideRightRoute( key: Key('obSlidePostComments'), widget: OBPostCommentsPage( - post:post, + pageType: PostCommentsPageType.comments, + post: post, showPostPreview: false, autofocusCommentInput: true))); } @@ -249,17 +250,15 @@ class NavigationService { post: post, showPostPreview: false, pageType: PostCommentsPageType.comments, - autofocusCommentInput: false) - )); + autofocusCommentInput: false))); } Future navigateToPostCommentReplies( {@required Post post, - @required PostComment postComment, - @required BuildContext context, - Function(PostComment) onReplyDeleted, - Function(PostComment) onReplyAdded - }) { + @required PostComment postComment, + @required BuildContext context, + Function(PostComment) onReplyDeleted, + Function(PostComment) onReplyAdded}) { return Navigator.push( context, OBSlideRightRoute( @@ -288,7 +287,9 @@ class NavigationService { } Future navigateToPostCommentRepliesLinked( - {@required PostComment postComment, @required PostComment parentComment, @required BuildContext context}) { + {@required PostComment postComment, + @required PostComment parentComment, + @required BuildContext context}) { return Navigator.push( context, OBSlideRightRoute( From 1f23f8ae4144b66b480cff84d05e7027291f6e96 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 27 May 2019 13:30:36 +0200 Subject: [PATCH 28/65] :truck: move controller of widget to its root --- lib/pages/home/pages/post_comments/post_comments_page.dart | 2 +- .../widgets => }/post_comments_page_controller.dart | 0 .../pages/post_comments/widgets/post_comments_header_bar.dart | 4 ++-- lib/services/navigation_service.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename lib/pages/home/pages/post_comments/{widgets/post_comment/widgets => }/post_comments_page_controller.dart (100%) diff --git a/lib/pages/home/pages/post_comments/post_comments_page.dart b/lib/pages/home/pages/post_comments/post_comments_page.dart index ba956827c..85042aef7 100644 --- a/lib/pages/home/pages/post_comments/post_comments_page.dart +++ b/lib/pages/home/pages/post_comments/post_comments_page.dart @@ -1,9 +1,9 @@ import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/pages/home/pages/post_comments/post_comments_page_controller.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post-commenter.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comment_tile.dart'; -import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_preview.dart'; import 'package:Openbook/services/theme.dart'; diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart b/lib/pages/home/pages/post_comments/post_comments_page_controller.dart similarity index 100% rename from lib/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart rename to lib/pages/home/pages/post_comments/post_comments_page_controller.dart diff --git a/lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart b/lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart index d4603d2aa..d13ab23e9 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comments_header_bar.dart @@ -1,5 +1,5 @@ import 'package:Openbook/models/post_comment.dart'; -import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart'; +import 'package:Openbook/pages/home/pages/post_comments/post_comments_page_controller.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/theme.dart'; import 'package:Openbook/services/theme_value_parser.dart'; @@ -41,7 +41,7 @@ class OBPostCommentsHeaderBar extends StatelessWidget { 'SEE_OLDEST': 'See oldest replies', 'BE_THE_FIRST': 'Be the first to reply', }; - + OBPostCommentsHeaderBar({ @required this.pageType, @required this.noMoreTopItemsToLoad, diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 7693ff76a..a4536e29f 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -46,7 +46,7 @@ import 'package:Openbook/pages/home/pages/menu/pages/themes/themes.dart'; import 'package:Openbook/pages/home/pages/notifications/pages/notifications_settings.dart'; import 'package:Openbook/pages/home/pages/post/post.dart'; import 'package:Openbook/pages/home/pages/post_comments/post_comments_page.dart'; -import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/widgets/post_comments_page_controller.dart'; +import 'package:Openbook/pages/home/pages/post_comments/post_comments_page_controller.dart'; import 'package:Openbook/pages/home/pages/profile/profile.dart'; import 'package:Openbook/pages/home/pages/report_object/pages/confirm_report_object.dart'; import 'package:Openbook/pages/home/pages/report_object/report_object.dart'; From 77a3d3cece23bc6b584334155c97027406844af8 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Mon, 27 May 2019 08:01:17 -0400 Subject: [PATCH 29/65] :bug: posible fix for infinte load --- lib/pages/home/pages/post_comments/post_comments_page.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pages/home/pages/post_comments/post_comments_page.dart b/lib/pages/home/pages/post_comments/post_comments_page.dart index 85042aef7..0e996fbf7 100644 --- a/lib/pages/home/pages/post_comments/post_comments_page.dart +++ b/lib/pages/home/pages/post_comments/post_comments_page.dart @@ -463,6 +463,9 @@ class OBPostCommentsPageState extends State this._postComments = postComments; }); _commentsPageController.updateControllerPostComments(this._postComments); + if (this._postComments.length == 0) { + _animationController.forward(); + } } void _setNoMoreBottomItemsToLoad(bool noMoreItemsToLoad) { From e717018fe309be297ff7892feddae8e15f79b36a Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 27 May 2019 14:21:36 +0200 Subject: [PATCH 30/65] :recycle: change order of comment actions --- .../widgets/post_comment/post_comment.dart | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart index b635a2d19..e838e6357 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart @@ -95,26 +95,25 @@ class OBPostCommentState extends State { List _commentActions = []; User loggedInUser = _userService.getLoggedInUser(); - if (loggedInUser.canReplyPostComment(widget.postComment)) { + if (loggedInUser.canDeletePostComment(widget.post, widget.postComment)) { _commentActions.add( - new IconSlideAction( - caption: 'Reply', - color: Colors.black38, - icon: Icons.reply, - onTap: _replyPostComment, - ) + new IconSlideAction( + caption: 'Delete', + color: Colors.red, + icon: Icons.delete, + onTap: _deletePostComment, + ), ); } - if (loggedInUser.canReportPostComment(widget.postComment)) { _commentActions.add( Opacity( opacity: widget.postComment.isReported ?? false ? 0.5 : 1, child: IconSlideAction( caption: - widget.postComment.isReported ?? false ? 'Reported' : 'Report', - color: Colors.red, + widget.postComment.isReported ?? false ? 'Reported' : 'Report', + color: Colors.black38, icon: Icons.report, onTap: _reportPostComment, ), @@ -133,14 +132,14 @@ class OBPostCommentState extends State { ); } - if (loggedInUser.canDeletePostComment(widget.post, widget.postComment)) { + if (loggedInUser.canReplyPostComment(widget.postComment)) { _commentActions.add( - new IconSlideAction( - caption: 'Delete', - color: Colors.red, - icon: Icons.delete, - onTap: _deletePostComment, - ), + new IconSlideAction( + caption: 'Reply', + color: Colors.blue, + icon: Icons.reply, + onTap: _replyPostComment, + ) ); } From 0f9029cca0bf5137e02aeee647c78e27e305c474 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Mon, 27 May 2019 08:27:28 -0400 Subject: [PATCH 31/65] :bug: use local state variable in widget build to ensure changes take place --- .../pages/post_comments/widgets/post_comment/post_comment.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart index b635a2d19..b0155a62a 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart @@ -153,7 +153,7 @@ class OBPostCommentState extends State { } Widget _buildPostCommentReplies() { - if (widget.postComment.repliesCount == 0) return SizedBox(); + if (_repliesCount == 0) return SizedBox(); return Padding( padding: EdgeInsets.only(left: 30.0, top: 0.0), child: Column( From 25e311654436822c4b0f495ed7848e748431d4b0 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Mon, 27 May 2019 08:56:22 -0400 Subject: [PATCH 32/65] :bug: refresh indicator key was incorrectly placed --- lib/pages/home/pages/post_comments/post_comments_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/pages/post_comments/post_comments_page.dart b/lib/pages/home/pages/post_comments/post_comments_page.dart index 0e996fbf7..4e8ce1a99 100644 --- a/lib/pages/home/pages/post_comments/post_comments_page.dart +++ b/lib/pages/home/pages/post_comments/post_comments_page.dart @@ -251,8 +251,8 @@ class OBPostCommentsPageState extends State _columnChildren.addAll([ Expanded( child: RefreshIndicator( + key: _refreshIndicatorKey, child: GestureDetector( - key: _refreshIndicatorKey, onTap: _unfocusCommentInput, child: LoadMore( whenEmptyLoad: false, From f1a78b130052af86ad220a83ce25f362a45a12ad Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 27 May 2019 16:39:58 +0200 Subject: [PATCH 33/65] :bug: remove reported comment immediately --- .../post_comments/post_comments_page.dart | 104 ++++++++++-------- 1 file changed, 59 insertions(+), 45 deletions(-) diff --git a/lib/pages/home/pages/post_comments/post_comments_page.dart b/lib/pages/home/pages/post_comments/post_comments_page.dart index 4e8ce1a99..dc42dcc00 100644 --- a/lib/pages/home/pages/post_comments/post_comments_page.dart +++ b/lib/pages/home/pages/post_comments/post_comments_page.dart @@ -94,7 +94,6 @@ class OBPostCommentsPageState extends State 'TITLE': 'Post comments', 'NO_MORE_TO_LOAD': 'No more comments to load', 'TAP_TO_RETRY': 'Tap to retry loading comments.', - }; static const PAGE_REPLIES_TEXT_MAP = { @@ -110,7 +109,7 @@ class OBPostCommentsPageState extends State super.initState(); if (widget.linkedPostComment != null) _post = widget.linkedPostComment.post; if (widget.post != null) _post = widget.post; - if (widget.pageType == PostCommentsPageType.comments){ + if (widget.pageType == PostCommentsPageType.comments) { _pageTextMap = PAGE_COMMENTS_TEXT_MAP; } else { _pageTextMap = PAGE_REPLIES_TEXT_MAP; @@ -169,25 +168,24 @@ class OBPostCommentsPageState extends State void _initialiseCommentsPageController() { _commentsPageController = OBPostCommentsPageController( - pageType: widget.pageType, - userService: _userService, - userPreferencesService: _userPreferencesService, - currentSort: _currentSort, - post: _post, - postComment: widget.postComment, - linkedPostComment: widget.linkedPostComment, - addPostComments: _addPostComments, - addToStartPostComments: _addToStartPostComments, - setPostComments: _setPostComments, - setCurrentSortValue: _setCurrentSortValue, - setNoMoreBottomItemsToLoad: _setNoMoreBottomItemsToLoad, - setNoMoreTopItemsToLoad: _setNoMoreTopItemsToLoad, - showNoMoreTopItemsToLoadToast: _showNoMoreTopItemsToLoadToast, - scrollToNewComment: _scrollToNewComment, - scrollToTop: _scrollToTop, - unfocusCommentInput: _unfocusCommentInput, - onError: _onError - ); + pageType: widget.pageType, + userService: _userService, + userPreferencesService: _userPreferencesService, + currentSort: _currentSort, + post: _post, + postComment: widget.postComment, + linkedPostComment: widget.linkedPostComment, + addPostComments: _addPostComments, + addToStartPostComments: _addToStartPostComments, + setPostComments: _setPostComments, + setCurrentSortValue: _setCurrentSortValue, + setNoMoreBottomItemsToLoad: _setNoMoreBottomItemsToLoad, + setNoMoreTopItemsToLoad: _setNoMoreTopItemsToLoad, + showNoMoreTopItemsToLoadToast: _showNoMoreTopItemsToLoadToast, + scrollToNewComment: _scrollToNewComment, + scrollToTop: _scrollToTop, + unfocusCommentInput: _unfocusCommentInput, + onError: _onError); } void dispose() { @@ -257,7 +255,8 @@ class OBPostCommentsPageState extends State child: LoadMore( whenEmptyLoad: false, isFinish: _noMoreBottomItemsToLoad, - delegate: OBInfinitePostCommentsLoadMoreDelegate(_pageTextMap), + delegate: + OBInfinitePostCommentsLoadMoreDelegate(_pageTextMap), child: ListView.builder( physics: const ClampingScrollPhysics(), controller: _postCommentsScrollController, @@ -266,42 +265,49 @@ class OBPostCommentsPageState extends State itemBuilder: (context, index) { if (index == 0) { return Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ _getPostPreview(), _getCommentPreview(), _getDivider(), OBPostCommentsHeaderBar( - pageType: widget.pageType, - noMoreTopItemsToLoad: _noMoreTopItemsToLoad, - postComments: _postComments, - currentSort: _currentSort, - onWantsToToggleSortComments:() => _commentsPageController.onWantsToToggleSortComments(), - loadMoreTopComments: () => _commentsPageController.loadMoreTopComments(), - onWantsToRefreshComments:() => _commentsPageController.onWantsToRefreshComments() - ), + pageType: widget.pageType, + noMoreTopItemsToLoad: _noMoreTopItemsToLoad, + postComments: _postComments, + currentSort: _currentSort, + onWantsToToggleSortComments: () => + _commentsPageController + .onWantsToToggleSortComments(), + loadMoreTopComments: () => + _commentsPageController + .loadMoreTopComments(), + onWantsToRefreshComments: () => + _commentsPageController + .onWantsToRefreshComments()), ], ); } else { return _getCommentTile(index); } }), - onLoadMore: () => _commentsPageController.loadMoreBottomComments()), + onLoadMore: () => + _commentsPageController.loadMoreBottomComments()), ), - onRefresh: () => _commentsPageController.onWantsToRefreshComments()), + onRefresh: () => + _commentsPageController.onWantsToRefreshComments()), ), OBPostCommenter( _post, postComment: widget.postComment, autofocus: widget.autofocusCommentInput, commentTextFieldFocusNode: _commentInputFocusNode, - onPostCommentCreated:(PostComment createdPostComment) { - _commentsPageController.refreshCommentsWithCreatedPostCommentVisible(createdPostComment); + onPostCommentCreated: (PostComment createdPostComment) { + _commentsPageController + .refreshCommentsWithCreatedPostCommentVisible(createdPostComment); if (widget.onCommentAdded != null) { widget.onCommentAdded(createdPostComment); - } - }, + } + }, ) ]); @@ -319,7 +325,8 @@ class OBPostCommentsPageState extends State if (widget.postComment == null) { return SizedBox(); } - return OBPostCommentTile(post: widget.post, postComment: widget.postComment); + return OBPostCommentTile( + post: widget.post, postComment: widget.postComment); } Widget _getCommentTile(int index) { @@ -331,7 +338,8 @@ class OBPostCommentsPageState extends State }; if (_animationController.status != AnimationStatus.completed && - !_startScrollWasInitialised && widget.linkedPostComment != null) { + !_startScrollWasInitialised && + widget.linkedPostComment != null) { Future.delayed(Duration(milliseconds: 0), () { _postCommentsScrollController.animateTo( _positionTopCommentSection - 100.0, @@ -351,7 +359,8 @@ class OBPostCommentsPageState extends State }); } - if (widget.linkedPostComment != null && postComment.id == widget.linkedPostComment.id) { + if (widget.linkedPostComment != null && + postComment.id == widget.linkedPostComment.id) { var theme = _themeService.getActiveTheme(); var primaryColor = _themeValueParserService.parseColor(theme.primaryColor); @@ -367,6 +376,7 @@ class OBPostCommentsPageState extends State postComment: postComment, post: _post, onPostCommentDeletedCallback: onPostCommentDeletedCallback, + onPostCommentReported: onPostCommentDeletedCallback, ), ); } else { @@ -375,6 +385,7 @@ class OBPostCommentsPageState extends State postComment: postComment, post: _post, onPostCommentDeletedCallback: onPostCommentDeletedCallback, + onPostCommentReported: onPostCommentDeletedCallback, ); } } @@ -389,7 +400,6 @@ class OBPostCommentsPageState extends State onPostDeleted: _onPostDeleted, focusCommentInput: _focusCommentInput, ); - } void _scrollToTop() { @@ -481,7 +491,8 @@ class OBPostCommentsPageState extends State } void _showNoMoreTopItemsToLoadToast() { - _toastService.info(context: context, message: _pageTextMap['NO_MORE_TO_LOAD']); + _toastService.info( + context: context, message: _pageTextMap['NO_MORE_TO_LOAD']); } void _setCurrentSortValue(PostCommentsSortType newSortValue) { @@ -492,8 +503,10 @@ class OBPostCommentsPageState extends State void _scrollToNewComment() { if (_currentSort == PostCommentsSortType.asc) { - _postCommentsScrollController.animateTo(_postCommentsScrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 50), curve: Curves.easeIn); + _postCommentsScrollController.animateTo( + _postCommentsScrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 50), + curve: Curves.easeIn); } else if (_currentSort == PostCommentsSortType.dec) { _postCommentsScrollController.animateTo( _positionTopCommentSection - 200.0, @@ -563,6 +576,7 @@ class OBPostCommentsPageState extends State class OBInfinitePostCommentsLoadMoreDelegate extends LoadMoreDelegate { Map pageTextMap; + OBInfinitePostCommentsLoadMoreDelegate(Map pageTextMap); @override From 6d66ce5622dd6ed2febd5732b8a8e6cc38f61752 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 28 May 2019 13:58:11 -0400 Subject: [PATCH 34/65] :bug: incr n decr count with parent comment checks --- lib/pages/home/pages/post_comments/widgets/post-commenter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/pages/post_comments/widgets/post-commenter.dart b/lib/pages/home/pages/post_comments/widgets/post-commenter.dart index a2c06d2ff..440847c78 100644 --- a/lib/pages/home/pages/post_comments/widgets/post-commenter.dart +++ b/lib/pages/home/pages/post_comments/widgets/post-commenter.dart @@ -206,7 +206,7 @@ class OBPostCommenterState extends State { } PostComment createdPostComment = await _submitFormOperation.value; - widget.post.incrementCommentsCount(); + if (createdPostComment.parentComment == null) widget.post.incrementCommentsCount(); _textController.clear(); _setFormWasSubmitted(false); _validateForm(); From c4f69c0b732e9b3761a4f5f4fd9536b5bfe33a9e Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 28 May 2019 13:59:48 -0400 Subject: [PATCH 35/65] :bug: navigate to replies after pop to ensure app bar is present --- .../post-comment-reply-expanded.dart | 6 ------ .../post_comments_page_controller.dart | 2 +- .../widgets/post_comment/post_comment.dart | 16 ++++++++++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart b/lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart index e44002ea8..1bf8f56e0 100644 --- a/lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart +++ b/lib/pages/home/modals/post_comment/post-comment-reply-expanded.dart @@ -127,12 +127,6 @@ class OBPostCommentReplyExpandedModalState extends State loadMoreBottomComments() async { if (_loadMoreBottomCommentsOperation != null) _loadMoreBottomCommentsOperation.cancel(); - if (this.postComments.length == 0) return true; + if (this.postComments.length == 0 || _refreshCommentsWithCreatedPostCommentVisibleOperation != null) return true; PostComment lastPost = this.postComments.last; int lastPostId = lastPost.id; diff --git a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart index 543bf956a..836fb4363 100644 --- a/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart +++ b/lib/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart @@ -214,12 +214,12 @@ class OBPostCommentState extends State { void _onReplyAdded(PostComment postCommentReply) async { PostCommentsSortType sortType = await _userPreferencesService.getPostCommentsSortType(); setState(() { - _repliesCount += 1; if (sortType == PostCommentsSortType.dec) { _replies.insert(0, postCommentReply); - } else if (_repliesCount <= 2) { + } else if (_repliesCount == _replies.length) { _replies.add(postCommentReply); } + _repliesCount += 1; }); } @@ -240,12 +240,20 @@ class OBPostCommentState extends State { } void _replyPostComment() async { - await _modalService.openExpandedReplyCommenter( + PostComment comment = await _modalService.openExpandedReplyCommenter( context: context, post: widget.post, postComment: widget.postComment, onReplyDeleted: _onReplyDeleted, onReplyAdded: _onReplyAdded); + if (comment != null) { + await _navigationService.navigateToPostCommentReplies( + post: widget.post, + postComment: widget.postComment, + onReplyAdded: _onReplyAdded, + onReplyDeleted: _onReplyDeleted, + context: context); + } } void _deletePostComment() async { @@ -257,7 +265,7 @@ class OBPostCommentState extends State { postComment: widget.postComment, post: widget.post)); await _requestOperation.value; - widget.post.decreaseCommentsCount(); + if (widget.postComment.parentComment == null) widget.post.decreaseCommentsCount(); _toastService.success(message: 'Comment deleted', context: context); if (widget.onPostCommentDeletedCallback != null) { widget.onPostCommentDeletedCallback(widget.postComment); From 316e23918b529a9556a6d3cd670072fc00b4c04d Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 28 May 2019 14:00:10 -0400 Subject: [PATCH 36/65] :art: add type void to future that dont return --- lib/services/navigation_service.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index a4536e29f..cece84288 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -240,7 +240,7 @@ class NavigationService { autofocusCommentInput: true))); } - Future navigateToPostComments( + Future navigateToPostComments( {@required Post post, @required BuildContext context}) { return Navigator.push( context, @@ -253,7 +253,7 @@ class NavigationService { autofocusCommentInput: false))); } - Future navigateToPostCommentReplies( + Future navigateToPostCommentReplies( {@required Post post, @required PostComment postComment, @required BuildContext context, @@ -264,6 +264,7 @@ class NavigationService { OBSlideRightRoute( key: Key('obSlideViewComments'), widget: OBPostCommentsPage( + pageType: PostCommentsPageType.replies, post: post, showPostPreview: false, postComment: postComment, @@ -272,7 +273,7 @@ class NavigationService { autofocusCommentInput: false))); } - Future navigateToPostCommentsLinked( + Future navigateToPostCommentsLinked( {@required PostComment postComment, @required BuildContext context}) { return Navigator.push( context, @@ -286,7 +287,7 @@ class NavigationService { autofocusCommentInput: false))); } - Future navigateToPostCommentRepliesLinked( + Future navigateToPostCommentRepliesLinked( {@required PostComment postComment, @required PostComment parentComment, @required BuildContext context}) { From 6f12f84be7fb65399a19f6b7ada555c18fe95685 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Tue, 28 May 2019 18:53:41 -0400 Subject: [PATCH 37/65] :bug: fix refreshindicator --- lib/pages/home/pages/post_comments/post_comments_page.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/home/pages/post_comments/post_comments_page.dart b/lib/pages/home/pages/post_comments/post_comments_page.dart index dc42dcc00..69bc13799 100644 --- a/lib/pages/home/pages/post_comments/post_comments_page.dart +++ b/lib/pages/home/pages/post_comments/post_comments_page.dart @@ -258,7 +258,8 @@ class OBPostCommentsPageState extends State delegate: OBInfinitePostCommentsLoadMoreDelegate(_pageTextMap), child: ListView.builder( - physics: const ClampingScrollPhysics(), + shrinkWrap: true, + physics: const AlwaysScrollableScrollPhysics(), controller: _postCommentsScrollController, padding: EdgeInsets.all(0), itemCount: _postComments.length + 1, From 5a1e6db86ae505f0b7ae29e977a74b2eb2c28b24 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Fri, 31 May 2019 15:35:01 +0200 Subject: [PATCH 38/65] :construction: skeleton for moderated objects feeds --- lib/models/moderation/moderated_object.dart | 72 ++++ .../moderation/moderated_object_log.dart | 102 ++++++ .../moderation/moderated_object_log_list.dart | 20 + lib/models/moderation/moderation_report.dart | 40 ++ .../moderation/moderation_report_list.dart | 20 + lib/models/user.dart | 5 + .../manage_community/manage_community.dart | 15 + lib/pages/home/pages/menu/menu.dart | 21 ++ .../home/pages/menu/pages/useful_links.dart | 6 +- .../moderated_objects_filters.dart | 259 +++++++++++++ .../moderated_objects/moderated_objects.dart | 345 ++++++++++++++++++ .../moderated_object_community_review.dart | 182 +++++++++ .../pages/moderated_object_global_review.dart | 159 ++++++++ .../moderated_object_category.dart | 46 +++ .../moderated_object_update_category.dart | 186 ++++++++++ .../moderated_object_description.dart | 45 +++ .../moderated_object_update_description.dart | 183 ++++++++++ .../moderated_object_reports_preview.dart | 130 +++++++ .../pages/moderated_object_reports.dart | 129 +++++++ .../moderated_object/moderated_object.dart | 74 ++++ .../widgets/moderated_object_actions.dart | 64 ++++ .../widgets/no_moderated_objects.dart | 23 ++ .../pages/report_object/report_object.dart | 2 - lib/services/modal_service.dart | 46 ++- lib/services/moderation_api.dart | 46 +++ lib/services/navigation_service.dart | 89 +++++ lib/services/user.dart | 62 +++- lib/services/validation.dart | 23 +- lib/widgets/icon.dart | 4 +- pubspec.lock | 6 +- 30 files changed, 2360 insertions(+), 44 deletions(-) create mode 100644 lib/models/moderation/moderated_object_log.dart create mode 100644 lib/models/moderation/moderated_object_log_list.dart create mode 100644 lib/models/moderation/moderation_report.dart create mode 100644 lib/models/moderation/moderation_report_list.dart create mode 100644 lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart create mode 100644 lib/pages/home/pages/moderated_objects/moderated_objects.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart create mode 100644 lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart create mode 100644 lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart create mode 100644 lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart diff --git a/lib/models/moderation/moderated_object.dart b/lib/models/moderation/moderated_object.dart index b455bbca8..c4713b32e 100644 --- a/lib/models/moderation/moderated_object.dart +++ b/lib/models/moderation/moderated_object.dart @@ -1,3 +1,4 @@ +import 'package:Openbook/libs/str_utils.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/post.dart'; @@ -66,6 +67,28 @@ class ModeratedObject extends UpdatableModel { contentObjectData: json['content_object'], type: type); } } + + void setIsVerified(bool isVerified) { + verified = isVerified; + notifyUpdate(); + } + + bool isVerified() { + return verified; + } + + void setIsApproved() { + _setStatus(ModeratedObjectStatus.approved); + } + + void setIsRejected() { + _setStatus(ModeratedObjectStatus.rejected); + } + + void _setStatus(ModeratedObjectStatus newStatus) { + status = newStatus; + notifyUpdate(); + } } class ModeratedObjectFactory extends UpdatableModelFactory { @@ -156,6 +179,29 @@ class ModeratedObjectFactory extends UpdatableModelFactory { } } + String convertStatusToHumanReadableString( + ModeratedObjectStatus moderatedObjectStatus, + {capitalize = false}) { + if (moderatedObjectStatus == null) return null; + + String result; + + switch (moderatedObjectStatus) { + case ModeratedObjectStatus.approved: + result = 'approved'; + break; + case ModeratedObjectStatus.rejected: + result = 'rejected'; + break; + case ModeratedObjectStatus.pending: + result = 'pending'; + break; + default: + } + + return capitalize ? toCapital(result) : result; + } + String convertTypeToString(ModeratedObjectType moderatedObjectType) { if (moderatedObjectType == null) return null; @@ -173,6 +219,32 @@ class ModeratedObjectFactory extends UpdatableModelFactory { } } + String convertTypeToHumanReadableString( + ModeratedObjectType moderatedObjectType, + {capitalize = false}) { + if (moderatedObjectType == null) return null; + + String result = 'object'; + + switch (moderatedObjectType) { + case ModeratedObjectType.community: + result = 'community'; + break; + case ModeratedObjectType.user: + result = 'user'; + break; + case ModeratedObjectType.post: + result = 'post'; + break; + case ModeratedObjectType.postComment: + result = 'post comment'; + break; + default: + } + + return capitalize ? toCapital(result) : result; + } + dynamic parseContentObject( {@required Map contentObjectData, @required ModeratedObjectType type}) { if (contentObjectData == null) return null; diff --git a/lib/models/moderation/moderated_object_log.dart b/lib/models/moderation/moderated_object_log.dart new file mode 100644 index 000000000..15e780f35 --- /dev/null +++ b/lib/models/moderation/moderated_object_log.dart @@ -0,0 +1,102 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/user.dart'; + +class ModeratedObjectLog { + static String descriptionChangedLogType = 'DC'; + static String statusChangedLogType = 'SC'; + static String verifiedChangedLogType = 'VC'; + static String categoryChangedLogType = 'CC'; + + final String description; + final bool verified; + final User actor; + final DateTime created; + ModeratedObjectLogType logType; + + ModeratedObjectLog( + {this.verified, + this.description, + this.actor, + this.created, + this.logType}); + + factory ModeratedObjectLog.fromJson(Map parsedJson) { + return ModeratedObjectLog( + verified: parsedJson['verified'], + description: parsedJson['description'], + actor: parseActor( + parsedJson['actor'], + ), + logType: parseType(parsedJson['log_type']), + created: parseCreated(parsedJson['created'])); + } + + static User parseActor(Map rawActor) { + if (rawActor == null) return null; + return User.fromJson(rawActor); + } + + static DateTime parseCreated(String created) { + if (created == null) return null; + return DateTime.parse(created).toLocal(); + } + + static ModeratedObjectLogType parseType(String moderatedObjectTypeStr) { + if (moderatedObjectTypeStr == null) return null; + + ModeratedObjectLogType moderatedObjectLogType; + if (moderatedObjectTypeStr == + ModeratedObjectLog.descriptionChangedLogType) { + moderatedObjectLogType = ModeratedObjectLogType.descriptionChanged; + } else if (moderatedObjectTypeStr == + ModeratedObjectLog.statusChangedLogType) { + moderatedObjectLogType = ModeratedObjectLogType.statusChanged; + } else if (moderatedObjectTypeStr == + ModeratedObjectLog.verifiedChangedLogType) { + moderatedObjectLogType = ModeratedObjectLogType.verifiedChanged; + } else if (moderatedObjectTypeStr == + ModeratedObjectLog.categoryChangedLogType) { + moderatedObjectLogType = ModeratedObjectLogType.categoryChanged; + } else { + print('Unsupported moderatedObjectLog type'); + } + + return moderatedObjectLogType; + } +} + +enum ModeratedObjectLogType { + descriptionChanged, + verifiedChanged, + statusChanged, + categoryChanged, +} + +class ModeratedObjectCategoryChangedLog { + final ModerationCategory changedFrom; + final ModerationCategory changedTo; + + ModeratedObjectCategoryChangedLog({this.changedFrom, this.changedTo}); +} + +class ModeratedObjectDescriptionChangedLog { + final String changedFrom; + final String changedTo; + + ModeratedObjectDescriptionChangedLog({this.changedFrom, this.changedTo}); +} + +class ModeratedObjectVerifiedChangedLog { + final bool changedFrom; + final bool changedTo; + + ModeratedObjectVerifiedChangedLog({this.changedFrom, this.changedTo}); +} + +class ModeratedObjectStatusChangedLog { + final ModeratedObjectStatus changedFrom; + final ModeratedObjectStatus changedTo; + + ModeratedObjectStatusChangedLog({this.changedFrom, this.changedTo}); +} diff --git a/lib/models/moderation/moderated_object_log_list.dart b/lib/models/moderation/moderated_object_log_list.dart new file mode 100644 index 000000000..d4c70550a --- /dev/null +++ b/lib/models/moderation/moderated_object_log_list.dart @@ -0,0 +1,20 @@ +import 'package:Openbook/models/moderation/moderated_object_log.dart'; + +class ModeratedObjectLogsList { + final List moderatedObjectLogs; + + ModeratedObjectLogsList({ + this.moderatedObjectLogs, + }); + + factory ModeratedObjectLogsList.fromJson(List parsedJson) { + List moderatedObjectLogs = parsedJson + .map((moderatedObjectLogJson) => + ModeratedObjectLog.fromJson(moderatedObjectLogJson)) + .toList(); + + return new ModeratedObjectLogsList( + moderatedObjectLogs: moderatedObjectLogs, + ); + } +} diff --git a/lib/models/moderation/moderation_report.dart b/lib/models/moderation/moderation_report.dart new file mode 100644 index 000000000..01d754e21 --- /dev/null +++ b/lib/models/moderation/moderation_report.dart @@ -0,0 +1,40 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/user.dart'; + +class ModerationReport { + final ModerationCategory category; + final String description; + final User reporter; + final DateTime created; + + ModerationReport( + {this.description, this.reporter, this.category, this.created}); + + factory ModerationReport.fromJson(Map parsedJson) { + return ModerationReport( + description: parsedJson['description'], + reporter: parseReporter( + parsedJson['reporter'], + ), + category: parseCategory( + parsedJson['category'], + ), + created: parseCreated(parsedJson['created'])); + } + + static User parseReporter(Map rawActor) { + if (rawActor == null) return null; + return User.fromJson(rawActor); + } + + static ModerationCategory parseCategory(Map rawModerationCategory) { + if (rawModerationCategory == null) return null; + return ModerationCategory.fromJson(rawModerationCategory); + } + + static DateTime parseCreated(String created) { + if (created == null) return null; + return DateTime.parse(created).toLocal(); + } +} diff --git a/lib/models/moderation/moderation_report_list.dart b/lib/models/moderation/moderation_report_list.dart new file mode 100644 index 000000000..f1ef97f19 --- /dev/null +++ b/lib/models/moderation/moderation_report_list.dart @@ -0,0 +1,20 @@ +import 'package:Openbook/models/moderation/moderation_report.dart'; + +class ModerationReportsList { + final List moderationReports; + + ModerationReportsList({ + this.moderationReports, + }); + + factory ModerationReportsList.fromJson(List parsedJson) { + List moderationReports = parsedJson + .map((moderationReportJson) => + ModerationReport.fromJson(moderationReportJson)) + .toList(); + + return new ModerationReportsList( + moderationReports: moderationReports, + ); + } +} diff --git a/lib/models/user.dart b/lib/models/user.dart index a89d02e8d..71695191d 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -32,6 +32,7 @@ class User extends UpdatableModel { bool isConnected; bool isReported; bool isBlocked; + bool isGlobalModerator; bool isFullyConnected; bool isPendingConnectionConfirmation; bool isMemberOfCommunities; @@ -86,6 +87,7 @@ class User extends UpdatableModel { this.inviteCount, this.isFollowing, this.isBlocked, + this.isGlobalModerator, this.isConnected, this.isReported, this.isFullyConnected, @@ -127,6 +129,8 @@ class User extends UpdatableModel { if (json.containsKey('invite_count')) inviteCount = json['invite_count']; if (json.containsKey('is_following')) isFollowing = json['is_following']; if (json.containsKey('is_connected')) isConnected = json['is_connected']; + if (json.containsKey('is_global_moderator')) + isGlobalModerator = json['is_global_moderator']; if (json.containsKey('is_blocked')) isBlocked = json['is_blocked']; if (json.containsKey('is_reported')) isReported = json['is_reported']; if (json.containsKey('connections_circle_id')) @@ -513,6 +517,7 @@ class UserFactory extends UpdatableModelFactory { followingCount: json['following_count'], isFollowing: json['is_following'], isConnected: json['is_connected'], + isGlobalModerator: json['is_global_moderator'], isBlocked: json['is_blocked'], isReported: json['is_reported'], isFullyConnected: json['is_fully_connected'], diff --git a/lib/pages/home/pages/community/pages/manage_community/manage_community.dart b/lib/pages/home/pages/community/pages/manage_community/manage_community.dart index 3dee52d50..e6b3da333 100644 --- a/lib/pages/home/pages/community/pages/manage_community/manage_community.dart +++ b/lib/pages/home/pages/community/pages/manage_community/manage_community.dart @@ -89,6 +89,21 @@ class OBManageCommunityPage extends StatelessWidget { )); } + if (loggedInUser.canBanOrUnbanUsersInCommunity(community)) { + menuListTiles.add(ListTile( + leading: const OBIcon(OBIcons.communityBannedUsers), + title: const OBText('Moderation reports'), + subtitle: const OBText( + 'See the community moderation reports.', + style: listItemSubtitleStyle, + ), + onTap: () { + navigationService.navigateToCommunityModeratedObjects( + context: context, community: community); + }, + )); + } + if (loggedInUser.canCloseOrOpenPostsInCommunity(community)) { menuListTiles.add(ListTile( leading: const OBIcon(OBIcons.closePost), diff --git a/lib/pages/home/pages/menu/menu.dart b/lib/pages/home/pages/menu/menu.dart index 919253a0c..98d810392 100644 --- a/lib/pages/home/pages/menu/menu.dart +++ b/lib/pages/home/pages/menu/menu.dart @@ -103,6 +103,27 @@ class OBMainMenuPage extends StatelessWidget { ); }, ), + StreamBuilder( + stream: userService.loggedInUserChange, + initialData: userService.getLoggedInUser(), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + User loggedInUser = snapshot.data; + + if (loggedInUser == null || + !(loggedInUser.isGlobalModerator ?? false)) + return const SizedBox(); + + return ListTile( + leading: const OBIcon(OBIcons.communityModerators), + title: OBText('Global moderation'), + onTap: () async { + navigationService.navigateToGlobalModeratedObjects( + context: context); + }, + ); + }, + ), ListTile( leading: const OBIcon(OBIcons.link), title: OBText('Useful links'), diff --git a/lib/pages/home/pages/menu/pages/useful_links.dart b/lib/pages/home/pages/menu/pages/useful_links.dart index e5cae994e..c8fc5ee3a 100644 --- a/lib/pages/home/pages/menu/pages/useful_links.dart +++ b/lib/pages/home/pages/menu/pages/useful_links.dart @@ -28,7 +28,7 @@ class OBUsefulLinksPage extends StatelessWidget { children: [ ListTile( leading: const OBIcon(OBIcons.guide), - title: OBText('Community guidelines'), + title: OBText('Openbook guidelines'), subtitle: OBSecondaryText( 'The guidelines we\'re all expected to follow for a healthy and friendly co-existence.'), onTap: () { @@ -70,9 +70,9 @@ class OBUsefulLinksPage extends StatelessWidget { ), ListTile( leading: const OBIcon(OBIcons.guide), - title: OBText('Community guide'), + title: OBText('Openbook handbook'), subtitle: OBSecondaryText( - 'An introduction to the Openbook Experience by @meep'), + 'A book with everything there is to know about using the platform'), onTap: () { urlLauncherService.launchUrl('https://openbook.support/'); }, diff --git a/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart new file mode 100644 index 000000000..c2c598135 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart @@ -0,0 +1,259 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/moderated_objects.dart'; +import 'package:Openbook/widgets/fields/checkbox_field.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectsFiltersModal extends StatefulWidget { + final OBModeratedObjectsPageController moderatedObjectsPageController; + + const OBModeratedObjectsFiltersModal( + {Key key, @required this.moderatedObjectsPageController}) + : super(key: key); + + @override + OBModeratedObjectsFiltersModalState createState() { + return OBModeratedObjectsFiltersModalState(); + } +} + +class OBModeratedObjectsFiltersModalState + extends State { + bool _requestInProgress; + + List _types; + List _selectedTypes; + List _statuses = [ + ModeratedObjectStatus.approved, + ModeratedObjectStatus.rejected, + ModeratedObjectStatus.pending, + ]; + List _selectedStatuses; + bool _onlyVerified; + + @override + void initState() { + super.initState(); + _requestInProgress = false; + + OBModeratedObjectsFilters currentFilters = + widget.moderatedObjectsPageController.getFilters(); + + if (widget.moderatedObjectsPageController.hasCommunity()) { + _types = [ + ModeratedObjectType.post, + ModeratedObjectType.postComment, + ]; + } else { + _types = [ + ModeratedObjectType.post, + ModeratedObjectType.postComment, + ModeratedObjectType.community, + ModeratedObjectType.user, + ]; + } + + _selectedTypes = currentFilters.types; + _selectedStatuses = currentFilters.statuses; + + _onlyVerified = currentFilters.onlyVerified; + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: _buildNavigationBar(), + child: OBPrimaryColorContainer( + child: Column( + children: [ + Expanded( + child: _buildFilters(), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: Row( + children: [ + Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.highlight, + child: Text('Clear all'), + onPressed: _onWantsToClearFilters, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + child: OBButton( + size: OBButtonSize.large, + child: _buildApplyFiltersText(), + onPressed: _onWantsToApplyFilters, + isLoading: _requestInProgress, + ), + ) + ], + ), + ) + ], + ))); + } + + Widget _buildApplyFiltersText() { + String text = 'Apply filters'; + int filterCount = _countFilters(); + if (filterCount > 0) { + String friendlyCount = filterCount.toString(); + text += ' ($friendlyCount)'; + } + return Text(text); + } + + Widget _buildFilters() { + return ListView( + children: [ + OBTileGroupTitle( + title: 'Type', + ), + ListView.builder( + itemBuilder: _buildTypeListTile, + shrinkWrap: true, + itemCount: _types.length, + ), + OBTileGroupTitle( + title: 'Status', + ), + ListView.builder( + itemBuilder: _buildStatusListTile, + shrinkWrap: true, + itemCount: _statuses.length, + ), + OBTileGroupTitle( + title: 'Other', + ), + _buildIsVerifiedListTile() + ], + ); + } + + Widget _buildTypeListTile(BuildContext context, int index) { + ModeratedObjectType type = _types[index]; + String typeString = ModeratedObject.factory + .convertTypeToHumanReadableString(type, capitalize: true); + return OBCheckboxField( + onTap: () { + _onTypePressed(type); + }, + title: typeString, + value: _types.contains(type), + ); + } + + Widget _buildStatusListTile(BuildContext context, int index) { + ModeratedObjectStatus status = _statuses[index]; + String statusString = ModeratedObject.factory + .convertStatusToHumanReadableString(status, capitalize: true); + return OBCheckboxField( + onTap: () { + _onStatusPressed(status); + }, + title: statusString, + value: _statuses.contains(status), + ); + } + + Widget _buildIsVerifiedListTile() { + return OBCheckboxField( + title: 'Are verified', + value: _onlyVerified, + ); + } + + Widget _buildNavigationBar() { + return OBThemedNavigationBar( + leading: GestureDetector( + child: const OBIcon(OBIcons.close), + onTap: () { + Navigator.pop(context); + }, + ), + title: 'Moderation Filters'); + } + + void _onWantsToApplyFilters() async { + _setRequestInProgress(true); + await widget.moderatedObjectsPageController.setFilters( + OBModeratedObjectsFilters( + types: _types, statuses: _statuses, onlyVerified: _onlyVerified)); + _setRequestInProgress(false); + Navigator.pop(context); + } + + void _onWantsToClearFilters() async { + setState(() { + _selectedTypes = []; + _selectedStatuses = []; + }); + } + + void _onTypePressed(ModeratedObjectType pressedType) { + if (_selectedTypes.contains(pressedType)) { + // Remove + _removeSelectedType(pressedType); + } else { + // Add + _addSelectedType(pressedType); + } + } + + void _addSelectedType(ModeratedObjectType type) { + setState(() { + _selectedTypes.add(type); + }); + } + + void _removeSelectedType(ModeratedObjectType type) { + setState(() { + _selectedTypes.remove(type); + }); + } + + void _onStatusPressed(ModeratedObjectStatus pressedStatus) { + if (_selectedStatuses.contains(pressedStatus)) { + // Remove + _removeSelectedStatus(pressedStatus); + } else { + // Add + _addSelectedStatus(pressedStatus); + } + } + + void _addSelectedStatus(ModeratedObjectStatus status) { + setState(() { + _selectedStatuses.add(status); + }); + } + + void _removeSelectedStatus(ModeratedObjectStatus status) { + setState(() { + _selectedStatuses.remove(status); + }); + } + + void _setRequestInProgress(bool requestInProgress) { + setState(() { + _requestInProgress = requestInProgress; + }); + } + + int _countFilters() { + return _selectedStatuses.length + + _selectedTypes.length + + (_onlyVerified ? 1 : 0); + } +} diff --git a/lib/pages/home/pages/moderated_objects/moderated_objects.dart b/lib/pages/home/pages/moderated_objects/moderated_objects.dart new file mode 100644 index 000000000..d806300e2 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/moderated_objects.dart @@ -0,0 +1,345 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderated_object_list.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/load_more.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/tiles/loading_indicator_tile.dart'; +import 'package:Openbook/widgets/tiles/retry_tile.dart'; +import 'package:async/async.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectsPage extends StatefulWidget { + final Community community; + + const OBModeratedObjectsPage({Key key, this.community}) : super(key: key); + + @override + OBModeratedObjectsPageState createState() { + return OBModeratedObjectsPageState(); + } +} + +class OBModeratedObjectsPageState extends State { + static int itemsLoadMoreCount = 10; + + OBModeratedObjectsPageController _controller; + Community _community; + OBModeratedObjectsFilters _filters; + ScrollController _scrollController; + final GlobalKey _refreshIndicatorKey = + GlobalKey(); + List _moderatedObjects; + + UserService _userService; + ToastService _toastService; + + CancelableOperation _loadMoreOperation; + CancelableOperation _refreshModeratedObjectsOperation; + + bool _needsBootstrap; + bool _moreModeratedObjectsToLoad; + bool _refreshModeratedObjectsInProgress; + + @override + void initState() { + super.initState(); + _community = widget.community; + _filters = + OBModeratedObjectsFilters(statuses: [], types: [], onlyVerified: false); + _controller = OBModeratedObjectsPageController(state: this); + _scrollController = ScrollController(); + _moderatedObjects = []; + _needsBootstrap = true; + _moreModeratedObjectsToLoad = true; + _refreshModeratedObjectsInProgress = true; + } + + @override + void dispose() { + super.dispose(); + if (_loadMoreOperation != null) _loadMoreOperation.cancel(); + if (_refreshModeratedObjectsOperation != null) + _refreshModeratedObjectsOperation.cancel(); + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + + if (_needsBootstrap) { + _bootstrap(); + _needsBootstrap = false; + } + + return CupertinoPageScaffold( + backgroundColor: Color.fromARGB(0, 0, 0, 0), + navigationBar: OBThemedNavigationBar( + title: widget.community != null + ? 'Community moderated objects' + : 'Globally moderated objects', + ), + child: OBPrimaryColorContainer( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: RefreshIndicator( + key: _refreshIndicatorKey, + child: LoadMore( + whenEmptyLoad: false, + isFinish: !_moreModeratedObjectsToLoad, + delegate: OBModeratedObjectsPageLoadMoreDelegate(), + child: ListView.builder( + controller: _scrollController, + physics: const ClampingScrollPhysics(), + padding: EdgeInsets.all(0), + itemCount: _moderatedObjects.length, + itemBuilder: _buildModeratedObject, + ), + onLoadMore: _loadMoreModeratedObjects), + onRefresh: _refreshModeratedObjects), + ) + ], + ), + )); + } + + Widget _buildModeratedObject(BuildContext context, int index) { + if (index == 0) { + Widget moderatedObjectItem; + + if (_refreshModeratedObjectsInProgress && _moderatedObjects.isEmpty) { + moderatedObjectItem = SizedBox( + child: Center( + child: Padding( + padding: EdgeInsets.only(top: 20), + child: OBProgressIndicator(), + ), + ), + ); + } else if (_moderatedObjects.length == 0) { + moderatedObjectItem = OBNoModeratedObjects( + onWantsToRefreshModeratedObjects: _refresh, + ); + } else { + moderatedObjectItem = const SizedBox( + height: 20, + ); + } + + return moderatedObjectItem; + } + + int postIndex = index - 1; + + var moderatedObject = _moderatedObjects[postIndex]; + + return OBModeratedObject( + moderatedObject: moderatedObject, + key: Key(moderatedObject.id.toString())); + } + + void _bootstrap() {} + + Future setFilters(OBModeratedObjectsFilters filters) { + _filters = filters; + _refresh(); + } + + void scrollToTop() { + if (_scrollController.hasClients) { + if (_scrollController.offset == 0) { + _refresh(); + } + + _scrollController.animateTo( + 0.0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 300), + ); + } + } + + Future _refresh() async { + _refreshIndicatorKey.currentState.show(); + } + + Future _refreshModeratedObjects() async { + try { + if (widget.community != null) { + _refreshModeratedObjectsOperation = CancelableOperation.fromFuture( + _userService.getGlobalModeratedObjects(count: itemsLoadMoreCount)); + } else { + _refreshModeratedObjectsOperation = CancelableOperation.fromFuture( + _userService.getCommunityModeratedObjects( + community: widget.community, count: itemsLoadMoreCount)); + } + + ModeratedObjectsList moderatedObjectList = + await _refreshModeratedObjectsOperation.value; + _setModeratedObjects(moderatedObjectList.moderatedObjects); + } catch (error) { + _onError(error); + } + } + + Future _loadMoreModeratedObjects() async { + if (_loadMoreOperation != null) _loadMoreOperation.cancel(); + + var lastModeratedObjectId; + if (_moderatedObjects.isNotEmpty) { + ModeratedObject lastModeratedObject = _moderatedObjects.last; + lastModeratedObjectId = lastModeratedObject.id; + } + + try { + if (widget.community != null) { + _loadMoreOperation = CancelableOperation.fromFuture( + _userService.getGlobalModeratedObjects( + maxId: lastModeratedObjectId, count: itemsLoadMoreCount)); + } else { + _loadMoreOperation = CancelableOperation.fromFuture( + _userService.getCommunityModeratedObjects( + community: _community, + maxId: lastModeratedObjectId, + count: itemsLoadMoreCount)); + } + + var moreModeratedObjects = + (await _loadMoreOperation.value).moderatedObjects; + + if (moreModeratedObjects.length == 0) { + _setMoreModeratedObjectsToLoad(false); + } else { + setState(() { + _moderatedObjects.addAll(moreModeratedObjects); + }); + } + return true; + } catch (error) { + _onError(error); + } finally { + _loadMoreOperation = null; + } + + return false; + } + + void _setMoreModeratedObjectsToLoad(bool moreModeratedObjectsToLoad) { + setState(() { + _moreModeratedObjectsToLoad = moreModeratedObjectsToLoad; + }); + } + + void _setModeratedObjects(List moderatedObjects) { + setState(() { + _moderatedObjects = moderatedObjects; + }); + } + + OBModeratedObjectsFilters getFilters() { + return _filters.clone(); + } + + bool hasCommunity() { + return _community != null; + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } +} + +class OBModeratedObjectsFilters { + final List types; + final List statuses; + final bool onlyVerified; + + OBModeratedObjectsFilters( + {@required this.types, + @required this.statuses, + @required this.onlyVerified}); + + OBModeratedObjectsFilters clone() { + return OBModeratedObjectsFilters( + types: types.toList(), + statuses: statuses.toList(), + onlyVerified: onlyVerified); + } +} + +class OBModeratedObjectsPageController { + OBModeratedObjectsPageState state; + + OBModeratedObjectsPageController({this.state}); + + void attach({OBModeratedObjectsPageState state}) { + state = state; + } + + Future setFilters(OBModeratedObjectsFilters filters) async { + return state.setFilters(filters); + } + + OBModeratedObjectsFilters getFilters() { + return state.getFilters(); + } + + void scrollToTop() { + state.scrollToTop(); + } + + bool hasCommunity() { + return state.hasCommunity(); + } +} + +class OBModeratedObjectsPageLoadMoreDelegate extends LoadMoreDelegate { + final VoidCallback onWantsToRetryLoading; + + const OBModeratedObjectsPageLoadMoreDelegate({this.onWantsToRetryLoading}); + + @override + Widget buildChild(LoadMoreStatus status, + {LoadMoreTextBuilder builder = DefaultLoadMoreTextBuilder.chinese}) { + String text = builder(status); + + if (status == LoadMoreStatus.fail) { + return OBRetryTile( + text: 'Tap to retry loading items', + onWantsToRetry: onWantsToRetryLoading, + ); + } + if (status == LoadMoreStatus.idle) { + // No clue why is this even a state. + return const SizedBox(); + } + if (status == LoadMoreStatus.loading) { + return OBLoadingIndicatorTile(); + } + if (status == LoadMoreStatus.nomore) { + return const SizedBox(); + } + + return Text(text); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart new file mode 100644 index 000000000..f17ad1c70 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart @@ -0,0 +1,182 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectCommunityReviewPage extends StatefulWidget { + final ModeratedObject moderatedObject; + final Community community; + + const OBModeratedObjectCommunityReviewPage( + {Key key, @required this.moderatedObject, @required this.community}) + : super(key: key); + + @override + OBModeratedObjectCommunityReviewPageState createState() { + return OBModeratedObjectCommunityReviewPageState(); + } +} + +class OBModeratedObjectCommunityReviewPageState + extends State { + bool _requestInProgress; + bool _isEditable; + + UserService _userService; + ToastService _toastService; + bool _needsBootstrap; + + CancelableOperation _requestOperation; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _isEditable = false; + } + + @override + void dispose() { + super.dispose(); + if (_requestOperation != null) _requestOperation.cancel(); + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _bootstrap(); + _needsBootstrap = false; + } + + return OBCupertinoPageScaffold( + navigationBar: OBThemedNavigationBar( + title: 'Review moderated object', + ), + child: OBPrimaryColorContainer( + child: Column( + children: [ + Expanded( + child: ListView( + children: [ + OBModeratedObjectDescription( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + ), + OBModeratedObjectCategory( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + ), + OBModeratedObjectReportsPreview( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + ) + ], + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: _buildPrimaryActions(), + ) + ], + )), + ); + } + + Widget _buildPrimaryActions() { + List actions = []; + + if (widget.moderatedObject.status == ModeratedObjectStatus.pending) { + actions.addAll([ + Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.danger, + child: Text('Reject'), + onPressed: _onWantsToRejectModeratedObject, + isLoading: _requestInProgress, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + child: OBButton( + size: OBButtonSize.large, + child: Text('Approve'), + onPressed: _onWantsToApproveModeratedObject, + isLoading: _requestInProgress, + ), + ) + ]); + } else { + actions.add( + Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.primary, + child: Text('This item has been verified'), + onPressed: null, + ), + ), + ); + } + + return Row( + children: actions, + ); + } + + void _onWantsToApproveModeratedObject() async { + try { + _requestOperation = CancelableOperation.fromFuture( + _userService.approveModeratedObject(widget.moderatedObject)); + await _requestOperation.value; + widget.moderatedObject.setIsApproved(); + Navigator.pop(context); + } catch (error) { + _onError(error); + } + } + + void _onWantsToRejectModeratedObject() async { + try { + _requestOperation = CancelableOperation.fromFuture( + _userService.rejectModeratedObject(widget.moderatedObject)); + await _requestOperation.value; + widget.moderatedObject.setIsRejected(); + Navigator.pop(context); + } catch (error) { + _onError(error); + } + } + + void _bootstrap() { + _isEditable = + widget.moderatedObject.status == ModeratedObjectStatus.pending; + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart new file mode 100644 index 000000000..3c964c711 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -0,0 +1,159 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectGlobalReviewPage extends StatefulWidget { + final ModeratedObject moderatedObject; + + const OBModeratedObjectGlobalReviewPage( + {Key key, @required this.moderatedObject}) + : super(key: key); + + @override + OBModeratedObjectGlobalReviewPageState createState() { + return OBModeratedObjectGlobalReviewPageState(); + } +} + +class OBModeratedObjectGlobalReviewPageState + extends State { + bool _requestInProgress; + bool _isEditable; + + UserService _userService; + ToastService _toastService; + bool _needsBootstrap; + + CancelableOperation _requestOperation; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _isEditable = false; + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _bootstrap(); + _needsBootstrap = false; + } + + return OBCupertinoPageScaffold( + navigationBar: OBThemedNavigationBar( + title: 'Review moderated object', + ), + child: OBPrimaryColorContainer( + child: Column( + children: [ + Expanded( + child: ListView( + children: [ + OBModeratedObjectDescription( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + ), + OBModeratedObjectCategory( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + ), + OBModeratedObjectReportsPreview( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + ) + ], + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: _buildPrimaryActions(), + ) + ], + )), + ); + } + + Widget _buildPrimaryActions() { + List actions = []; + + if (widget.moderatedObject.verified) { + actions.add(Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.danger, + child: Text('Unverify'), + onPressed: _onWantsToUnverifyModeratedObject, + isLoading: _requestInProgress, + ), + )); + } else { + actions.add(Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.danger, + child: Text('Verify'), + onPressed: _onWantsToVerifyModeratedObject, + isLoading: _requestInProgress, + ), + )); + } + + return Row( + children: actions, + ); + } + + void _onWantsToVerifyModeratedObject() async { + try { + _requestOperation = CancelableOperation.fromFuture( + _userService.verifyModeratedObject(widget.moderatedObject)); + await _requestOperation.value; + widget.moderatedObject.setIsVerified(true); + Navigator.pop(context); + } catch (error) { + _onError(error); + } + } + + void _onWantsToUnverifyModeratedObject() async { + try { + _requestOperation = CancelableOperation.fromFuture( + _userService.unverifyModeratedObject(widget.moderatedObject)); + await _requestOperation.value; + widget.moderatedObject.setIsVerified(false); + Navigator.pop(context); + } catch (error) { + _onError(error); + } + } + + void _bootstrap() { + _isEditable = !widget.moderatedObject.verified; + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart new file mode 100644 index 000000000..a2109b33a --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart @@ -0,0 +1,46 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectCategory extends StatelessWidget { + final bool isEditable; + final ModeratedObject moderatedObject; + + const OBModeratedObjectCategory( + {Key key, @required this.moderatedObject, @required this.isEditable}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBTileGroupTitle( + title: 'Category', + ), + StreamBuilder( + initialData: moderatedObject, + stream: moderatedObject.updateSubject, + builder: + (BuildContext context, AsyncSnapshot snapshot) { + return ListTile( + onTap: () { + OpenbookProviderState openbookProvider = + OpenbookProvider.of(context); + openbookProvider.navigationService + .navigateToModeratedObjectUpdateCategory( + context: context, moderatedObject: moderatedObject); + }, + title: OBText(snapshot.data.category.title), + subtitle: OBText(snapshot.data.category.description), + trailing: const OBIcon(OBIcons.chevronRight), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart new file mode 100644 index 000000000..a8841347a --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart @@ -0,0 +1,186 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/moderation/moderation_category_list.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/checkbox.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectUpdateCategoryPage extends StatefulWidget { + final ModeratedObject moderatedObject; + + const OBModeratedObjectUpdateCategoryPage( + {Key key, @required this.moderatedObject}) + : super(key: key); + + @override + OBModeratedObjectUpdateCategoryPageState createState() { + return OBModeratedObjectUpdateCategoryPageState(); + } +} + +class OBModeratedObjectUpdateCategoryPageState + extends State { + UserService _userService; + ToastService _toastService; + List _moderationCategories = []; + ModerationCategory _selectedModerationCategory; + bool _needsBootstrap; + bool _requestInProgress; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _requestInProgress = false; + _selectedModerationCategory = widget.moderatedObject.category; + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + var openbookProvider = OpenbookProvider.of(context); + _toastService = openbookProvider.toastService; + _userService = openbookProvider.userService; + _bootstrap(); + _needsBootstrap = false; + } + + return OBCupertinoPageScaffold( + navigationBar: _buildNavigationBar(), + child: OBPrimaryColorContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _moderationCategories.isEmpty + ? _buildProgressIndicator() + : _buildModerationCategories(), + ], + ), + )); + } + + Widget _buildProgressIndicator() { + return Expanded( + child: Center( + child: OBProgressIndicator(), + ), + ); + } + + Widget _buildModerationCategories() { + return Opacity( + opacity: _requestInProgress ? 0.8 : 1, + child: Expanded( + child: ListView.separated( + padding: EdgeInsets.all(0.0), + itemBuilder: _buildModerationCategoryTile, + separatorBuilder: (context, index) { + return const Divider(); + }, + itemCount: _moderationCategories.length, + ), + ), + ); + } + + Widget _buildModerationCategoryTile(context, index) { + ModerationCategory category = _moderationCategories[index]; + + return GestureDetector( + onTap: () => _setSelectedModerationCategory(category), + child: Row( + children: [ + Expanded( + child: ListTile( + title: OBText( + category.title, + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: OBSecondaryText(category.description), + //trailing: OBIcon(OBIcons.chevronRight), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: OBCheckbox( + value: _selectedModerationCategory == category, + ), + ) + ], + ), + ); + } + + void _setSelectedModerationCategory(ModerationCategory category) { + setState(() { + _selectedModerationCategory = category; + }); + } + + void _saveModerationCategory() { + _setRequestInProgress(true); + try { + _userService.updateModeratedObject(widget.moderatedObject, + category: _selectedModerationCategory); + Navigator.of(context).pop(); + } catch (error) { + _onError(error); + } finally { + _setRequestInProgress(false); + } + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } + + Widget _buildNavigationBar() { + return OBThemedNavigationBar( + title: 'Update category', + trailing: OBButton( + isLoading: _requestInProgress, + size: OBButtonSize.small, + onPressed: _saveModerationCategory, + child: Text('Save'), + ), + ); + } + + void _bootstrap() async { + var moderationCategories = await _userService.getModerationCategories(); + _setModerationCategories(moderationCategories); + } + + _setModerationCategories(ModerationCategoriesList moderationCategoriesList) { + setState(() { + _moderationCategories = moderationCategoriesList.moderationCategories; + }); + } + + _setRequestInProgress(bool requestInProgress) { + setState(() { + _requestInProgress = requestInProgress; + }); + } +} + +typedef OnObjectReported(dynamic object); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart new file mode 100644 index 000000000..e00fc67ef --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart @@ -0,0 +1,45 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectDescription extends StatelessWidget { + final bool isEditable; + final ModeratedObject moderatedObject; + + const OBModeratedObjectDescription( + {Key key, @required this.moderatedObject, @required this.isEditable}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBTileGroupTitle( + title: 'Description', + ), + ListTile( + onTap: () { + OpenbookProviderState openbookProvider = + OpenbookProvider.of(context); + openbookProvider.navigationService + .navigateToModeratedObjectUpdateDescription( + context: context, moderatedObject: moderatedObject); + }, + title: StreamBuilder( + initialData: moderatedObject, + stream: moderatedObject.updateSubject, + builder: (BuildContext context, + AsyncSnapshot snapshot) { + return OBText(snapshot.data.description); + }, + ), + trailing: const OBIcon(OBIcons.chevronRight), + ) + ], + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart new file mode 100644 index 000000000..ad031ca90 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart @@ -0,0 +1,183 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/httpie.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/services/validation.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/fields/text_form_field.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:async/async.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectUpdateDescriptionPage extends StatefulWidget { + final ModeratedObject moderatedObject; + + const OBModeratedObjectUpdateDescriptionPage( + {Key key, @required this.moderatedObject}) + : super(key: key); + + @override + OBModeratedObjectUpdateDescriptionPageState createState() { + return OBModeratedObjectUpdateDescriptionPageState(); + } +} + +class OBModeratedObjectUpdateDescriptionPageState + extends State { + UserService _userService; + ToastService _toastService; + ValidationService _validationService; + + bool _requestInProgress; + bool _formWasSubmitted; + bool _formValid; + + GlobalKey _formKey; + + TextEditingController _descriptionController; + + CancelableOperation _editDescriptionOperation; + + @override + void initState() { + super.initState(); + _formValid = true; + _requestInProgress = false; + _formWasSubmitted = false; + _descriptionController = + TextEditingController(text: widget.moderatedObject.description); + _formKey = GlobalKey(); + + _descriptionController.addListener(_updateFormValid); + } + + @override + void dispose() { + super.dispose(); + if (_editDescriptionOperation != null) _editDescriptionOperation.cancel(); + } + + @override + Widget build(BuildContext context) { + var openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + _validationService = openbookProvider.validationService; + + return OBCupertinoPageScaffold( + navigationBar: _buildNavigationBar(), + child: OBPrimaryColorContainer( + child: SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + children: [ + OBTextFormField( + textCapitalization: + TextCapitalization.sentences, + size: OBTextFormFieldSize.large, + autofocus: true, + controller: _descriptionController, + decoration: InputDecoration( + labelText: 'Report description', + hintText: + 'e.g. The report item was found to...'), + validator: (String description) { + if (!_formWasSubmitted) return null; + return _validationService + .validateModeratedObjectDescription( + description); + }), + ], + )), + ], + )), + ), + )); + } + + Widget _buildNavigationBar() { + return OBThemedNavigationBar( + leading: GestureDetector( + child: const OBIcon(OBIcons.close), + onTap: () { + Navigator.pop(context); + }, + ), + title: 'Edit description', + trailing: OBButton( + isDisabled: !_formValid, + isLoading: _requestInProgress, + size: OBButtonSize.small, + onPressed: _submitForm, + child: Text('Save'), + )); + } + + bool _validateForm() { + return _formKey.currentState.validate(); + } + + bool _updateFormValid() { + var formValid = _validateForm(); + _setFormValid(formValid); + return formValid; + } + + void _submitForm() async { + _formWasSubmitted = true; + + var formIsValid = _updateFormValid(); + if (!formIsValid) return; + _setRequestInProgress(true); + try { + _editDescriptionOperation = CancelableOperation.fromFuture( + _userService.updateModeratedObject(widget.moderatedObject, + description: _descriptionController.text)); + + Navigator.of(context).pop(); + } catch (error) { + _onError(error); + } finally { + _setRequestInProgress(false); + _editDescriptionOperation = null; + } + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } + + void _setRequestInProgress(bool requestInProgress) { + setState(() { + _requestInProgress = requestInProgress; + }); + } + + void _setFormValid(bool formValid) { + setState(() { + _formValid = formValid; + }); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart new file mode 100644 index 000000000..d2093cc65 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart @@ -0,0 +1,130 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_report.dart'; +import 'package:Openbook/models/moderation/moderation_report_list.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectReportsPreview extends StatefulWidget { + final bool isEditable; + final ModeratedObject moderatedObject; + + const OBModeratedObjectReportsPreview( + {Key key, @required this.moderatedObject, @required this.isEditable}) + : super(key: key); + + @override + OBModeratedObjectReportsPreviewState createState() { + return OBModeratedObjectReportsPreviewState(); + } +} + +class OBModeratedObjectReportsPreviewState + extends State { + bool _needsBootstrap; + UserService _userService; + ToastService _toastService; + + CancelableOperation _refreshReportsOperation; + bool _refreshInProgress; + List _reports; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _refreshInProgress = false; + _reports = []; + } + + @override + void dispose() { + super.dispose(); + if (_refreshReportsOperation != null) _refreshReportsOperation.cancel(); + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + _refreshReports(); + _needsBootstrap = false; + _refreshInProgress = true; + } + return _refreshInProgress + ? Row( + children: [ + Padding( + padding: EdgeInsets.all(20), + child: OBProgressIndicator(), + ) + ], + ) + : ListView.builder( + itemBuilder: _buildModerationReport, + itemCount: _reports.length, + ); + } + + Widget _buildModerationReport(BuildContext contenxt, int index) { + ModerationReport report = _reports[index]; + return ListTile( + title: OBSecondaryText(report.reporter.username), + subtitle: Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBText(report.description), + OBSecondaryText(report.category.title) + ], + ), + isThreeLine: true, + ); + } + + Future _refreshReports() async { + _setRefreshInProgress(true); + try { + _refreshReportsOperation = CancelableOperation.fromFuture(_userService + .getModeratedObjectReports(widget.moderatedObject, count: 5)); + + ModerationReportsList moderationReportsList = + await _refreshReportsOperation.value; + _setReports(moderationReportsList.moderationReports); + } catch (error) { + _onError(error); + } + _setRefreshInProgress(false); + } + + void _setRefreshInProgress(bool refreshInProgress) { + setState(() { + _refreshInProgress = refreshInProgress; + }); + } + + void _setReports(List reports) { + setState(() { + _reports = reports; + }); + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart new file mode 100644 index 000000000..8286612d2 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart @@ -0,0 +1,129 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_report.dart'; +import 'package:Openbook/models/moderation/moderation_report_list.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectReportsPage extends StatefulWidget { + final ModeratedObject moderatedObject; + + const OBModeratedObjectReportsPage( + {Key key, @required this.moderatedObject}) + : super(key: key); + + @override + OBModeratedObjectReportsPageState createState() { + return OBModeratedObjectReportsPageState(); + } +} + +class OBModeratedObjectReportsPageState + extends State { + bool _needsBootstrap; + UserService _userService; + ToastService _toastService; + + CancelableOperation _refreshReportsOperation; + bool _refreshInProgress; + List _reports; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _refreshInProgress = false; + _reports = []; + } + + @override + void dispose() { + super.dispose(); + if (_refreshReportsOperation != null) _refreshReportsOperation.cancel(); + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + _refreshReports(); + _needsBootstrap = false; + _refreshInProgress = true; + } + return _refreshInProgress + ? Row( + children: [ + Padding( + padding: EdgeInsets.all(20), + child: OBProgressIndicator(), + ) + ], + ) + : ListView.builder( + itemBuilder: _buildModerationReport, + itemCount: _reports.length, + ); + } + + Widget _buildModerationReport(BuildContext contenxt, int index) { + ModerationReport report = _reports[index]; + return ListTile( + title: OBSecondaryText(report.reporter.username), + subtitle: Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBText(report.description), + OBSecondaryText(report.category.title) + ], + ), + isThreeLine: true, + ); + } + + Future _refreshReports() async { + _setRefreshInProgress(true); + try { + _refreshReportsOperation = CancelableOperation.fromFuture(_userService + .getModeratedObjectReports(widget.moderatedObject, count: 5)); + + ModerationReportsList moderationReportsList = + await _refreshReportsOperation.value; + _setReports(moderationReportsList.moderationReports); + } catch (error) { + _onError(error); + } + _setRefreshInProgress(false); + } + + void _setRefreshInProgress(bool refreshInProgress) { + setState(() { + _refreshInProgress = refreshInProgress; + }); + } + + void _setReports(List reports) { + setState(() { + _reports = reports; + }); + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } +} diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart new file mode 100644 index 000000000..b2352f276 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -0,0 +1,74 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart'; +import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; +import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; +import 'package:Openbook/widgets/theming/divider.dart'; +import 'package:Openbook/widgets/theming/post_divider.dart'; +import 'package:Openbook/widgets/tiles/community_tile.dart'; +import 'package:Openbook/widgets/tiles/user_tile.dart'; +import 'package:flutter/cupertino.dart'; + +class OBModeratedObject extends StatelessWidget { + final ModeratedObject moderatedObject; + final Community community; + + const OBModeratedObject( + {Key key, @required this.moderatedObject, this.community}) + : super(key: key); + + @override + Widget build(BuildContext context) { + Widget widget; + + switch (moderatedObject.type) { + case ModeratedObjectType.post: + widget = Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBPostHeader( + post: moderatedObject.contentObject, + ), + OBPostBody(moderatedObject.contentObject), + ], + ); + break; + case ModeratedObjectType.community: + widget = Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBCommunityTile(moderatedObject.contentObject), + ], + ); + break; + case ModeratedObjectType.postComment: + widget = Column( + children: [ + OBCommunityTile(community), + ], + ); + break; + case ModeratedObjectType.user: + widget = Column( + children: [ + OBUserTile(moderatedObject.contentObject), + ], + ); + break; + default: + widget = const SizedBox(); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + widget, + OBModeratedObjectActions( + moderatedObject: moderatedObject, + community: community, + ), + OBDivider() + ], + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart new file mode 100644 index 000000000..4779044ca --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart @@ -0,0 +1,64 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectActions extends StatelessWidget { + final Community community; + final ModeratedObject moderatedObject; + + OBModeratedObjectActions( + {@required this.community, @required this.moderatedObject}); + + @override + Widget build(BuildContext context) { + List moderatedObjectActions = [ + Expanded( + child: OBButton( + type: OBButtonType.highlight, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const OBIcon( + OBIcons.reviewModeratedObject, + customSize: 20.0, + ), + const SizedBox( + width: 10.0, + ), + const OBText('Review'), + ], + ), + onPressed: () { + OpenbookProviderState openbookProvider = + OpenbookProvider.of(context); + + if (community != null) { + openbookProvider.navigationService + .navigateToModeratedObjectCommunityReview( + moderatedObject: moderatedObject, + community: community, + context: context); + } else { + openbookProvider.navigationService + .navigateToModeratedObjectGlobalReview( + moderatedObject: moderatedObject, context: context); + } + })), + ]; + + return Padding( + padding: EdgeInsets.only(left: 20.0, top: 10.0, right: 20.0), + child: Column( + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: moderatedObjectActions, + ) + ], + )); + } +} diff --git a/lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart b/lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart new file mode 100644 index 000000000..87b6ecea2 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart @@ -0,0 +1,23 @@ +import 'package:Openbook/models/user.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/alerts/button_alert.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:flutter/material.dart'; + +class OBNoModeratedObjects extends StatelessWidget { + final VoidCallback onWantsToRefreshModeratedObjects; + + OBNoModeratedObjects({@required this.onWantsToRefreshModeratedObjects}); + + @override + Widget build(BuildContext context) { + return OBButtonAlert( + text: 'No moderation items', + onPressed: onWantsToRefreshModeratedObjects, + buttonText: 'Refresh', + buttonIcon: OBIcons.refresh, + assetImage: 'assets/images/stickers/perplexed-owl.png', + ); + } +} diff --git a/lib/pages/home/pages/report_object/report_object.dart b/lib/pages/home/pages/report_object/report_object.dart index 4d5ed1d72..85af0a651 100644 --- a/lib/pages/home/pages/report_object/report_object.dart +++ b/lib/pages/home/pages/report_object/report_object.dart @@ -2,8 +2,6 @@ import 'package:Openbook/libs/type_to_str.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/moderation/moderation_category_list.dart'; import 'package:Openbook/services/navigation_service.dart'; -import 'package:Openbook/services/theme.dart'; -import 'package:Openbook/services/theme_value_parser.dart'; import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/provider.dart'; diff --git a/lib/services/modal_service.dart b/lib/services/modal_service.dart index cde037736..de5e1b6f6 100644 --- a/lib/services/modal_service.dart +++ b/lib/services/modal_service.dart @@ -24,6 +24,8 @@ import 'package:Openbook/pages/home/pages/community/pages/manage_community/pages import 'package:Openbook/pages/home/pages/community/pages/manage_community/pages/community_moderators/modals/add_community_moderator/add_community_moderator.dart'; import 'package:Openbook/pages/home/modals/user_invites/create_user_invite.dart'; import 'package:Openbook/pages/home/modals/user_invites/send_invite_email.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/moderated_objects.dart'; import 'package:Openbook/pages/home/pages/timeline/timeline.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -86,23 +88,22 @@ class ModalService { Future openExpandedReplyCommenter( {@required BuildContext context, - @required PostComment postComment, - @required Post post, - @required Function(PostComment) onReplyAdded, - @required Function(PostComment) onReplyDeleted}) async { + @required PostComment postComment, + @required Post post, + @required Function(PostComment) onReplyAdded, + @required Function(PostComment) onReplyDeleted}) async { PostComment replyComment = await Navigator.of(context, rootNavigator: true) .push(CupertinoPageRoute( - fullscreenDialog: true, - builder: (BuildContext context) { - return Material( - child: OBPostCommentReplyExpandedModal( - post: post, - postComment: postComment, - onReplyAdded: onReplyAdded, - onReplyDeleted: onReplyDeleted - ), - ); - })); + fullscreenDialog: true, + builder: (BuildContext context) { + return Material( + child: OBPostCommentReplyExpandedModal( + post: post, + postComment: postComment, + onReplyAdded: onReplyAdded, + onReplyDeleted: onReplyDeleted), + ); + })); return replyComment; } @@ -330,6 +331,21 @@ class ModalService { })); } + Future openModeratedObjectsFilters( + {@required + BuildContext context, + @required + OBModeratedObjectsPageController + moderatedObjectsPageController}) async { + await Navigator.of(context).push(CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) { + return OBModeratedObjectsFiltersModal( + moderatedObjectsPageController: moderatedObjectsPageController, + ); + })); + } + Future openAcceptGuidelines({@required BuildContext context}) async { await Navigator.of(context).push(CupertinoPageRoute( fullscreenDialog: true, diff --git a/lib/services/moderation_api.dart b/lib/services/moderation_api.dart index 1743e1d79..99e45dff1 100644 --- a/lib/services/moderation_api.dart +++ b/lib/services/moderation_api.dart @@ -20,6 +20,10 @@ class ModerationApiService { 'api/moderation/moderated_objects/{moderatedObjectId}/verify/'; static const UNVERIFY_MODERATED_OBJECT_PATH = 'api/moderation/moderated_objects/{moderatedObjectId}/unverify/'; + static const MODERATED_OBJECT_LOGS_PATH = + 'api/moderation/moderated_objects/{moderatedObjectId}/logs/'; + static const MODERATED_OBJECT_REPORTS_PATH = + 'api/moderation/moderated_objects/{moderatedObjectId}/reports/'; void setHttpieService(HttpieService httpService) { _httpService = httpService; @@ -58,6 +62,38 @@ class ModerationApiService { queryParameters: queryParams, appendAuthorizationToken: true); } + Future getModeratedObjectLogs( + int moderatedObjectId, { + int count, + int maxId, + }) { + Map queryParams = {}; + if (count != null) queryParams['count'] = count; + + if (maxId != null) queryParams['max_id'] = maxId; + + String path = _makeModeratedObjectLogsPath(moderatedObjectId); + + return _httpService.get(_makeApiUrl(path), + queryParameters: queryParams, appendAuthorizationToken: true); + } + + Future getModeratedObjectReports( + int moderatedObjectId, { + int count, + int maxId, + }) { + Map queryParams = {}; + if (count != null) queryParams['count'] = count; + + if (maxId != null) queryParams['max_id'] = maxId; + + String path = _makeModeratedObjectReportsPath(moderatedObjectId); + + return _httpService.get(_makeApiUrl(path), + queryParameters: queryParams, appendAuthorizationToken: true); + } + Future getModerationCategories() { String path = GET_MODERATION_CATEGORIES_PATH; @@ -122,6 +158,16 @@ class ModerationApiService { {'moderatedObjectId': moderatedObjectId}); } + String _makeModeratedObjectLogsPath(int moderatedObjectId) { + return _stringTemplateService.parse( + MODERATED_OBJECT_LOGS_PATH, {'moderatedObjectId': moderatedObjectId}); + } + + String _makeModeratedObjectReportsPath(int moderatedObjectId) { + return _stringTemplateService.parse(MODERATED_OBJECT_REPORTS_PATH, + {'moderatedObjectId': moderatedObjectId}); + } + String _makeRejectModeratedObjectsPath(int moderatedObjectId) { return _stringTemplateService.parse( REJECT_MODERATED_OBJECT_PATH, {'moderatedObjectId': moderatedObjectId}); diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index cece84288..ef1b0e3f2 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -2,6 +2,7 @@ import 'package:Openbook/models/circle.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/emoji.dart'; import 'package:Openbook/models/follows_list.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; @@ -43,6 +44,12 @@ import 'package:Openbook/pages/home/pages/menu/pages/useful_links.dart'; import 'package:Openbook/pages/home/pages/menu/pages/user_invites/pages/user_invite_detail.dart'; import 'package:Openbook/pages/home/pages/menu/pages/user_invites/user_invites.dart'; import 'package:Openbook/pages/home/pages/menu/pages/themes/themes.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/moderated_objects.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart'; import 'package:Openbook/pages/home/pages/notifications/pages/notifications_settings.dart'; import 'package:Openbook/pages/home/pages/post/post.dart'; import 'package:Openbook/pages/home/pages/post_comments/post_comments_page.dart'; @@ -548,6 +555,88 @@ class NavigationService { ))); } + Future navigateToCommunityModeratedObjects( + {@required BuildContext context, @required Community community}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obCommunityModeratedObjects'), + widget: OBModeratedObjectsPage( + community: community, + ))); + } + + Future navigateToGlobalModeratedObjects( + {@required BuildContext context}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obGlobalModeratedObjects'), + widget: OBModeratedObjectsPage())); + } + + Future navigateToModeratedObjectUpdateDescription( + {@required BuildContext context, + @required ModeratedObject moderatedObject}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obModeratedObjectUpdateDescriptionPage'), + widget: OBModeratedObjectUpdateDescriptionPage( + moderatedObject: moderatedObject, + ))); + } + + Future navigateToModeratedObjectUpdateCategory( + {@required BuildContext context, + @required ModeratedObject moderatedObject}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obModeratedObjectUpdateCategoryPage'), + widget: OBModeratedObjectUpdateCategoryPage( + moderatedObject: moderatedObject, + ))); + } + + Future navigateToModeratedObjectReports( + {@required BuildContext context, + @required ModeratedObject moderatedObject}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obModeratedObjectReportsPage'), + widget: OBModeratedObjectReportsPage( + moderatedObject: moderatedObject, + ))); + } + + Future navigateToModeratedObjectGlobalReview( + {@required BuildContext context, + @required ModeratedObject moderatedObject}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obModeratedObjectGlobalReviewPage'), + widget: OBModeratedObjectGlobalReviewPage( + moderatedObject: moderatedObject, + ))); + } + + Future navigateToModeratedObjectCommunityReview( + {@required BuildContext context, + @required Community community, + @required ModeratedObject moderatedObject}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obModeratedObjectCommunityReviewPage'), + widget: OBModeratedObjectCommunityReviewPage( + community: community, + moderatedObject: moderatedObject, + ))); + } + Future navigateToBlankPageWithWidget( {@required BuildContext context, @required String navBarTitle, diff --git a/lib/services/user.dart b/lib/services/user.dart index 91d207a3c..36e3e562f 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -19,8 +19,10 @@ import 'package:Openbook/models/follow.dart'; import 'package:Openbook/models/follows_list.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/moderation/moderated_object_list.dart'; +import 'package:Openbook/models/moderation/moderated_object_log_list.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/moderation/moderation_category_list.dart'; +import 'package:Openbook/models/moderation/moderation_report_list.dart'; import 'package:Openbook/models/notifications/notification.dart'; import 'package:Openbook/models/notifications/notifications_list.dart'; import 'package:Openbook/models/post.dart'; @@ -489,8 +491,8 @@ class UserService { Future replyPostComment( {@required Post post, - @required PostComment postComment, - @required String text}) async { + @required PostComment postComment, + @required String text}) async { HttpieResponse response = await _postsApiService.replyPostComment( postUuid: post.uuid, postCommentId: postComment.id, text: text); _checkResponseIsCreated(response); @@ -538,23 +540,22 @@ class UserService { return PostCommentList.fromJson(json.decode(response.body)); } - Future getCommentRepliesForPostComment(Post post, - PostComment postComment, + Future getCommentRepliesForPostComment( + Post post, PostComment postComment, {int maxId, - int countMax, - int minId, - int countMin, - PostCommentsSortType sort}) async { - HttpieResponse response = await _postsApiService.getRepliesForCommentWithIdForPostWithUuid( - post.uuid, - postComment.id, - countMax: countMax, - maxId: maxId, - countMin: countMin, - minId: minId, - sort: sort != null - ? PostComment.convertPostCommentSortTypeToString(sort) - : null); + int countMax, + int minId, + int countMin, + PostCommentsSortType sort}) async { + HttpieResponse response = await _postsApiService + .getRepliesForCommentWithIdForPostWithUuid(post.uuid, postComment.id, + countMax: countMax, + maxId: maxId, + countMin: countMin, + minId: minId, + sort: sort != null + ? PostComment.convertPostCommentSortTypeToString(sort) + : null); _checkResponseIsOk(response); return PostCommentList.fromJson(json.decode(response.body)); @@ -1525,7 +1526,7 @@ class UserService { return ModeratedObjectsList.fromJson(json.decode(response.body)); } - Future getModeratedObjectsForCommunity( + Future getCommunityModeratedObjects( {@required Community community, List statuses, List types, @@ -1566,6 +1567,29 @@ class UserService { _checkResponseIsCreated(response); } + Future getModeratedObjectLogs( + ModeratedObject moderatedObject, + {int maxId, + int count}) async { + HttpieResponse response = await _moderationApiService + .getModeratedObjectLogs(moderatedObject.id, maxId: maxId, count: count); + _checkResponseIsOk(response); + + return ModeratedObjectLogsList.fromJson(json.decode(response.body)); + } + + Future getModeratedObjectReports( + ModeratedObject moderatedObject, + {int maxId, + int count}) async { + HttpieResponse response = await _moderationApiService + .getModeratedObjectReports(moderatedObject.id, + maxId: maxId, count: count); + _checkResponseIsOk(response); + + return ModerationReportsList.fromJson(json.decode(response.body)); + } + Future unverifyModeratedObject(ModeratedObject moderatedObject) async { HttpieResponse response = await _moderationApiService .unverifyModeratedObjectWithId(moderatedObject.id); diff --git a/lib/services/validation.dart b/lib/services/validation.dart index ba5e3c37c..da52ef787 100644 --- a/lib/services/validation.dart +++ b/lib/services/validation.dart @@ -27,6 +27,7 @@ class ValidationService { static const int COLOR_ATTR_MAX_LENGTH = 7; static const int LIST_MAX_LENGTH = 100; static const int PROFILE_NAME_MAX_LENGTH = 192; + static const int MODERATED_OBJECT_DESCRIPTION_MAX_LENGTH = 1000; static const int PROFILE_NAME_MIN_LENGTH = 1; static const int PROFILE_LOCATION_MAX_LENGTH = 64; static const int PROFILE_BIO_MAX_LENGTH = 150; @@ -95,7 +96,8 @@ class ValidationService { } bool isLocationAllowedLength(String location) { - return location.length > 0 && location.length <= PROFILE_LOCATION_MAX_LENGTH; + return location.length > 0 && + location.length <= PROFILE_LOCATION_MAX_LENGTH; } bool isUsernameAllowedLength(String username) { @@ -219,6 +221,11 @@ class ValidationService { name.length <= PROFILE_NAME_MAX_LENGTH; } + bool isModeratedObjectDescriptionAllowedLength( + String moderatedObjectDescription) { + return moderatedObjectDescription.length <= PROFILE_NAME_MAX_LENGTH; + } + Future isImageAllowedSize(File image, OBImageType type) async { int size = await image.length(); return size <= getAllowedImageSize(type); @@ -324,6 +331,20 @@ class ValidationService { return errorMsg; } + String validateModeratedObjectDescription(String description) { + assert(description != null); + + String errorMsg; + + if (description.isEmpty) { + errorMsg = 'Description can\'t be empty.'; + } else if (!isModeratedObjectDescriptionAllowedLength(description)) { + errorMsg = + 'Description must be between $PROFILE_NAME_MIN_LENGTH and $PROFILE_NAME_MAX_LENGTH characters.'; + } + return errorMsg; + } + String validateUserProfileUrl(String url) { assert(url != null); diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index ddc7f71f6..7133ed536 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -196,6 +196,7 @@ class OBIcons { static const expand = OBIconData(filename: 'expand-icon.png'); static const mutePost = OBIconData(nativeIcon: Icons.notifications_active); static const editPost = OBIconData(nativeIcon: Icons.edit); + static const reviewModeratedObject = OBIconData(nativeIcon: Icons.edit); static const unmutePost = OBIconData(nativeIcon: Icons.notifications_off); static const deleteAccount = OBIconData(nativeIcon: Icons.delete_forever); static const account = OBIconData(nativeIcon: Icons.account_circle); @@ -210,7 +211,8 @@ class OBIcons { static const themes = OBIconData(nativeIcon: Icons.format_paint); static const invite = OBIconData(nativeIcon: Icons.card_giftcard); static const disableComments = OBIconData(nativeIcon: Icons.chat_bubble); - static const enableComments = OBIconData(nativeIcon: Icons.chat_bubble_outline); + static const enableComments = + OBIconData(nativeIcon: Icons.chat_bubble_outline); static const closePost = OBIconData(nativeIcon: Icons.lock_outline); static const openPost = OBIconData(nativeIcon: Icons.lock_open); static const block = OBIconData(nativeIcon: Icons.block); diff --git a/pubspec.lock b/pubspec.lock index 36a20b472..9b86ad845 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -395,7 +395,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.4.0" petitparser: dependency: transitive description: @@ -526,7 +526,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.5.4" sprintf: dependency: "direct main" description: @@ -703,5 +703,5 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.1.1-dev.0.0 <3.0.0" + dart: ">=2.1.0 <3.0.0" flutter: ">=1.2.1 <2.0.0" From 1c0f36f75bcb22bc995f48d2c2287baa6cb3ad5c Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Fri, 31 May 2019 17:10:59 +0200 Subject: [PATCH 39/65] :sparkles: basic moderated objects timeline working --- .../manage_community/manage_community.dart | 1 - .../moderated_objects/moderated_objects.dart | 9 +- .../moderated_object/moderated_object.dart | 8 +- lib/services/moderation_api.dart | 16 ++-- pubspec.lock | 89 ++++++++++--------- 5 files changed, 69 insertions(+), 54 deletions(-) diff --git a/lib/pages/home/pages/community/pages/manage_community/manage_community.dart b/lib/pages/home/pages/community/pages/manage_community/manage_community.dart index e6b3da333..d6bfbe720 100644 --- a/lib/pages/home/pages/community/pages/manage_community/manage_community.dart +++ b/lib/pages/home/pages/community/pages/manage_community/manage_community.dart @@ -14,7 +14,6 @@ import 'package:flutter/material.dart'; class OBManageCommunityPage extends StatelessWidget { final Community community; - const OBManageCommunityPage({@required this.community}); @override diff --git a/lib/pages/home/pages/moderated_objects/moderated_objects.dart b/lib/pages/home/pages/moderated_objects/moderated_objects.dart index d806300e2..6e746ee9a 100644 --- a/lib/pages/home/pages/moderated_objects/moderated_objects.dart +++ b/lib/pages/home/pages/moderated_objects/moderated_objects.dart @@ -31,6 +31,7 @@ class OBModeratedObjectsPageState extends State { static int itemsLoadMoreCount = 10; OBModeratedObjectsPageController _controller; + Community _community; OBModeratedObjectsFilters _filters; ScrollController _scrollController; @@ -149,7 +150,11 @@ class OBModeratedObjectsPageState extends State { key: Key(moderatedObject.id.toString())); } - void _bootstrap() {} + void _bootstrap() { + Future.delayed(Duration(milliseconds: 100), () { + _refresh(); + }); + } Future setFilters(OBModeratedObjectsFilters filters) { _filters = filters; @@ -176,7 +181,7 @@ class OBModeratedObjectsPageState extends State { Future _refreshModeratedObjects() async { try { - if (widget.community != null) { + if (widget.community == null) { _refreshModeratedObjectsOperation = CancelableOperation.fromFuture( _userService.getGlobalModeratedObjects(count: itemsLoadMoreCount)); } else { diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index b2352f276..4992acb72 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -1,6 +1,8 @@ import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; import 'package:Openbook/widgets/theming/divider.dart'; @@ -42,9 +44,13 @@ class OBModeratedObject extends StatelessWidget { ); break; case ModeratedObjectType.postComment: + PostComment postComment = moderatedObject.contentObject; widget = Column( children: [ - OBCommunityTile(community), + OBPostComment( + post: postComment.post, + postComment: moderatedObject.contentObject, + ), ], ); break; diff --git a/lib/services/moderation_api.dart b/lib/services/moderation_api.dart index 99e45dff1..c1d466631 100644 --- a/lib/services/moderation_api.dart +++ b/lib/services/moderation_api.dart @@ -8,22 +8,22 @@ class ModerationApiService { String apiURL; static const GET_GLOBAL_MODERATED_OBJECTS_PATH = - 'api/moderation/moderated_objects/'; + 'api/moderation/moderated-objects/global/'; static const GET_MODERATION_CATEGORIES_PATH = 'api/moderation/categories/'; static const MODERATED_OBJECT_PATH = - 'api/moderation/moderated_objects/{moderatedObjectId}/'; + 'api/moderation/moderated-objects/{moderatedObjectId}/'; static const APPROVE_MODERATED_OBJECT_PATH = - 'api/moderation/moderated_objects/{moderatedObjectId}/approve/'; + 'api/moderation/moderated-objects/{moderatedObjectId}/approve/'; static const REJECT_MODERATED_OBJECT_PATH = - 'api/moderation/moderated_objects/{moderatedObjectId}/reject/'; + 'api/moderation/moderated-objects/{moderatedObjectId}/reject/'; static const VERIFY_MODERATED_OBJECT_PATH = - 'api/moderation/moderated_objects/{moderatedObjectId}/verify/'; + 'api/moderation/moderated-objects/{moderatedObjectId}/verify/'; static const UNVERIFY_MODERATED_OBJECT_PATH = - 'api/moderation/moderated_objects/{moderatedObjectId}/unverify/'; + 'api/moderation/moderated-objects/{moderatedObjectId}/unverify/'; static const MODERATED_OBJECT_LOGS_PATH = - 'api/moderation/moderated_objects/{moderatedObjectId}/logs/'; + 'api/moderation/moderated-objects/{moderatedObjectId}/logs/'; static const MODERATED_OBJECT_REPORTS_PATH = - 'api/moderation/moderated_objects/{moderatedObjectId}/reports/'; + 'api/moderation/moderated-objects/{moderatedObjectId}/reports/'; void setHttpieService(HttpieService httpService) { _httpService = httpService; diff --git a/pubspec.lock b/pubspec.lock index 9b86ad845..7f2ec1c89 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,21 +7,21 @@ packages: name: after_layout url: "https://pub.dartlang.org" source: hosted - version: "1.0.7" + version: "1.0.7+1" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.35.4" + version: "0.36.3" archive: dependency: transitive description: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.0.9" args: dependency: transitive description: @@ -35,7 +35,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0" back_button_interceptor: dependency: "direct main" description: @@ -85,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.6" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" cupertino_icons: dependency: "direct main" description: @@ -92,15 +99,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" - dart_config: - dependency: transitive - description: - path: "." - ref: HEAD - resolved-ref: a7ed88a4793e094a4d5d5c2d88a89e55510accde - url: "https://github.com/MarkOSullivan94/dart_config.git" - source: git - version: "0.5.0" dcache: dependency: "direct main" description: @@ -114,7 +112,7 @@ packages: name: device_info url: "https://pub.dartlang.org" source: hosted - version: "0.4.0+1" + version: "0.4.0+2" flutter: dependency: "direct main" description: flutter @@ -140,21 +138,21 @@ packages: name: flutter_colorpicker url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.5" flutter_exif_rotation: dependency: "direct main" description: name: flutter_exif_rotation url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.2.2" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.7.0" + version: "0.7.2" flutter_localizations: dependency: "direct main" description: flutter @@ -194,7 +192,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.12.4" + version: "0.12.4+1" flutter_test: dependency: "direct dev" description: flutter @@ -206,7 +204,7 @@ packages: name: front_end url: "https://pub.dartlang.org" source: hosted - version: "0.1.14" + version: "0.1.18" glob: dependency: transitive description: @@ -214,6 +212,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.7" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+2" http: dependency: "direct main" description: @@ -241,14 +246,14 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.0.7" + version: "2.1.3" image_cropper: dependency: "direct main" description: name: image_cropper url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.0.2" image_picker: dependency: "direct main" description: @@ -269,7 +274,7 @@ packages: name: intl url: "https://pub.dartlang.org" source: hosted - version: "0.15.7" + version: "0.15.8" io: dependency: transitive description: @@ -297,7 +302,7 @@ packages: name: kernel url: "https://pub.dartlang.org" source: hosted - version: "0.3.14" + version: "0.3.18" markdown: dependency: transitive description: @@ -311,7 +316,7 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.3+1" + version: "0.12.5" meta: dependency: transitive description: @@ -325,7 +330,7 @@ packages: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.6+2" + version: "0.9.6+3" multi_server_socket: dependency: transitive description: @@ -374,14 +379,14 @@ packages: name: path_drawing url: "https://pub.dartlang.org" source: hosted - version: "0.4.0" + version: "0.4.1" path_parsing: dependency: transitive description: name: path_parsing url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.4" path_provider: dependency: transitive description: @@ -395,14 +400,14 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.0" petitparser: dependency: transitive description: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.2.1" photo_view: dependency: "direct main" description: @@ -437,7 +442,7 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.0.2" rxdart: dependency: "direct main" description: @@ -465,7 +470,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.5.2" + version: "0.5.2+2" shelf: dependency: transitive description: @@ -526,7 +531,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.4" + version: "1.5.5" sprintf: dependency: "direct main" description: @@ -554,7 +559,7 @@ packages: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "1.6.8" + version: "2.0.0" string_scanner: dependency: transitive description: @@ -582,28 +587,28 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "1.6.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.2" + version: "0.2.4" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.2.1+1" + version: "0.2.3" timeago: dependency: "direct main" description: name: timeago url: "https://pub.dartlang.org" source: hosted - version: "2.0.14" + version: "2.0.16" tinycolor: dependency: "direct main" description: @@ -666,7 +671,7 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "0.10.0+8" + version: "0.10.1+2" vm_service_client: dependency: transitive description: @@ -687,14 +692,14 @@ packages: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.0.12" + version: "1.0.13" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "3.3.1" + version: "3.4.1" yaml: dependency: transitive description: @@ -703,5 +708,5 @@ packages: source: hosted version: "2.1.15" sdks: - dart: ">=2.1.0 <3.0.0" - flutter: ">=1.2.1 <2.0.0" + dart: ">=2.2.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" From 573397900867ada8a7fb477588befc106cf34ef6 Mon Sep 17 00:00:00 2001 From: Shantanu Date: Sat, 1 Jun 2019 09:39:10 -0700 Subject: [PATCH 40/65] :pencil2: fix typo randing -> ranging --- .../home/pages/report_object/pages/confirm_report_object.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart index b1b147aee..ad410291d 100644 --- a/lib/pages/home/pages/report_object/pages/confirm_report_object.dart +++ b/lib/pages/home/pages/report_object/pages/confirm_report_object.dart @@ -126,7 +126,7 @@ class OBConfirmReportObjectState extends State { data: '- Your report will be submitted anonymously. \n ' '- If you are reporting a post or comment, the report will be sent to the Openbook staff and the community moderators if applicable and the post will be hidden from your feed \n' '- If you are reporting an account or community, it will be sent to the Openbook staff. \n' - '- We\'ll review it, if approved, content will be deleted and penalties delivered to the people involved randing from deletion of account to hours of suspension depending on the severity of the report. \n' + '- We\'ll review it, if approved, content will be deleted and penalties delivered to the people involved ranging from deletion of account to hours of suspension depending on the severity of the report. \n' '- If the report is found to be made in an attempt to damage another member or community in the platform with no infringement of the stated reason, penalties will be applied to you. \n') ], ), From 0dabeb2f7632681b47684f602d47de3de2f36dc6 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Sat, 1 Jun 2019 19:36:14 +0200 Subject: [PATCH 41/65] :sparkles: description n category update working! --- lib/models/moderation/moderated_object.dart | 6 +- lib/models/moderation/moderation_report.dart | 3 + .../moderated_objects/moderated_objects.dart | 2 +- .../pages/moderated_object_global_review.dart | 11 +- .../moderated_object_category.dart | 1 + .../moderated_object_update_category.dart | 23 ++-- .../moderated_object_description.dart | 10 +- .../moderated_object_reports_preview.dart | 129 +++++++++++++++--- .../moderated_object/moderated_object.dart | 1 + lib/services/moderation_api.dart | 4 +- lib/services/user.dart | 7 +- .../post/widgets/post_header/post_header.dart | 16 ++- .../widgets/community_post_header.dart | 26 ++-- .../post_header/widgets/user_post_header.dart | 26 ++-- 14 files changed, 194 insertions(+), 71 deletions(-) diff --git a/lib/models/moderation/moderated_object.dart b/lib/models/moderation/moderated_object.dart index c4713b32e..58d7fff85 100644 --- a/lib/models/moderation/moderated_object.dart +++ b/lib/models/moderation/moderated_object.dart @@ -18,7 +18,7 @@ class ModeratedObject extends UpdatableModel { static String objectTypePost = 'P'; static String objectTypePostComment = 'PC'; static String objectTypeCommunity = 'C'; - static String objectTypeUser = 'C'; + static String objectTypeUser = 'U'; static String statusPending = 'P'; static String statusApproved = 'A'; static String statusRejected = 'R'; @@ -50,6 +50,10 @@ class ModeratedObject extends UpdatableModel { description = json['description']; } + if (json.containsKey('category')) { + category = factory.parseModerationCategory(json['category']); + } + if (json.containsKey('verified')) { verified = json['verified']; } diff --git a/lib/models/moderation/moderation_report.dart b/lib/models/moderation/moderation_report.dart index 01d754e21..b93554810 100644 --- a/lib/models/moderation/moderation_report.dart +++ b/lib/models/moderation/moderation_report.dart @@ -12,6 +12,7 @@ class ModerationReport { {this.description, this.reporter, this.category, this.created}); factory ModerationReport.fromJson(Map parsedJson) { + print(parsedJson); return ModerationReport( description: parsedJson['description'], reporter: parseReporter( @@ -30,6 +31,8 @@ class ModerationReport { static ModerationCategory parseCategory(Map rawModerationCategory) { if (rawModerationCategory == null) return null; + print('PARSIN'); + print(rawModerationCategory); return ModerationCategory.fromJson(rawModerationCategory); } diff --git a/lib/pages/home/pages/moderated_objects/moderated_objects.dart b/lib/pages/home/pages/moderated_objects/moderated_objects.dart index 6e746ee9a..9d9c538f3 100644 --- a/lib/pages/home/pages/moderated_objects/moderated_objects.dart +++ b/lib/pages/home/pages/moderated_objects/moderated_objects.dart @@ -208,7 +208,7 @@ class OBModeratedObjectsPageState extends State { } try { - if (widget.community != null) { + if (widget.community == null) { _loadMoreOperation = CancelableOperation.fromFuture( _userService.getGlobalModeratedObjects( maxId: lastModeratedObjectId, count: itemsLoadMoreCount)); diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index 3c964c711..c6939ed58 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -41,6 +41,7 @@ class OBModeratedObjectGlobalReviewPageState super.initState(); _needsBootstrap = true; _isEditable = false; + _requestInProgress = false; } @override @@ -56,8 +57,7 @@ class OBModeratedObjectGlobalReviewPageState navigationBar: OBThemedNavigationBar( title: 'Review moderated object', ), - child: OBPrimaryColorContainer( - child: Column( + child: Column( children: [ Expanded( child: ListView( @@ -78,11 +78,11 @@ class OBModeratedObjectGlobalReviewPageState ), ), Padding( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), child: _buildPrimaryActions(), ) ], - )), + ), ); } @@ -103,7 +103,7 @@ class OBModeratedObjectGlobalReviewPageState actions.add(Expanded( child: OBButton( size: OBButtonSize.large, - type: OBButtonType.danger, + type: OBButtonType.success, child: Text('Verify'), onPressed: _onWantsToVerifyModeratedObject, isLoading: _requestInProgress, @@ -112,6 +112,7 @@ class OBModeratedObjectGlobalReviewPageState } return Row( + mainAxisSize: MainAxisSize.max, children: actions, ); } diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart index a2109b33a..6e0380f7c 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart @@ -16,6 +16,7 @@ class OBModeratedObjectCategory extends StatelessWidget { @override Widget build(BuildContext context) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ OBTileGroupTitle( diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart index a8841347a..a37d20242 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart @@ -78,17 +78,14 @@ class OBModeratedObjectUpdateCategoryPageState } Widget _buildModerationCategories() { - return Opacity( - opacity: _requestInProgress ? 0.8 : 1, - child: Expanded( - child: ListView.separated( - padding: EdgeInsets.all(0.0), - itemBuilder: _buildModerationCategoryTile, - separatorBuilder: (context, index) { - return const Divider(); - }, - itemCount: _moderationCategories.length, - ), + return Expanded( + child: ListView.separated( + itemBuilder: _buildModerationCategoryTile, + padding: EdgeInsets.symmetric(vertical: 20, horizontal: 10), + separatorBuilder: (context, index) { + return const Divider(); + }, + itemCount: _moderationCategories.length, ), ); } @@ -97,6 +94,7 @@ class OBModeratedObjectUpdateCategoryPageState ModerationCategory category = _moderationCategories[index]; return GestureDetector( + key: Key(category.id.toString()), onTap: () => _setSelectedModerationCategory(category), child: Row( children: [ @@ -113,7 +111,7 @@ class OBModeratedObjectUpdateCategoryPageState Padding( padding: const EdgeInsets.only(left: 20), child: OBCheckbox( - value: _selectedModerationCategory == category, + value: _selectedModerationCategory.id == category.id, ), ) ], @@ -132,6 +130,7 @@ class OBModeratedObjectUpdateCategoryPageState try { _userService.updateModeratedObject(widget.moderatedObject, category: _selectedModerationCategory); + print(_selectedModerationCategory); Navigator.of(context).pop(); } catch (error) { _onError(error); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart index e00fc67ef..bba27212a 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart @@ -1,6 +1,7 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tile_group_title.dart'; import 'package:flutter/material.dart'; @@ -16,6 +17,7 @@ class OBModeratedObjectDescription extends StatelessWidget { @override Widget build(BuildContext context) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ OBTileGroupTitle( @@ -34,7 +36,13 @@ class OBModeratedObjectDescription extends StatelessWidget { stream: moderatedObject.updateSubject, builder: (BuildContext context, AsyncSnapshot snapshot) { - return OBText(snapshot.data.description); + String description = snapshot.data.description; + return description != null + ? OBText(snapshot.data.description) + : OBSecondaryText( + 'No description', + style: TextStyle(fontStyle: FontStyle.italic), + ); }, ), trailing: const OBIcon(OBIcons.chevronRight), diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart index d2093cc65..35ceeb863 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart @@ -4,9 +4,13 @@ import 'package:Openbook/models/moderation/moderation_report_list.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/avatars/avatar.dart'; import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/divider.dart'; import 'package:Openbook/widgets/theming/secondary_text.dart'; import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:Openbook/widgets/tiles/user_tile.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; @@ -58,33 +62,114 @@ class OBModeratedObjectReportsPreviewState _needsBootstrap = false; _refreshInProgress = true; } - return _refreshInProgress - ? Row( - children: [ - Padding( - padding: EdgeInsets.all(20), - child: OBProgressIndicator(), + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBTileGroupTitle( + title: 'Reports', + ), + OBDivider(), + _refreshInProgress + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(20), + child: OBProgressIndicator(), + ) + ], ) - ], - ) - : ListView.builder( - itemBuilder: _buildModerationReport, - itemCount: _reports.length, - ); + : ListView.separated( + separatorBuilder: (BuildContext context, int index) { + return OBDivider(); + }, + itemBuilder: _buildModerationReport, + itemCount: _reports.length, + shrinkWrap: true, + ), + OBDivider(), + ], + ); } Widget _buildModerationReport(BuildContext contenxt, int index) { ModerationReport report = _reports[index]; - return ListTile( - title: OBSecondaryText(report.reporter.username), - subtitle: Column( - mainAxisSize: MainAxisSize.min, - children: [ - OBText(report.description), - OBSecondaryText(report.category.title) - ], - ), - isThreeLine: true, + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildReportCategory(report), + _buildReportDescription(report), + SizedBox(height: 10,), + _buildReportReporter(report), + ], + ), + ), + ], + ); + } + + Widget _buildReportReporter(ModerationReport report) { + return Row( + children: [ + OBAvatar( + borderRadius: 4, + customSize: 16, + avatarUrl: report.reporter.getProfileAvatar(), + ), + const SizedBox( + width: 6, + ), + OBSecondaryText( + '@' + report.reporter.username, + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText(' reported'), + ], + ); + } + + Widget _buildReportDescription(ModerationReport report) { + if (report.description == null) return const SizedBox(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBText( + 'Description', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText( + report.description != null ? report.description : 'No description', + style: TextStyle( + fontStyle: report.description == null + ? FontStyle.italic + : FontStyle.normal), + ), + ], + ); + } + + Widget _buildReportCategory(ModerationReport report) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBText( + 'Category', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText( + report.category.title, + ), + ], ); } diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index 4992acb72..5c1d3d7c4 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -30,6 +30,7 @@ class OBModeratedObject extends StatelessWidget { children: [ OBPostHeader( post: moderatedObject.contentObject, + hasActions: false, ), OBPostBody(moderatedObject.contentObject), ], diff --git a/lib/services/moderation_api.dart b/lib/services/moderation_api.dart index c1d466631..823768862 100644 --- a/lib/services/moderation_api.dart +++ b/lib/services/moderation_api.dart @@ -130,11 +130,11 @@ class ModerationApiService { if (description != null) body['description'] = description; - if (categoryId != null) body['category_id'] = categoryId; + if (categoryId != null) body['category_id'] = categoryId.toString(); String path = _makeModeratedObjectsPath(moderatedObjectId); - return _httpService.post(_makeApiUrl(path), + return _httpService.patchJSON(_makeApiUrl(path), body: body, appendAuthorizationToken: true); } diff --git a/lib/services/user.dart b/lib/services/user.dart index 36e3e562f..42b1eb0e3 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -1523,6 +1523,9 @@ class UserService { _checkResponseIsOk(response); + var what = json.decode(response.body); + print(what); + return ModeratedObjectsList.fromJson(json.decode(response.body)); } @@ -1556,8 +1559,8 @@ class UserService { {String description, ModerationCategory category}) async { HttpieResponse response = await _moderationApiService .updateModeratedObjectWithId(moderatedObject.id, - description: description, categoryId: category.id); - _checkResponseIsCreated(response); + description: description, categoryId: category?.id); + _checkResponseIsOk(response); return ModeratedObject.fromJSON(json.decode(response.body)); } diff --git a/lib/widgets/post/widgets/post_header/post_header.dart b/lib/widgets/post/widgets/post_header/post_header.dart index 5b9eb9a98..f0bcf1be7 100644 --- a/lib/widgets/post/widgets/post_header/post_header.dart +++ b/lib/widgets/post/widgets/post_header/post_header.dart @@ -8,20 +8,26 @@ class OBPostHeader extends StatelessWidget { final Post post; final OnPostDeleted onPostDeleted; final ValueChanged onPostReported; + final bool hasActions; const OBPostHeader( - {Key key, this.onPostDeleted, this.post, this.onPostReported}) + {Key key, + this.onPostDeleted, + this.post, + this.onPostReported, + this.hasActions = true}) : super(key: key); @override Widget build(BuildContext context) { return post.isCommunityPost() - ? OBCommunityPostHeader( - post, + ? OBCommunityPostHeader(post, onPostDeleted: onPostDeleted, onPostReported: onPostReported, - ) + hasActions: hasActions) : OBUserPostHeader(post, - onPostDeleted: onPostDeleted, onPostReported: onPostReported); + onPostDeleted: onPostDeleted, + onPostReported: onPostReported, + hasActions: hasActions); } } diff --git a/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart b/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart index 9ac3de342..698c0d66c 100644 --- a/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart +++ b/lib/widgets/post/widgets/post_header/widgets/community_post_header.dart @@ -15,9 +15,13 @@ class OBCommunityPostHeader extends StatelessWidget { final Post _post; final OnPostDeleted onPostDeleted; final ValueChanged onPostReported; + final bool hasActions; const OBCommunityPostHeader(this._post, - {Key key, @required this.onPostDeleted, this.onPostReported}) + {Key key, + @required this.onPostDeleted, + this.onPostReported, + this.hasActions = true}) : super(key: key); @override @@ -41,15 +45,17 @@ class OBCommunityPostHeader extends StatelessWidget { user: _post.creator, context: context); }, ), - trailing: IconButton( - icon: const OBIcon(OBIcons.moreVertical), - onPressed: () { - bottomSheetService.showPostActions( - context: context, - post: _post, - onPostDeleted: onPostDeleted, - onPostReported: onPostReported); - }), + trailing: hasActions + ? IconButton( + icon: const OBIcon(OBIcons.moreVertical), + onPressed: () { + bottomSheetService.showPostActions( + context: context, + post: _post, + onPostDeleted: onPostDeleted, + onPostReported: onPostReported); + }) + : null, title: GestureDetector( onTap: () { navigationService.navigateToCommunity( diff --git a/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart b/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart index b93d4a370..f1934fa30 100644 --- a/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart +++ b/lib/widgets/post/widgets/post_header/widgets/user_post_header.dart @@ -15,9 +15,13 @@ class OBUserPostHeader extends StatelessWidget { final Post _post; final OnPostDeleted onPostDeleted; final ValueChanged onPostReported; + final bool hasActions; const OBUserPostHeader(this._post, - {Key key, @required this.onPostDeleted, this.onPostReported}) + {Key key, + @required this.onPostDeleted, + this.onPostReported, + this.hasActions = true}) : super(key: key); @override @@ -46,15 +50,17 @@ class OBUserPostHeader extends StatelessWidget { avatarUrl: postCreator.getProfileAvatar(), ); }), - trailing: IconButton( - icon: const OBIcon(OBIcons.moreVertical), - onPressed: () { - bottomSheetService.showPostActions( - context: context, - post: _post, - onPostDeleted: onPostDeleted, - onPostReported: onPostReported); - }), + trailing: hasActions + ? IconButton( + icon: const OBIcon(OBIcons.moreVertical), + onPressed: () { + bottomSheetService.showPostActions( + context: context, + post: _post, + onPostDeleted: onPostDeleted, + onPostReported: onPostReported); + }) + : null, title: GestureDetector( onTap: () { navigationService.navigateToUserProfile( From 6dd404d3b0085c4207ded1bfc370066be4f8b52a Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 11:21:12 +0200 Subject: [PATCH 42/65] :sparkles: moderated object reports/description n category work --- lib/models/moderation/moderated_object.dart | 7 +++ .../moderated_object_update_category.dart | 12 ++-- .../moderated_object_category.dart | 7 +-- .../moderated_object_update_description.dart | 19 ++---- .../moderated_object_description.dart | 10 ++-- .../moderated_object_reports_preview.dart | 59 +++++++++++++------ lib/services/modal_service.dart | 28 +++++++++ lib/services/navigation_service.dart | 26 -------- lib/widgets/buttons/see_all_button.dart | 46 +++++++++++++++ lib/widgets/icon.dart | 1 + 10 files changed, 145 insertions(+), 70 deletions(-) rename lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/{pages => modals}/moderated_object_update_category.dart (94%) rename lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/{pages => modals}/moderated_object_update_description.dart (90%) create mode 100644 lib/widgets/buttons/see_all_button.dart diff --git a/lib/models/moderation/moderated_object.dart b/lib/models/moderation/moderated_object.dart index 58d7fff85..c6239c40e 100644 --- a/lib/models/moderation/moderated_object.dart +++ b/lib/models/moderation/moderated_object.dart @@ -33,6 +33,7 @@ class ModeratedObject extends UpdatableModel { String description; bool verified; + int reportsCount; ModeratedObject( {this.id, @@ -40,6 +41,7 @@ class ModeratedObject extends UpdatableModel { this.contentObject, this.type, this.status, + this.reportsCount, this.category, this.description, this.verified}); @@ -58,6 +60,10 @@ class ModeratedObject extends UpdatableModel { verified = json['verified']; } + if (json.containsKey('reports_count')) { + reportsCount = json['reports_count']; + } + if (json.containsKey('status')) { status = factory.parseStatus(json['status']); } @@ -112,6 +118,7 @@ class ModeratedObjectFactory extends UpdatableModelFactory { community: community, category: category, description: json['description'], + reportsCount: json['reports_count'], status: status, type: type, contentObject: parseContentObject( diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart similarity index 94% rename from lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart rename to lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart index a37d20242..8c17bd842 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart @@ -15,21 +15,21 @@ import 'package:Openbook/widgets/theming/text.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class OBModeratedObjectUpdateCategoryPage extends StatefulWidget { +class OBModeratedObjectUpdateCategoryModal extends StatefulWidget { final ModeratedObject moderatedObject; - const OBModeratedObjectUpdateCategoryPage( + const OBModeratedObjectUpdateCategoryModal( {Key key, @required this.moderatedObject}) : super(key: key); @override - OBModeratedObjectUpdateCategoryPageState createState() { - return OBModeratedObjectUpdateCategoryPageState(); + OBModeratedObjectUpdateCategoryModalState createState() { + return OBModeratedObjectUpdateCategoryModalState(); } } -class OBModeratedObjectUpdateCategoryPageState - extends State { +class OBModeratedObjectUpdateCategoryModalState + extends State { UserService _userService; ToastService _toastService; List _moderationCategories = []; diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart index 6e0380f7c..eeb9e14b9 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart @@ -31,13 +31,12 @@ class OBModeratedObjectCategory extends StatelessWidget { onTap: () { OpenbookProviderState openbookProvider = OpenbookProvider.of(context); - openbookProvider.navigationService - .navigateToModeratedObjectUpdateCategory( - context: context, moderatedObject: moderatedObject); + openbookProvider.modalService.openModeratedObjectUpdateCategory( + context: context, moderatedObject: moderatedObject); }, title: OBText(snapshot.data.category.title), subtitle: OBText(snapshot.data.category.description), - trailing: const OBIcon(OBIcons.chevronRight), + trailing: const OBIcon(OBIcons.edit, themeColor: OBIconThemeColor.secondaryText,), ); }, ), diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart similarity index 90% rename from lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart rename to lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart index ad031ca90..04dff0a5f 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart @@ -1,5 +1,4 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; -import 'package:Openbook/services/navigation_service.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/provider.dart'; @@ -15,21 +14,21 @@ import 'package:async/async.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class OBModeratedObjectUpdateDescriptionPage extends StatefulWidget { +class OBModeratedObjectUpdateDescriptionModal extends StatefulWidget { final ModeratedObject moderatedObject; - const OBModeratedObjectUpdateDescriptionPage( + const OBModeratedObjectUpdateDescriptionModal( {Key key, @required this.moderatedObject}) : super(key: key); @override - OBModeratedObjectUpdateDescriptionPageState createState() { - return OBModeratedObjectUpdateDescriptionPageState(); + OBModeratedObjectUpdateDescriptionModalState createState() { + return OBModeratedObjectUpdateDescriptionModalState(); } } -class OBModeratedObjectUpdateDescriptionPageState - extends State { +class OBModeratedObjectUpdateDescriptionModalState + extends State { UserService _userService; ToastService _toastService; ValidationService _validationService; @@ -110,12 +109,6 @@ class OBModeratedObjectUpdateDescriptionPageState Widget _buildNavigationBar() { return OBThemedNavigationBar( - leading: GestureDetector( - child: const OBIcon(OBIcons.close), - onTap: () { - Navigator.pop(context); - }, - ), title: 'Edit description', trailing: OBButton( isDisabled: !_formValid, diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart index bba27212a..623099012 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart @@ -27,9 +27,8 @@ class OBModeratedObjectDescription extends StatelessWidget { onTap: () { OpenbookProviderState openbookProvider = OpenbookProvider.of(context); - openbookProvider.navigationService - .navigateToModeratedObjectUpdateDescription( - context: context, moderatedObject: moderatedObject); + openbookProvider.modalService.openModeratedObjectUpdateDescription( + context: context, moderatedObject: moderatedObject); }, title: StreamBuilder( initialData: moderatedObject, @@ -45,7 +44,10 @@ class OBModeratedObjectDescription extends StatelessWidget { ); }, ), - trailing: const OBIcon(OBIcons.chevronRight), + trailing: const OBIcon( + OBIcons.edit, + themeColor: OBIconThemeColor.secondaryText, + ), ) ], ); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart index 35ceeb863..103c8763e 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart @@ -2,9 +2,11 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/moderation/moderation_report.dart'; import 'package:Openbook/models/moderation/moderation_report_list.dart'; import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/navigation_service.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/avatars/avatar.dart'; +import 'package:Openbook/widgets/buttons/see_all_button.dart'; import 'package:Openbook/widgets/progress_indicator.dart'; import 'package:Openbook/widgets/theming/divider.dart'; import 'package:Openbook/widgets/theming/secondary_text.dart'; @@ -30,9 +32,12 @@ class OBModeratedObjectReportsPreview extends StatefulWidget { class OBModeratedObjectReportsPreviewState extends State { + static int reportsPreviewsCount = 5; + bool _needsBootstrap; UserService _userService; ToastService _toastService; + NavigationService _navigationService; CancelableOperation _refreshReportsOperation; bool _refreshInProgress; @@ -58,6 +63,7 @@ class OBModeratedObjectReportsPreviewState OpenbookProviderState openbookProvider = OpenbookProvider.of(context); _userService = openbookProvider.userService; _toastService = openbookProvider.toastService; + _navigationService = openbookProvider.navigationService; _refreshReports(); _needsBootstrap = false; _refreshInProgress = true; @@ -89,6 +95,12 @@ class OBModeratedObjectReportsPreviewState shrinkWrap: true, ), OBDivider(), + OBSeeAllButton( + previewedResourcesCount: _reports.length, + resourcesCount: widget.moderatedObject.reportsCount, + resourceName: 'reports', + onPressed: _onWantsToSeeAllReports, + ) ], ); } @@ -107,7 +119,9 @@ class OBModeratedObjectReportsPreviewState children: [ _buildReportCategory(report), _buildReportDescription(report), - SizedBox(height: 10,), + SizedBox( + height: 10, + ), _buildReportReporter(report), ], ), @@ -117,22 +131,28 @@ class OBModeratedObjectReportsPreviewState } Widget _buildReportReporter(ModerationReport report) { - return Row( - children: [ - OBAvatar( - borderRadius: 4, - customSize: 16, - avatarUrl: report.reporter.getProfileAvatar(), - ), - const SizedBox( - width: 6, - ), - OBSecondaryText( - '@' + report.reporter.username, - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBSecondaryText(' reported'), - ], + return GestureDetector( + onTap: () { + _navigationService.navigateToUserProfile( + user: report.reporter, context: context); + }, + child: Row( + children: [ + OBAvatar( + borderRadius: 4, + customSize: 16, + avatarUrl: report.reporter.getProfileAvatar(), + ), + const SizedBox( + width: 6, + ), + OBSecondaryText( + '@' + report.reporter.username, + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText(' reported'), + ], + ), ); } @@ -188,6 +208,11 @@ class OBModeratedObjectReportsPreviewState _setRefreshInProgress(false); } + void _onWantsToSeeAllReports() { + _navigationService.navigateToModeratedObjectReports( + context: context, moderatedObject: widget.moderatedObject); + } + void _setRefreshInProgress(bool refreshInProgress) { setState(() { _refreshInProgress = refreshInProgress; diff --git a/lib/services/modal_service.dart b/lib/services/modal_service.dart index de5e1b6f6..6fe911724 100644 --- a/lib/services/modal_service.dart +++ b/lib/services/modal_service.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:Openbook/models/circle.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/follows_list.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/models/post_reaction.dart'; @@ -26,6 +27,8 @@ import 'package:Openbook/pages/home/modals/user_invites/create_user_invite.dart' import 'package:Openbook/pages/home/modals/user_invites/send_invite_email.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/moderated_objects.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart'; import 'package:Openbook/pages/home/pages/timeline/timeline.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -355,4 +358,29 @@ class ModalService { ); })); } + + Future openModeratedObjectUpdateDescription( + {@required BuildContext context, + @required ModeratedObject moderatedObject}) async { + return Navigator.of(context).push(CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) { + return OBModeratedObjectUpdateDescriptionModal( + moderatedObject: moderatedObject, + ); + })); + } + + Future openModeratedObjectUpdateCategory( + {@required BuildContext context, + @required ModeratedObject moderatedObject}) async { + + return Navigator.of(context).push(CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) { + return OBModeratedObjectUpdateCategoryModal( + moderatedObject: moderatedObject, + ); + })); + } } diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index ef1b0e3f2..f637fc303 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -47,8 +47,6 @@ import 'package:Openbook/pages/home/pages/menu/pages/themes/themes.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/moderated_objects.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart'; -import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/pages/moderated_object_update_category.dart'; -import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/pages/moderated_object_update_description.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart'; import 'package:Openbook/pages/home/pages/notifications/pages/notifications_settings.dart'; import 'package:Openbook/pages/home/pages/post/post.dart'; @@ -575,30 +573,6 @@ class NavigationService { widget: OBModeratedObjectsPage())); } - Future navigateToModeratedObjectUpdateDescription( - {@required BuildContext context, - @required ModeratedObject moderatedObject}) async { - return Navigator.push( - context, - OBSlideRightRoute( - key: Key('obModeratedObjectUpdateDescriptionPage'), - widget: OBModeratedObjectUpdateDescriptionPage( - moderatedObject: moderatedObject, - ))); - } - - Future navigateToModeratedObjectUpdateCategory( - {@required BuildContext context, - @required ModeratedObject moderatedObject}) async { - return Navigator.push( - context, - OBSlideRightRoute( - key: Key('obModeratedObjectUpdateCategoryPage'), - widget: OBModeratedObjectUpdateCategoryPage( - moderatedObject: moderatedObject, - ))); - } - Future navigateToModeratedObjectReports( {@required BuildContext context, @required ModeratedObject moderatedObject}) async { diff --git a/lib/widgets/buttons/see_all_button.dart b/lib/widgets/buttons/see_all_button.dart new file mode 100644 index 000000000..01bc78de8 --- /dev/null +++ b/lib/widgets/buttons/see_all_button.dart @@ -0,0 +1,46 @@ +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:flutter/material.dart'; + +import '../icon.dart'; + +class OBSeeAllButton extends StatelessWidget { + final VoidCallback onPressed; + final String resourceName; + final int previewedResourcesCount; + final int resourcesCount; + + const OBSeeAllButton( + {Key key, + @required this.onPressed, + @required this.resourceName, + @required this.previewedResourcesCount, + @required this.resourcesCount}) + : super(key: key); + + @override + Widget build(BuildContext context) { + int remainingResourcesToDisplay = resourcesCount - previewedResourcesCount; + + if (previewedResourcesCount == 0 || remainingResourcesToDisplay <= 0) return const SizedBox(); + + return GestureDetector( + onTap: onPressed, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + OBSecondaryText( + 'See all $resourcesCount $resourceName', + style: TextStyle(fontSize: 16), + ), + const SizedBox( + width: 5, + ), + OBIcon(OBIcons.seeMore, themeColor: OBIconThemeColor.secondaryText) + ], + ), + ), + ); + } +} diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index 7133ed536..6379ac8cf 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -196,6 +196,7 @@ class OBIcons { static const expand = OBIconData(filename: 'expand-icon.png'); static const mutePost = OBIconData(nativeIcon: Icons.notifications_active); static const editPost = OBIconData(nativeIcon: Icons.edit); + static const edit = OBIconData(nativeIcon: Icons.edit); static const reviewModeratedObject = OBIconData(nativeIcon: Icons.edit); static const unmutePost = OBIconData(nativeIcon: Icons.notifications_off); static const deleteAccount = OBIconData(nativeIcon: Icons.delete_forever); From 6e2afee80f5c1a1bb5c7c8b8c2c7b6b7c34106da Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 13:36:02 +0200 Subject: [PATCH 43/65] :sparkles: add logs to moderated object view --- .../moderation/moderated_object_log.dart | 78 +++++++++- lib/models/moderation/moderation_report.dart | 3 - .../pages/moderated_object_global_review.dart | 4 + .../moderated_object_category.dart | 13 +- .../moderated_object_logs.dart | 135 ++++++++++++++++++ .../moderated_object_log_tile.dart | 67 +++++++++ ...ated_object_category_changed_log_tile.dart | 52 +++++++ ...d_object_description_changed_log_tile.dart | 54 +++++++ .../widgets/moderated_object_log_actor.dart | 43 ++++++ ...erated_object_status_changed_log_tile.dart | 58 ++++++++ ...ated_object_verified_changed_log_tile.dart | 55 +++++++ lib/widgets/tile_group_title.dart | 2 +- .../tiles/moderation_category_tile.dart | 39 +++++ 13 files changed, 592 insertions(+), 11 deletions(-) create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_log_actor.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart create mode 100644 lib/widgets/tiles/moderation_category_tile.dart diff --git a/lib/models/moderation/moderated_object_log.dart b/lib/models/moderation/moderated_object_log.dart index 15e780f35..3c582b2ba 100644 --- a/lib/models/moderation/moderated_object_log.dart +++ b/lib/models/moderation/moderated_object_log.dart @@ -1,6 +1,7 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/user.dart'; +import 'package:meta/meta.dart'; class ModeratedObjectLog { static String descriptionChangedLogType = 'DC'; @@ -14,22 +15,29 @@ class ModeratedObjectLog { final DateTime created; ModeratedObjectLogType logType; + dynamic contentObject; + ModeratedObjectLog( {this.verified, this.description, + this.contentObject, this.actor, this.created, this.logType}); factory ModeratedObjectLog.fromJson(Map parsedJson) { + ModeratedObjectLogType logType = parseType(parsedJson['log_type']); + return ModeratedObjectLog( verified: parsedJson['verified'], description: parsedJson['description'], actor: parseActor( parsedJson['actor'], ), - logType: parseType(parsedJson['log_type']), - created: parseCreated(parsedJson['created'])); + logType: logType, + created: parseCreated(parsedJson['created']), + contentObject: parseContentObject( + contentObjectData: parsedJson['content_object'], logType: logType)); } static User parseActor(Map rawActor) { @@ -42,6 +50,35 @@ class ModeratedObjectLog { return DateTime.parse(created).toLocal(); } + static dynamic parseContentObject( + {@required Map contentObjectData, + @required ModeratedObjectLogType logType}) { + if (contentObjectData == null) return null; + + dynamic contentObject; + switch (logType) { + case ModeratedObjectLogType.categoryChanged: + contentObject = + ModeratedObjectCategoryChangedLog.fromJson(contentObjectData); + break; + case ModeratedObjectLogType.descriptionChanged: + contentObject = + ModeratedObjectDescriptionChangedLog.fromJson(contentObjectData); + break; + case ModeratedObjectLogType.verifiedChanged: + contentObject = + ModeratedObjectVerifiedChangedLog.fromJson(contentObjectData); + break; + case ModeratedObjectLogType.statusChanged: + contentObject = + ModeratedObjectStatusChangedLog.fromJson(contentObjectData); + break; + default: + } + + return contentObject; + } + static ModeratedObjectLogType parseType(String moderatedObjectTypeStr) { if (moderatedObjectTypeStr == null) return null; @@ -78,6 +115,19 @@ class ModeratedObjectCategoryChangedLog { final ModerationCategory changedTo; ModeratedObjectCategoryChangedLog({this.changedFrom, this.changedTo}); + + factory ModeratedObjectCategoryChangedLog.fromJson( + Map parsedJson) { + return ModeratedObjectCategoryChangedLog( + changedFrom: parseCategory(parsedJson['changed_from']), + changedTo: parseCategory(parsedJson['changed_to']), + ); + } + + static ModerationCategory parseCategory(Map rawModerationCategory) { + if (rawModerationCategory == null) return null; + return ModerationCategory.fromJson(rawModerationCategory); + } } class ModeratedObjectDescriptionChangedLog { @@ -85,6 +135,14 @@ class ModeratedObjectDescriptionChangedLog { final String changedTo; ModeratedObjectDescriptionChangedLog({this.changedFrom, this.changedTo}); + + factory ModeratedObjectDescriptionChangedLog.fromJson( + Map parsedJson) { + return ModeratedObjectDescriptionChangedLog( + changedFrom: parsedJson['changed_from'], + changedTo: parsedJson['changed_to'], + ); + } } class ModeratedObjectVerifiedChangedLog { @@ -92,6 +150,14 @@ class ModeratedObjectVerifiedChangedLog { final bool changedTo; ModeratedObjectVerifiedChangedLog({this.changedFrom, this.changedTo}); + + factory ModeratedObjectVerifiedChangedLog.fromJson( + Map parsedJson) { + return ModeratedObjectVerifiedChangedLog( + changedFrom: parsedJson['changed_from'], + changedTo: parsedJson['changed_to'], + ); + } } class ModeratedObjectStatusChangedLog { @@ -99,4 +165,12 @@ class ModeratedObjectStatusChangedLog { final ModeratedObjectStatus changedTo; ModeratedObjectStatusChangedLog({this.changedFrom, this.changedTo}); + + factory ModeratedObjectStatusChangedLog.fromJson( + Map parsedJson) { + return ModeratedObjectStatusChangedLog( + changedFrom: parsedJson['changed_from'], + changedTo: parsedJson['changed_to'], + ); + } } diff --git a/lib/models/moderation/moderation_report.dart b/lib/models/moderation/moderation_report.dart index b93554810..01d754e21 100644 --- a/lib/models/moderation/moderation_report.dart +++ b/lib/models/moderation/moderation_report.dart @@ -12,7 +12,6 @@ class ModerationReport { {this.description, this.reporter, this.category, this.created}); factory ModerationReport.fromJson(Map parsedJson) { - print(parsedJson); return ModerationReport( description: parsedJson['description'], reporter: parseReporter( @@ -31,8 +30,6 @@ class ModerationReport { static ModerationCategory parseCategory(Map rawModerationCategory) { if (rawModerationCategory == null) return null; - print('PARSIN'); - print(rawModerationCategory); return ModerationCategory.fromJson(rawModerationCategory); } diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index c6939ed58..38a15be22 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -1,6 +1,7 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; @@ -73,6 +74,9 @@ class OBModeratedObjectGlobalReviewPageState OBModeratedObjectReportsPreview( isEditable: _isEditable, moderatedObject: widget.moderatedObject, + ), + OBModeratedObjectLogs( + moderatedObject: widget.moderatedObject, ) ], ), diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart index eeb9e14b9..4183061b3 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart @@ -3,6 +3,7 @@ import 'package:Openbook/provider.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:Openbook/widgets/tiles/moderation_category_tile.dart'; import 'package:flutter/material.dart'; class OBModeratedObjectCategory extends StatelessWidget { @@ -27,16 +28,18 @@ class OBModeratedObjectCategory extends StatelessWidget { stream: moderatedObject.updateSubject, builder: (BuildContext context, AsyncSnapshot snapshot) { - return ListTile( - onTap: () { + return OBModerationCategoryTile( + category: snapshot.data.category, + onPressed: (category) { OpenbookProviderState openbookProvider = OpenbookProvider.of(context); openbookProvider.modalService.openModeratedObjectUpdateCategory( context: context, moderatedObject: moderatedObject); }, - title: OBText(snapshot.data.category.title), - subtitle: OBText(snapshot.data.category.description), - trailing: const OBIcon(OBIcons.edit, themeColor: OBIconThemeColor.secondaryText,), + trailing: const OBIcon( + OBIcons.edit, + themeColor: OBIconThemeColor.secondaryText, + ), ); }, ), diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart new file mode 100644 index 000000000..be3c26703 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart @@ -0,0 +1,135 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderated_object_log.dart'; +import 'package:Openbook/models/moderation/moderated_object_log_list.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/divider.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectLogs extends StatefulWidget { + final ModeratedObject moderatedObject; + + const OBModeratedObjectLogs({Key key, @required this.moderatedObject}) + : super(key: key); + + @override + OBModeratedObjectLogsState createState() { + return OBModeratedObjectLogsState(); + } +} + +class OBModeratedObjectLogsState extends State { + static int logssCount = 5; + + bool _needsBootstrap; + UserService _userService; + ToastService _toastService; + + CancelableOperation _refreshLogsOperation; + bool _refreshInProgress; + List _logs; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _refreshInProgress = false; + _logs = []; + } + + @override + void dispose() { + super.dispose(); + if (_refreshLogsOperation != null) _refreshLogsOperation.cancel(); + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + _refreshLogs(); + _needsBootstrap = false; + _refreshInProgress = true; + } + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBTileGroupTitle( + title: 'Logs', + ), + OBDivider(), + _refreshInProgress + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.all(20), + child: OBProgressIndicator(), + ) + ], + ) + : ListView.builder( + padding: EdgeInsets.all(0), + itemBuilder: _buildModerationLog, + itemCount: _logs.length, + shrinkWrap: true, + ), + ], + ); + } + + Widget _buildModerationLog(BuildContext context, int index) { + ModeratedObjectLog log = _logs[index]; + return OBModeratedObjectLogTile( + log: log, + ); + } + + Future _refreshLogs() async { + _setRefreshInProgress(true); + try { + _refreshLogsOperation = CancelableOperation.fromFuture(_userService + .getModeratedObjectLogs(widget.moderatedObject, count: 5)); + + ModeratedObjectLogsList moderationLogsList = + await _refreshLogsOperation.value; + _setLogs(moderationLogsList.moderatedObjectLogs); + } catch (error) { + _onError(error); + } + _setRefreshInProgress(false); + } + + void _setRefreshInProgress(bool refreshInProgress) { + setState(() { + _refreshInProgress = refreshInProgress; + }); + } + + void _setLogs(List logs) { + setState(() { + _logs = logs; + }); + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart new file mode 100644 index 000000000..739869d5d --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart @@ -0,0 +1,67 @@ +import 'package:Openbook/models/moderation/moderated_object_log.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart'; +import 'package:Openbook/widgets/theming/divider.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectLogTile extends StatelessWidget { + final ModeratedObjectLog log; + final ValueChanged onModeratedObjectLogTileDeleted; + final ValueChanged onPressed; + + const OBModeratedObjectLogTile( + {Key key, + @required this.log, + this.onModeratedObjectLogTileDeleted, + this.onPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + Widget logTile; + + switch (log.contentObject.runtimeType) { + case ModeratedObjectDescriptionChangedLog: + logTile = OBModeratedObjectDescriptionChangedLogTile( + moderatedObjectDescriptionChangedLog: log.contentObject, + log: log, + ); + break; + case ModeratedObjectVerifiedChangedLog: + logTile = OBModeratedObjectVerifiedChangedLogTile( + moderatedObjectVerifiedChangedLog: log.contentObject, + log: log, + ); + break; + case ModeratedObjectStatusChangedLog: + logTile = OBModeratedObjectStatusChangedLogTile( + moderatedObjectStatusChangedLog: log.contentObject, + log: log, + ); + break; + case ModeratedObjectCategoryChangedLog: + logTile = OBModeratedObjectCategoryChangedLogTile( + moderatedObjectCategoryChangedLog: log.contentObject, + log: log, + ); + break; + default: + logTile = const ListTile( + title: OBText( + 'Unsupported log type', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [logTile, OBDivider()], + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart new file mode 100644 index 000000000..9ab30553c --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart @@ -0,0 +1,52 @@ +import 'package:Openbook/models/moderation/moderated_object_log.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tiles/moderation_category_tile.dart'; +import 'package:flutter/material.dart'; + +import 'moderated_object_log_actor.dart'; + +class OBModeratedObjectCategoryChangedLogTile extends StatelessWidget { + final ModeratedObjectLog log; + final ModeratedObjectCategoryChangedLog moderatedObjectCategoryChangedLog; + final VoidCallback onPressed; + + const OBModeratedObjectCategoryChangedLogTile( + {Key key, + @required this.log, + @required this.moderatedObjectCategoryChangedLog, + this.onPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBText( + 'Category changed from:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBModerationCategoryTile( + contentPadding: const EdgeInsets.all(0), + category: moderatedObjectCategoryChangedLog.changedFrom), + OBText( + 'To:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBModerationCategoryTile( + contentPadding: const EdgeInsets.all(0), + category: moderatedObjectCategoryChangedLog.changedTo), + OBText( + 'By:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBModeratedObjectLogActor( + actor: log.actor, + ) + ], + ), + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart new file mode 100644 index 000000000..c3d92fa87 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart @@ -0,0 +1,54 @@ +import 'package:Openbook/models/moderation/moderated_object_log.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; + +import 'moderated_object_log_actor.dart'; + +class OBModeratedObjectDescriptionChangedLogTile extends StatelessWidget { + final ModeratedObjectLog log; + final ModeratedObjectDescriptionChangedLog + moderatedObjectDescriptionChangedLog; + final VoidCallback onPressed; + + const OBModeratedObjectDescriptionChangedLogTile( + {Key key, + @required this.log, + @required this.moderatedObjectDescriptionChangedLog, + this.onPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBText( + 'Description changed from:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText(moderatedObjectDescriptionChangedLog.changedFrom), + const SizedBox( + height: 10, + ), + OBText( + 'To:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText(moderatedObjectDescriptionChangedLog.changedTo), + const SizedBox( + height: 10, + ), + OBText( + 'By:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBModeratedObjectLogActor( + actor: log.actor, + ) + ], + ), + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_log_actor.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_log_actor.dart new file mode 100644 index 000000000..af77ea5fa --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_log_actor.dart @@ -0,0 +1,43 @@ +import 'package:Openbook/models/user.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/widgets/avatars/avatar.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectLogActor extends StatelessWidget { + final User actor; + + const OBModeratedObjectLogActor({Key key, @required this.actor}) + : super(key: key); + + Widget build(BuildContext context) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + NavigationService navigationService = openbookProvider.navigationService; + + return GestureDetector( + onTap: () { + navigationService.navigateToUserProfile(user: actor, context: context); + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 3), + child: Row( + children: [ + OBAvatar( + borderRadius: 4, + customSize: 16, + avatarUrl: actor.getProfileAvatar(), + ), + const SizedBox( + width: 6, + ), + OBSecondaryText( + '@' + actor.username, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart new file mode 100644 index 000000000..5bf1454ee --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart @@ -0,0 +1,58 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderated_object_log.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; + +import 'moderated_object_log_actor.dart'; + +class OBModeratedObjectStatusChangedLogTile extends StatelessWidget { + final ModeratedObjectLog log; + final ModeratedObjectStatusChangedLog moderatedObjectStatusChangedLog; + final VoidCallback onPressed; + + const OBModeratedObjectStatusChangedLogTile( + {Key key, + @required this.log, + @required this.moderatedObjectStatusChangedLog, + this.onPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBText( + 'Status changed from:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText(ModeratedObject.factory + .convertStatusToHumanReadableString( + moderatedObjectStatusChangedLog.changedFrom)), + const SizedBox( + height: 10, + ), + OBText( + 'To:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText(ModeratedObject.factory + .convertStatusToHumanReadableString( + moderatedObjectStatusChangedLog.changedTo)), + const SizedBox( + height: 10, + ), + OBText( + 'By:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBModeratedObjectLogActor( + actor: log.actor, + ) + ], + ), + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart new file mode 100644 index 000000000..1d0511170 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart @@ -0,0 +1,55 @@ +import 'package:Openbook/models/moderation/moderated_object_log.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; + +import 'moderated_object_log_actor.dart'; + +class OBModeratedObjectVerifiedChangedLogTile extends StatelessWidget { + final ModeratedObjectLog log; + final ModeratedObjectVerifiedChangedLog moderatedObjectVerifiedChangedLog; + final VoidCallback onPressed; + + const OBModeratedObjectVerifiedChangedLogTile( + {Key key, + @required this.log, + @required this.moderatedObjectVerifiedChangedLog, + this.onPressed}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBText( + 'Verified changed from:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText( + moderatedObjectVerifiedChangedLog.changedFrom.toString()), + const SizedBox( + height: 10, + ), + OBText( + 'To:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText( + moderatedObjectVerifiedChangedLog.changedTo.toString()), + const SizedBox( + height: 10, + ), + OBText( + 'By:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBModeratedObjectLogActor( + actor: log.actor, + ) + ], + ), + ); + } +} diff --git a/lib/widgets/tile_group_title.dart b/lib/widgets/tile_group_title.dart index ff87ccb00..b47638c07 100644 --- a/lib/widgets/tile_group_title.dart +++ b/lib/widgets/tile_group_title.dart @@ -9,7 +9,7 @@ class OBTileGroupTitle extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 5), + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 5), child: OBText( title, size: OBTextSize.large, diff --git a/lib/widgets/tiles/moderation_category_tile.dart b/lib/widgets/tiles/moderation_category_tile.dart new file mode 100644 index 000000000..27e08d559 --- /dev/null +++ b/lib/widgets/tiles/moderation_category_tile.dart @@ -0,0 +1,39 @@ +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBModerationCategoryTile extends StatelessWidget { + final ModerationCategory category; + final Widget trailing; + final ValueChanged onPressed; + final EdgeInsets contentPadding; + + const OBModerationCategoryTile( + {Key key, + this.trailing, + @required this.category, + this.onPressed, + this.contentPadding}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + key: Key(category.id.toString()), + onTap: () { + if (onPressed != null) onPressed(category); + }, + child: ListTile( + contentPadding: contentPadding, + title: OBText( + category.title, + ), + subtitle: OBSecondaryText(category.description), + trailing: trailing, + //trailing: OBIcon(OBIcons.chevronRight), + ), + ); + } +} From 6e13d4f58996d607d7a89306d44839fd9b1cc4b9 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 13:48:21 +0200 Subject: [PATCH 44/65] :sparkles: add date to moderated object logs --- .../moderated_object_log_tile.dart | 32 ++++++++++++++++++- ...ated_object_category_changed_log_tile.dart | 7 ---- ...d_object_description_changed_log_tile.dart | 10 ------ ...erated_object_status_changed_log_tile.dart | 10 ------ ...ated_object_verified_changed_log_tile.dart | 10 ------ 5 files changed, 31 insertions(+), 38 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart index 739869d5d..71f1d5a44 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart @@ -1,6 +1,7 @@ import 'package:Openbook/models/moderation/moderated_object_log.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_log_actor.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart'; import 'package:Openbook/widgets/theming/divider.dart'; @@ -61,7 +62,36 @@ class OBModeratedObjectLogTile extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: [logTile, OBDivider()], + children: [ + logTile, + const SizedBox( + height: 5, + ), + ListTile( + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBText( + 'By:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBModeratedObjectLogActor( + actor: log.actor, + ), + SizedBox( + height: 5, + ), + OBText( + 'On:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBText(log.created.toString()), + ], + ), + ), + OBDivider() + ], ); } } diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart index 9ab30553c..e0034e479 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_category_changed_log_tile.dart @@ -38,13 +38,6 @@ class OBModeratedObjectCategoryChangedLogTile extends StatelessWidget { OBModerationCategoryTile( contentPadding: const EdgeInsets.all(0), category: moderatedObjectCategoryChangedLog.changedTo), - OBText( - 'By:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBModeratedObjectLogActor( - actor: log.actor, - ) ], ), ); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart index c3d92fa87..9bd93dab4 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart @@ -37,16 +37,6 @@ class OBModeratedObjectDescriptionChangedLogTile extends StatelessWidget { style: TextStyle(fontWeight: FontWeight.bold), ), OBSecondaryText(moderatedObjectDescriptionChangedLog.changedTo), - const SizedBox( - height: 10, - ), - OBText( - 'By:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBModeratedObjectLogActor( - actor: log.actor, - ) ], ), ); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart index 5bf1454ee..687556c00 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart @@ -41,16 +41,6 @@ class OBModeratedObjectStatusChangedLogTile extends StatelessWidget { OBSecondaryText(ModeratedObject.factory .convertStatusToHumanReadableString( moderatedObjectStatusChangedLog.changedTo)), - const SizedBox( - height: 10, - ), - OBText( - 'By:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBModeratedObjectLogActor( - actor: log.actor, - ) ], ), ); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart index 1d0511170..834433a02 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_verified_changed_log_tile.dart @@ -38,16 +38,6 @@ class OBModeratedObjectVerifiedChangedLogTile extends StatelessWidget { ), OBSecondaryText( moderatedObjectVerifiedChangedLog.changedTo.toString()), - const SizedBox( - height: 10, - ), - OBText( - 'By:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBModeratedObjectLogActor( - actor: log.actor, - ) ], ), ); From 5f6bca4771acdf73b8478a563fafff048393a719 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 15:34:44 +0200 Subject: [PATCH 45/65] :sparkles: refresh logs on description or category change --- ios/Podfile.lock | 8 +- .../moderation/moderated_object_log.dart | 3 + .../pages/moderated_object_global_review.dart | 28 ++++- .../moderated_object_update_category.dart | 3 +- .../moderated_object_category.dart | 17 ++- .../moderated_object_update_description.dart | 2 +- .../moderated_object_description.dart | 15 ++- .../moderated_object_logs.dart | 23 +++- .../moderated_object_reports_preview.dart | 86 +------------ .../moderation_report_tile.dart | 114 ++++++++++++++++++ .../pages/moderated_object_reports.dart | 19 +-- lib/services/modal_service.dart | 10 +- lib/services/user.dart | 1 - pubspec.lock | 6 +- 14 files changed, 206 insertions(+), 129 deletions(-) create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderation_report_tile.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9a58fd8ce..b489dc416 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -11,7 +11,7 @@ PODS: - FMDB/standard (2.7.5) - image_cropper (0.0.1): - Flutter - - TOCropViewController (~> 2.4.0) + - TOCropViewController (~> 2.5.0) - image_picker (0.0.1): - Flutter - Intercom (5.2.1) @@ -31,7 +31,7 @@ PODS: - sqflite (0.0.1): - Flutter - FMDB (~> 2.7.2) - - TOCropViewController (2.4.0) + - TOCropViewController (2.5.0) - uni_links (0.0.1): - Flutter - url_launcher (0.0.1): @@ -102,7 +102,7 @@ SPEC CHECKSUMS: flutter_exif_rotation: 458778023267a1f0157ae8d9483474749990ce24 flutter_secure_storage: dbcc8ff35d99569c3a4d3b483afa3339416c1a78 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - image_cropper: 43c1f7f5ea92b68f43cae9340f55c84bdaad54bb + image_cropper: e0a40e80b2107490926d0f15e3b42f44867c15b4 image_picker: ee00aab0487cedc80a304085219503cc6d0f2e22 Intercom: 53f07c24f0ca18c1e910750e79b75f9c9867338f intercom_flutter: e281b619d2fc03dccf171089c87c01a17e1ed8b1 @@ -112,7 +112,7 @@ SPEC CHECKSUMS: share: 222b5dcc8031238af9d7de91149df65bad1aef75 shared_preferences: 5a1d487c427ee18fcd3ea1f2a131569481834b53 sqflite: d1612813fa7db7c667bed9f1d1b508deffc56999 - TOCropViewController: 368d8df3ea43b62c3dc5a61f11b9048274d240bd + TOCropViewController: d86078d3a57a70c116bf3db7c89c80046a0e5fdf uni_links: 5ee5240df5cbffc52d9e7f8017a576b6a6bc5141 url_launcher: 92b89c1029a0373879933c21642958c874539095 video_player: 906796a841943c8d370ac7c13b18039aa9b56498 diff --git a/lib/models/moderation/moderated_object_log.dart b/lib/models/moderation/moderated_object_log.dart index 3c582b2ba..629225b45 100644 --- a/lib/models/moderation/moderated_object_log.dart +++ b/lib/models/moderation/moderated_object_log.dart @@ -9,6 +9,7 @@ class ModeratedObjectLog { static String verifiedChangedLogType = 'VC'; static String categoryChangedLogType = 'CC'; + final int id; final String description; final bool verified; final User actor; @@ -19,6 +20,7 @@ class ModeratedObjectLog { ModeratedObjectLog( {this.verified, + this.id, this.description, this.contentObject, this.actor, @@ -30,6 +32,7 @@ class ModeratedObjectLog { return ModeratedObjectLog( verified: parsedJson['verified'], + id: parsedJson['id'], description: parsedJson['description'], actor: parseActor( parsedJson['actor'], diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index 38a15be22..10111f247 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -1,4 +1,5 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart'; @@ -36,6 +37,7 @@ class OBModeratedObjectGlobalReviewPageState bool _needsBootstrap; CancelableOperation _requestOperation; + OBModeratedObjectLogsController _logsController; @override void initState() { @@ -43,6 +45,7 @@ class OBModeratedObjectGlobalReviewPageState _needsBootstrap = true; _isEditable = false; _requestInProgress = false; + _logsController = OBModeratedObjectLogsController(); } @override @@ -64,19 +67,20 @@ class OBModeratedObjectGlobalReviewPageState child: ListView( children: [ OBModeratedObjectDescription( - isEditable: _isEditable, - moderatedObject: widget.moderatedObject, - ), + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + onDescriptionChanged: _onDescriptionChanged), OBModeratedObjectCategory( - isEditable: _isEditable, - moderatedObject: widget.moderatedObject, - ), + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + onCategoryChanged: _onCategoryChanged), OBModeratedObjectReportsPreview( isEditable: _isEditable, moderatedObject: widget.moderatedObject, ), OBModeratedObjectLogs( moderatedObject: widget.moderatedObject, + controller: _logsController, ) ], ), @@ -121,6 +125,18 @@ class OBModeratedObjectGlobalReviewPageState ); } + void _onDescriptionChanged(String newDescription) { + Future.delayed(Duration(seconds: 1), (){ + _logsController.refreshLogs(); + }); + } + + void _onCategoryChanged(ModerationCategory newCategory) { + Future.delayed(Duration(seconds: 1), (){ + _logsController.refreshLogs(); + }); + } + void _onWantsToVerifyModeratedObject() async { try { _requestOperation = CancelableOperation.fromFuture( diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart index 8c17bd842..f7d3be489 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart @@ -130,8 +130,7 @@ class OBModeratedObjectUpdateCategoryModalState try { _userService.updateModeratedObject(widget.moderatedObject, category: _selectedModerationCategory); - print(_selectedModerationCategory); - Navigator.of(context).pop(); + Navigator.of(context).pop(_selectedModerationCategory); } catch (error) { _onError(error); } finally { diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart index 4183061b3..f1f6dfc4e 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart @@ -1,4 +1,5 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/text.dart'; @@ -9,9 +10,13 @@ import 'package:flutter/material.dart'; class OBModeratedObjectCategory extends StatelessWidget { final bool isEditable; final ModeratedObject moderatedObject; + final ValueChanged onCategoryChanged; const OBModeratedObjectCategory( - {Key key, @required this.moderatedObject, @required this.isEditable}) + {Key key, + @required this.moderatedObject, + @required this.isEditable, + this.onCategoryChanged}) : super(key: key); @override @@ -30,11 +35,15 @@ class OBModeratedObjectCategory extends StatelessWidget { (BuildContext context, AsyncSnapshot snapshot) { return OBModerationCategoryTile( category: snapshot.data.category, - onPressed: (category) { + onPressed: (category) async { OpenbookProviderState openbookProvider = OpenbookProvider.of(context); - openbookProvider.modalService.openModeratedObjectUpdateCategory( - context: context, moderatedObject: moderatedObject); + ModerationCategory newModerationCategory = + await openbookProvider.modalService + .openModeratedObjectUpdateCategory( + context: context, moderatedObject: moderatedObject); + if (newModerationCategory != null && onCategoryChanged != null) + onCategoryChanged(newModerationCategory); }, trailing: const OBIcon( OBIcons.edit, diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart index 04dff0a5f..57bb43b74 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart @@ -140,7 +140,7 @@ class OBModeratedObjectUpdateDescriptionModalState _userService.updateModeratedObject(widget.moderatedObject, description: _descriptionController.text)); - Navigator.of(context).pop(); + Navigator.of(context).pop(_descriptionController.text); } catch (error) { _onError(error); } finally { diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart index 623099012..8cffe5aa8 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart @@ -9,9 +9,13 @@ import 'package:flutter/material.dart'; class OBModeratedObjectDescription extends StatelessWidget { final bool isEditable; final ModeratedObject moderatedObject; + final ValueChanged onDescriptionChanged; const OBModeratedObjectDescription( - {Key key, @required this.moderatedObject, @required this.isEditable}) + {Key key, + @required this.moderatedObject, + @required this.isEditable, + this.onDescriptionChanged}) : super(key: key); @override @@ -24,11 +28,14 @@ class OBModeratedObjectDescription extends StatelessWidget { title: 'Description', ), ListTile( - onTap: () { + onTap: () async { OpenbookProviderState openbookProvider = OpenbookProvider.of(context); - openbookProvider.modalService.openModeratedObjectUpdateDescription( - context: context, moderatedObject: moderatedObject); + String newDescription = await openbookProvider.modalService + .openModeratedObjectUpdateDescription( + context: context, moderatedObject: moderatedObject); + if (newDescription != null && onDescriptionChanged != null) + onDescriptionChanged(newDescription); }, title: StreamBuilder( initialData: moderatedObject, diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart index be3c26703..7243bc5f6 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart @@ -13,8 +13,10 @@ import 'package:flutter/material.dart'; class OBModeratedObjectLogs extends StatefulWidget { final ModeratedObject moderatedObject; + final OBModeratedObjectLogsController controller; - const OBModeratedObjectLogs({Key key, @required this.moderatedObject}) + const OBModeratedObjectLogs( + {Key key, @required this.moderatedObject, this.controller}) : super(key: key); @override @@ -40,6 +42,7 @@ class OBModeratedObjectLogsState extends State { _needsBootstrap = true; _refreshInProgress = false; _logs = []; + if (widget.controller != null) widget.controller.attach(this); } @override @@ -89,11 +92,13 @@ class OBModeratedObjectLogsState extends State { Widget _buildModerationLog(BuildContext context, int index) { ModeratedObjectLog log = _logs[index]; return OBModeratedObjectLogTile( + key: Key(log.id.toString()), log: log, ); } Future _refreshLogs() async { + if (_refreshInProgress) return; _setRefreshInProgress(true); try { _refreshLogsOperation = CancelableOperation.fromFuture(_userService @@ -102,10 +107,12 @@ class OBModeratedObjectLogsState extends State { ModeratedObjectLogsList moderationLogsList = await _refreshLogsOperation.value; _setLogs(moderationLogsList.moderatedObjectLogs); + print(moderationLogsList.moderatedObjectLogs.first.logType); } catch (error) { _onError(error); + } finally { + _setRefreshInProgress(false); } - _setRefreshInProgress(false); } void _setRefreshInProgress(bool refreshInProgress) { @@ -133,3 +140,15 @@ class OBModeratedObjectLogsState extends State { } } } + +class OBModeratedObjectLogsController { + OBModeratedObjectLogsState _state; + + void attach(state) { + _state = state; + } + + Future refreshLogs() { + return _state._refreshLogs(); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart index 103c8763e..871bb9e52 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart @@ -16,6 +16,8 @@ import 'package:Openbook/widgets/tiles/user_tile.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; +import 'moderation_report_tile.dart'; + class OBModeratedObjectReportsPreview extends StatefulWidget { final bool isEditable; final ModeratedObject moderatedObject; @@ -108,89 +110,7 @@ class OBModeratedObjectReportsPreviewState Widget _buildModerationReport(BuildContext contenxt, int index) { ModerationReport report = _reports[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - _buildReportCategory(report), - _buildReportDescription(report), - SizedBox( - height: 10, - ), - _buildReportReporter(report), - ], - ), - ), - ], - ); - } - - Widget _buildReportReporter(ModerationReport report) { - return GestureDetector( - onTap: () { - _navigationService.navigateToUserProfile( - user: report.reporter, context: context); - }, - child: Row( - children: [ - OBAvatar( - borderRadius: 4, - customSize: 16, - avatarUrl: report.reporter.getProfileAvatar(), - ), - const SizedBox( - width: 6, - ), - OBSecondaryText( - '@' + report.reporter.username, - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBSecondaryText(' reported'), - ], - ), - ); - } - - Widget _buildReportDescription(ModerationReport report) { - if (report.description == null) return const SizedBox(); - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - OBText( - 'Description', - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBSecondaryText( - report.description != null ? report.description : 'No description', - style: TextStyle( - fontStyle: report.description == null - ? FontStyle.italic - : FontStyle.normal), - ), - ], - ); - } - - Widget _buildReportCategory(ModerationReport report) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - OBText( - 'Category', - style: TextStyle(fontWeight: FontWeight.bold), - ), - OBSecondaryText( - report.category.title, - ), - ], - ); + return OBModerationReportTile(report: report); } Future _refreshReports() async { diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderation_report_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderation_report_tile.dart new file mode 100644 index 000000000..94ac6b719 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderation_report_tile.dart @@ -0,0 +1,114 @@ +import 'package:Openbook/models/moderation/moderation_report.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/avatars/avatar.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; + +class OBModerationReportTile extends StatelessWidget { + final ModerationReport report; + + const OBModerationReportTile({Key key, @required this.report}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + _buildReportCategory(report), + const SizedBox( + height: 5, + ), + _buildReportDescription(report), + const SizedBox( + height: 5, + ), + _buildReportReporter(report: report, context: context), + ], + ), + ), + ], + ); + } + + Widget _buildReportReporter( + {@required ModerationReport report, @required BuildContext context}) { + return GestureDetector( + onTap: () { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + openbookProvider.navigationService + .navigateToUserProfile(user: report.reporter, context: context); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBText( + 'Reporter', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Padding( + padding: EdgeInsets.symmetric(vertical: 5), + child: Row( + children: [ + OBAvatar( + borderRadius: 4, + customSize: 16, + avatarUrl: report.reporter.getProfileAvatar(), + ), + const SizedBox( + width: 6, + ), + OBSecondaryText( + '@' + report.reporter.username, + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ) + ], + )); + } + + Widget _buildReportDescription(ModerationReport report) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBText( + 'Description', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText( + report.description != null ? report.description : 'No description', + style: TextStyle( + fontStyle: report.description == null + ? FontStyle.italic + : FontStyle.normal), + ), + ], + ); + } + + Widget _buildReportCategory(ModerationReport report) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBText( + 'Category', + style: TextStyle(fontWeight: FontWeight.bold), + ), + OBSecondaryText( + report.category.title, + ), + ], + ); + } +} diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart index 8286612d2..261b2d548 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/pages/moderated_object_reports.dart @@ -5,16 +5,15 @@ import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/progress_indicator.dart'; -import 'package:Openbook/widgets/theming/secondary_text.dart'; -import 'package:Openbook/widgets/theming/text.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; +import '../moderation_report_tile.dart'; + class OBModeratedObjectReportsPage extends StatefulWidget { final ModeratedObject moderatedObject; - const OBModeratedObjectReportsPage( - {Key key, @required this.moderatedObject}) + const OBModeratedObjectReportsPage({Key key, @required this.moderatedObject}) : super(key: key); @override @@ -74,16 +73,8 @@ class OBModeratedObjectReportsPageState Widget _buildModerationReport(BuildContext contenxt, int index) { ModerationReport report = _reports[index]; - return ListTile( - title: OBSecondaryText(report.reporter.username), - subtitle: Column( - mainAxisSize: MainAxisSize.min, - children: [ - OBText(report.description), - OBSecondaryText(report.category.title) - ], - ), - isThreeLine: true, + return OBModerationReportTile( + report: report, ); } diff --git a/lib/services/modal_service.dart b/lib/services/modal_service.dart index 6fe911724..1c539223d 100644 --- a/lib/services/modal_service.dart +++ b/lib/services/modal_service.dart @@ -4,6 +4,7 @@ import 'package:Openbook/models/circle.dart'; import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/follows_list.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/post.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/models/post_reaction.dart'; @@ -359,10 +360,10 @@ class ModalService { })); } - Future openModeratedObjectUpdateDescription( + Future openModeratedObjectUpdateDescription( {@required BuildContext context, @required ModeratedObject moderatedObject}) async { - return Navigator.of(context).push(CupertinoPageRoute( + return Navigator.of(context).push(CupertinoPageRoute( fullscreenDialog: true, builder: (BuildContext context) { return OBModeratedObjectUpdateDescriptionModal( @@ -371,11 +372,10 @@ class ModalService { })); } - Future openModeratedObjectUpdateCategory( + Future openModeratedObjectUpdateCategory( {@required BuildContext context, @required ModeratedObject moderatedObject}) async { - - return Navigator.of(context).push(CupertinoPageRoute( + return Navigator.of(context).push(CupertinoPageRoute( fullscreenDialog: true, builder: (BuildContext context) { return OBModeratedObjectUpdateCategoryModal( diff --git a/lib/services/user.dart b/lib/services/user.dart index 42b1eb0e3..036061d44 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -191,7 +191,6 @@ class UserService { {@required String username, @required String password}) async { HttpieResponse response = await _authApiService.loginWithCredentials( username: username, password: password); - if (response.isOk()) { var parsedResponse = response.parseJsonBody(); var authToken = parsedResponse['token']; diff --git a/pubspec.lock b/pubspec.lock index 7f2ec1c89..3a55684b1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -28,7 +28,7 @@ packages: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" + version: "1.5.2" async: dependency: transitive description: @@ -246,7 +246,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" image_cropper: dependency: "direct main" description: @@ -470,7 +470,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.5.2+2" + version: "0.5.3" shelf: dependency: transitive description: From 362fb7a71e7e25aebcdf30218538d33b2a2f7dd1 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 15:37:51 +0200 Subject: [PATCH 46/65] :bug: no need for delay on refresh logs --- .../pages/moderated_object_global_review.dart | 9 ++------- .../modals/moderated_object_update_category.dart | 4 ++-- .../modals/moderated_object_update_description.dart | 2 ++ .../moderated_object_logs/moderated_object_logs.dart | 1 - 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index 10111f247..2ff811cf7 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -10,7 +10,6 @@ import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/buttons/button.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; -import 'package:Openbook/widgets/theming/primary_color_container.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; @@ -126,15 +125,11 @@ class OBModeratedObjectGlobalReviewPageState } void _onDescriptionChanged(String newDescription) { - Future.delayed(Duration(seconds: 1), (){ - _logsController.refreshLogs(); - }); + _logsController.refreshLogs(); } void _onCategoryChanged(ModerationCategory newCategory) { - Future.delayed(Duration(seconds: 1), (){ - _logsController.refreshLogs(); - }); + _logsController.refreshLogs(); } void _onWantsToVerifyModeratedObject() async { diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart index f7d3be489..e3840bb06 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart @@ -125,10 +125,10 @@ class OBModeratedObjectUpdateCategoryModalState }); } - void _saveModerationCategory() { + void _saveModerationCategory() async{ _setRequestInProgress(true); try { - _userService.updateModeratedObject(widget.moderatedObject, + await _userService.updateModeratedObject(widget.moderatedObject, category: _selectedModerationCategory); Navigator.of(context).pop(_selectedModerationCategory); } catch (error) { diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart index 57bb43b74..43c5ba675 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart @@ -140,6 +140,8 @@ class OBModeratedObjectUpdateDescriptionModalState _userService.updateModeratedObject(widget.moderatedObject, description: _descriptionController.text)); + await _editDescriptionOperation.value; + Navigator.of(context).pop(_descriptionController.text); } catch (error) { _onError(error); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart index 7243bc5f6..1e40ac65d 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart @@ -107,7 +107,6 @@ class OBModeratedObjectLogsState extends State { ModeratedObjectLogsList moderationLogsList = await _refreshLogsOperation.value; _setLogs(moderationLogsList.moderatedObjectLogs); - print(moderationLogsList.moderatedObjectLogs.first.logType); } catch (error) { _onError(error); } finally { From 0581dc343bfad8c3c9b71550063e955325865679 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 16:55:36 +0200 Subject: [PATCH 47/65] :sparkles: add moderated object status to global review --- lib/models/moderation/moderated_object.dart | 6 +- .../moderation/moderated_object_log.dart | 11 +- .../pages/moderated_object_global_review.dart | 16 +- .../moderated_object_category.dart | 1 + .../moderated_object_description.dart | 1 + .../moderated_object_log_tile.dart | 2 +- ...erated_object_status_changed_log_tile.dart | 6 +- .../moderated_object_update_status.dart | 200 ++++++++++++++++++ .../moderated_object_status.dart | 58 +++++ lib/services/modal_service.dart | 13 ++ lib/services/user.dart | 4 +- 11 files changed, 306 insertions(+), 12 deletions(-) create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart create mode 100644 lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart diff --git a/lib/models/moderation/moderated_object.dart b/lib/models/moderation/moderated_object.dart index c6239c40e..d08f9f872 100644 --- a/lib/models/moderation/moderated_object.dart +++ b/lib/models/moderation/moderated_object.dart @@ -88,14 +88,14 @@ class ModeratedObject extends UpdatableModel { } void setIsApproved() { - _setStatus(ModeratedObjectStatus.approved); + setStatus(ModeratedObjectStatus.approved); } void setIsRejected() { - _setStatus(ModeratedObjectStatus.rejected); + setStatus(ModeratedObjectStatus.rejected); } - void _setStatus(ModeratedObjectStatus newStatus) { + void setStatus(ModeratedObjectStatus newStatus) { status = newStatus; notifyUpdate(); } diff --git a/lib/models/moderation/moderated_object_log.dart b/lib/models/moderation/moderated_object_log.dart index 629225b45..f0f26c453 100644 --- a/lib/models/moderation/moderated_object_log.dart +++ b/lib/models/moderation/moderated_object_log.dart @@ -5,7 +5,7 @@ import 'package:meta/meta.dart'; class ModeratedObjectLog { static String descriptionChangedLogType = 'DC'; - static String statusChangedLogType = 'SC'; + static String statusChangedLogType = 'AC'; static String verifiedChangedLogType = 'VC'; static String categoryChangedLogType = 'CC'; @@ -172,8 +172,13 @@ class ModeratedObjectStatusChangedLog { factory ModeratedObjectStatusChangedLog.fromJson( Map parsedJson) { return ModeratedObjectStatusChangedLog( - changedFrom: parsedJson['changed_from'], - changedTo: parsedJson['changed_to'], + changedFrom: parseStatus(parsedJson['changed_from']), + changedTo: parseStatus(parsedJson['changed_to']), ); } + + static ModeratedObjectStatus parseStatus(String rawModerationStatus) { + if (rawModerationStatus == null) return null; + return ModeratedObject.factory.parseStatus(rawModerationStatus); + } } diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index 2ff811cf7..e3687f4c2 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -4,6 +4,7 @@ import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/modera import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; @@ -73,6 +74,11 @@ class OBModeratedObjectGlobalReviewPageState isEditable: _isEditable, moderatedObject: widget.moderatedObject, onCategoryChanged: _onCategoryChanged), + OBModeratedObjectStatus( + moderatedObject: widget.moderatedObject, + isEditable: _isEditable, + onStatusChanged: _onStatusChanged, + ), OBModeratedObjectReportsPreview( isEditable: _isEditable, moderatedObject: widget.moderatedObject, @@ -125,10 +131,18 @@ class OBModeratedObjectGlobalReviewPageState } void _onDescriptionChanged(String newDescription) { - _logsController.refreshLogs(); + _refreshLogs(); } void _onCategoryChanged(ModerationCategory newCategory) { + _refreshLogs(); + } + + void _onStatusChanged(ModeratedObjectStatus newStatus) { + _refreshLogs(); + } + + void _refreshLogs() { _logsController.refreshLogs(); } diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart index f1f6dfc4e..64dd30618 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart @@ -36,6 +36,7 @@ class OBModeratedObjectCategory extends StatelessWidget { return OBModerationCategoryTile( category: snapshot.data.category, onPressed: (category) async { + if (!isEditable) return; OpenbookProviderState openbookProvider = OpenbookProvider.of(context); ModerationCategory newModerationCategory = diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart index 8cffe5aa8..a873e35d2 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart @@ -29,6 +29,7 @@ class OBModeratedObjectDescription extends StatelessWidget { ), ListTile( onTap: () async { + if(!isEditable) return; OpenbookProviderState openbookProvider = OpenbookProvider.of(context); String newDescription = await openbookProvider.modalService diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart index 71f1d5a44..8f9f12650 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/moderated_object_log_tile.dart @@ -50,7 +50,7 @@ class OBModeratedObjectLogTile extends StatelessWidget { ); break; default: - logTile = const ListTile( + logTile = ListTile( title: OBText( 'Unsupported log type', style: TextStyle(fontWeight: FontWeight.bold), diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart index 687556c00..128e8f31a 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_status_changed_log_tile.dart @@ -30,7 +30,8 @@ class OBModeratedObjectStatusChangedLogTile extends StatelessWidget { ), OBSecondaryText(ModeratedObject.factory .convertStatusToHumanReadableString( - moderatedObjectStatusChangedLog.changedFrom)), + moderatedObjectStatusChangedLog.changedFrom, + capitalize: true)), const SizedBox( height: 10, ), @@ -40,7 +41,8 @@ class OBModeratedObjectStatusChangedLogTile extends StatelessWidget { ), OBSecondaryText(ModeratedObject.factory .convertStatusToHumanReadableString( - moderatedObjectStatusChangedLog.changedTo)), + moderatedObjectStatusChangedLog.changedTo, + capitalize: true)), ], ), ); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart new file mode 100644 index 000000000..20f3babf9 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart @@ -0,0 +1,200 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/services/toast.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/checkbox.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/progress_indicator.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:async/async.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectUpdateStatusModal extends StatefulWidget { + final ModeratedObject moderatedObject; + + const OBModeratedObjectUpdateStatusModal( + {Key key, @required this.moderatedObject}) + : super(key: key); + + @override + OBModeratedObjectUpdateStatusModalState createState() { + return OBModeratedObjectUpdateStatusModalState(); + } +} + +class OBModeratedObjectUpdateStatusModalState + extends State { + UserService _userService; + ToastService _toastService; + List _moderationStatuses = [ + ModeratedObjectStatus.rejected, + ModeratedObjectStatus.approved, + ]; + ModeratedObjectStatus _selectedModerationStatus; + bool _needsBootstrap; + bool _requestInProgress; + + CancelableOperation _updateStatusOperation; + + @override + void initState() { + super.initState(); + _needsBootstrap = true; + _requestInProgress = false; + _selectedModerationStatus = widget.moderatedObject.status; + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + var openbookProvider = OpenbookProvider.of(context); + _toastService = openbookProvider.toastService; + _userService = openbookProvider.userService; + _needsBootstrap = false; + } + + return OBCupertinoPageScaffold( + navigationBar: _buildNavigationBar(), + child: OBPrimaryColorContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _moderationStatuses.isEmpty + ? _buildProgressIndicator() + : _buildModerationStatuses(), + ], + ), + )); + } + + @override + void dispose() { + super.dispose(); + if (_updateStatusOperation != null) _updateStatusOperation.cancel(); + } + + Widget _buildProgressIndicator() { + return Expanded( + child: Center( + child: OBProgressIndicator(), + ), + ); + } + + Widget _buildModerationStatuses() { + return Expanded( + child: ListView.separated( + itemBuilder: _buildModerationStatusTile, + padding: EdgeInsets.symmetric(vertical: 20, horizontal: 10), + separatorBuilder: (context, index) { + return const Divider(); + }, + itemCount: _moderationStatuses.length, + ), + ); + } + + Widget _buildModerationStatusTile(context, index) { + ModeratedObjectStatus status = _moderationStatuses[index]; + String statusString = ModeratedObject.factory + .convertStatusToHumanReadableString(status, capitalize: true); + + return GestureDetector( + key: Key(statusString), + onTap: () => _setSelectedModerationStatus(status), + child: Row( + children: [ + Expanded( + child: ListTile( + title: OBText( + statusString, + style: TextStyle(fontWeight: FontWeight.bold), + ), + //trailing: OBIcon(OBIcons.chevronRight), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: OBCheckbox( + value: _selectedModerationStatus == status, + ), + ) + ], + ), + ); + } + + void _setSelectedModerationStatus(ModeratedObjectStatus status) { + setState(() { + _selectedModerationStatus = status; + }); + } + + void _saveModerationStatus() async { + _setRequestInProgress(true); + try { + if (_selectedModerationStatus == widget.moderatedObject.status) { + Navigator.of(context).pop(); + return; + } + + switch (_selectedModerationStatus) { + case ModeratedObjectStatus.approved: + _updateStatusOperation = CancelableOperation.fromFuture( + _userService.approveModeratedObject(widget.moderatedObject)); + break; + case ModeratedObjectStatus.rejected: + _updateStatusOperation = CancelableOperation.fromFuture( + _userService.rejectModeratedObject(widget.moderatedObject)); + break; + default: + throw 'Unsuppported update type'; + } + await _updateStatusOperation.value; + Navigator.of(context).pop(_selectedModerationStatus); + widget.moderatedObject.setStatus(_selectedModerationStatus); + } catch (error) { + _onError(error); + } finally { + _updateStatusOperation = null; + _setRequestInProgress(false); + } + } + + void _onError(error) async { + if (error is HttpieConnectionRefusedError) { + _toastService.error( + message: error.toHumanReadableMessage(), context: context); + } else if (error is HttpieRequestError) { + String errorMessage = await error.toHumanReadableMessage(); + _toastService.error(message: errorMessage, context: context); + } else { + _toastService.error(message: 'Unknown error', context: context); + throw error; + } + } + + Widget _buildNavigationBar() { + return OBThemedNavigationBar( + title: 'Update status', + trailing: OBButton( + isLoading: _requestInProgress, + size: OBButtonSize.small, + onPressed: _saveModerationStatus, + child: Text('Save'), + ), + ); + } + + _setRequestInProgress(bool requestInProgress) { + setState(() { + _requestInProgress = requestInProgress; + }); + } +} + +typedef OnObjectReported(dynamic object); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart new file mode 100644 index 000000000..747c83251 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart @@ -0,0 +1,58 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectStatus extends StatelessWidget { + final bool isEditable; + final ModeratedObject moderatedObject; + final ValueChanged onStatusChanged; + + const OBModeratedObjectStatus( + {Key key, + @required this.moderatedObject, + @required this.isEditable, + this.onStatusChanged}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBTileGroupTitle( + title: 'Status', + ), + StreamBuilder( + initialData: moderatedObject, + stream: moderatedObject.updateSubject, + builder: + (BuildContext context, AsyncSnapshot snapshot) { + return ListTile( + title: OBText(ModeratedObject.factory + .convertStatusToHumanReadableString(moderatedObject.status, capitalize: true)), + onTap: () async { + if (!isEditable) return; + OpenbookProviderState openbookProvider = + OpenbookProvider.of(context); + ModeratedObjectStatus newModerationStatus = + await openbookProvider.modalService + .openModeratedObjectUpdateStatus( + context: context, moderatedObject: moderatedObject); + if (newModerationStatus != null && onStatusChanged != null) + onStatusChanged(newModerationStatus); + }, + trailing: const OBIcon( + OBIcons.edit, + themeColor: OBIconThemeColor.secondaryText, + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/services/modal_service.dart b/lib/services/modal_service.dart index 1c539223d..4a2c2ffab 100644 --- a/lib/services/modal_service.dart +++ b/lib/services/modal_service.dart @@ -30,6 +30,7 @@ import 'package:Openbook/pages/home/pages/moderated_objects/modals/moderated_obj import 'package:Openbook/pages/home/pages/moderated_objects/moderated_objects.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/modals/moderated_object_update_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/modals/moderated_object_update_description.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart'; import 'package:Openbook/pages/home/pages/timeline/timeline.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -383,4 +384,16 @@ class ModalService { ); })); } + + Future openModeratedObjectUpdateStatus( + {@required BuildContext context, + @required ModeratedObject moderatedObject}) async { + return Navigator.of(context).push(CupertinoPageRoute( + fullscreenDialog: true, + builder: (BuildContext context) { + return OBModeratedObjectUpdateStatusModal( + moderatedObject: moderatedObject, + ); + })); + } } diff --git a/lib/services/user.dart b/lib/services/user.dart index 036061d44..86d623c0e 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -1601,13 +1601,13 @@ class UserService { Future approveModeratedObject(ModeratedObject moderatedObject) async { HttpieResponse response = await _moderationApiService .approveModeratedObjectWithId(moderatedObject.id); - _checkResponseIsCreated(response); + _checkResponseIsOk(response); } Future rejectModeratedObject(ModeratedObject moderatedObject) async { HttpieResponse response = await _moderationApiService .rejectModeratedObjectWithId(moderatedObject.id); - _checkResponseIsCreated(response); + _checkResponseIsOk(response); } Future getModerationCategories() async { From 2da0c1065762b10d18f1f7619bf2c2b7c9bc05e6 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 17:11:29 +0200 Subject: [PATCH 48/65] :sparkles: verify/unverify global object works --- .../pages/moderated_object_global_review.dart | 78 ++++++++++++------- .../moderated_object_category.dart | 4 +- .../moderated_object_description.dart | 4 +- .../moderated_object_status.dart | 4 +- .../moderated_object/moderated_object.dart | 1 - lib/services/user.dart | 4 +- 6 files changed, 57 insertions(+), 38 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index e3687f4c2..a1b4c602c 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -53,6 +53,7 @@ class OBModeratedObjectGlobalReviewPageState if (_needsBootstrap) { OpenbookProviderState openbookProvider = OpenbookProvider.of(context); _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; _bootstrap(); _needsBootstrap = false; } @@ -100,33 +101,39 @@ class OBModeratedObjectGlobalReviewPageState } Widget _buildPrimaryActions() { - List actions = []; - - if (widget.moderatedObject.verified) { - actions.add(Expanded( - child: OBButton( - size: OBButtonSize.large, - type: OBButtonType.danger, - child: Text('Unverify'), - onPressed: _onWantsToUnverifyModeratedObject, - isLoading: _requestInProgress, - ), - )); - } else { - actions.add(Expanded( - child: OBButton( - size: OBButtonSize.large, - type: OBButtonType.success, - child: Text('Verify'), - onPressed: _onWantsToVerifyModeratedObject, - isLoading: _requestInProgress, - ), - )); - } - - return Row( - mainAxisSize: MainAxisSize.max, - children: actions, + return StreamBuilder( + stream: widget.moderatedObject.updateSubject, + initialData: widget.moderatedObject, + builder: (BuildContext context, AsyncSnapshot snapshot) { + List actions = []; + + if (snapshot.data.verified) { + actions.add(Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.danger, + child: Text('Unverify'), + onPressed: _onWantsToUnverifyModeratedObject, + isLoading: _requestInProgress, + ), + )); + } else { + actions.add(Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.success, + child: Text('Verify'), + onPressed: _onWantsToVerifyModeratedObject, + isLoading: _requestInProgress, + ), + )); + } + + return Row( + mainAxisSize: MainAxisSize.max, + children: actions, + ); + }, ); } @@ -147,26 +154,33 @@ class OBModeratedObjectGlobalReviewPageState } void _onWantsToVerifyModeratedObject() async { + _setRequestInProgress(true); try { _requestOperation = CancelableOperation.fromFuture( _userService.verifyModeratedObject(widget.moderatedObject)); await _requestOperation.value; widget.moderatedObject.setIsVerified(true); - Navigator.pop(context); + _refreshLogs(); } catch (error) { _onError(error); + } finally { + _setRequestInProgress(false); } } void _onWantsToUnverifyModeratedObject() async { + _setRequestInProgress(true); + try { _requestOperation = CancelableOperation.fromFuture( _userService.unverifyModeratedObject(widget.moderatedObject)); await _requestOperation.value; widget.moderatedObject.setIsVerified(false); - Navigator.pop(context); + _refreshLogs(); } catch (error) { _onError(error); + } finally { + _setRequestInProgress(false); } } @@ -186,4 +200,10 @@ class OBModeratedObjectGlobalReviewPageState throw error; } } + + void _setRequestInProgress(requestInProgress) { + setState(() { + _requestInProgress = requestInProgress; + }); + } } diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart index 64dd30618..1b7e8adc1 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart @@ -46,10 +46,10 @@ class OBModeratedObjectCategory extends StatelessWidget { if (newModerationCategory != null && onCategoryChanged != null) onCategoryChanged(newModerationCategory); }, - trailing: const OBIcon( + trailing: isEditable ? const OBIcon( OBIcons.edit, themeColor: OBIconThemeColor.secondaryText, - ), + ) : null, ); }, ), diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart index a873e35d2..93fb2e16d 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart @@ -52,10 +52,10 @@ class OBModeratedObjectDescription extends StatelessWidget { ); }, ), - trailing: const OBIcon( + trailing: isEditable ? const OBIcon( OBIcons.edit, themeColor: OBIconThemeColor.secondaryText, - ), + ) : null, ) ], ); diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart index 747c83251..304985278 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart @@ -45,10 +45,10 @@ class OBModeratedObjectStatus extends StatelessWidget { if (newModerationStatus != null && onStatusChanged != null) onStatusChanged(newModerationStatus); }, - trailing: const OBIcon( + trailing: isEditable ? const OBIcon( OBIcons.edit, themeColor: OBIconThemeColor.secondaryText, - ), + ) : null, ); }, ), diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index 5c1d3d7c4..287f3b81f 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -6,7 +6,6 @@ import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/pos import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; import 'package:Openbook/widgets/theming/divider.dart'; -import 'package:Openbook/widgets/theming/post_divider.dart'; import 'package:Openbook/widgets/tiles/community_tile.dart'; import 'package:Openbook/widgets/tiles/user_tile.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/services/user.dart b/lib/services/user.dart index 86d623c0e..a2675afe7 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -1566,7 +1566,7 @@ class UserService { Future verifyModeratedObject(ModeratedObject moderatedObject) async { HttpieResponse response = await _moderationApiService .verifyModeratedObjectWithId(moderatedObject.id); - _checkResponseIsCreated(response); + _checkResponseIsOk(response); } Future getModeratedObjectLogs( @@ -1595,7 +1595,7 @@ class UserService { Future unverifyModeratedObject(ModeratedObject moderatedObject) async { HttpieResponse response = await _moderationApiService .unverifyModeratedObjectWithId(moderatedObject.id); - _checkResponseIsCreated(response); + _checkResponseIsOk(response); } Future approveModeratedObject(ModeratedObject moderatedObject) async { From 40f9c350e59ed270a4fbd28b8e5ec2b34c173470 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 18:07:18 +0200 Subject: [PATCH 49/65] :bug: isEditable gets refreshed when verify/unverify --- .../pages/moderated_object_global_review.dart | 37 ++++++++++++++++++- ...d_object_description_changed_log_tile.dart | 11 +++++- lib/widgets/icon.dart | 2 + 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index a1b4c602c..74f3f4407 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -9,6 +9,7 @@ import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; import 'package:async/async.dart'; @@ -112,7 +113,19 @@ class OBModeratedObjectGlobalReviewPageState child: OBButton( size: OBButtonSize.large, type: OBButtonType.danger, - child: Text('Unverify'), + child: Row( + children: [ + const OBIcon( + OBIcons.unverify, + color: Colors.white, + customSize: 18, + ), + const SizedBox( + width: 10, + ), + Text('Unverify') + ], + ), onPressed: _onWantsToUnverifyModeratedObject, isLoading: _requestInProgress, ), @@ -122,7 +135,19 @@ class OBModeratedObjectGlobalReviewPageState child: OBButton( size: OBButtonSize.large, type: OBButtonType.success, - child: Text('Verify'), + child: Row( + children: [ + const OBIcon( + OBIcons.verify, + color: Colors.white, + customSize: 18, + ), + const SizedBox( + width: 10, + ), + Text('Verify') + ], + ), onPressed: _onWantsToVerifyModeratedObject, isLoading: _requestInProgress, ), @@ -160,6 +185,7 @@ class OBModeratedObjectGlobalReviewPageState _userService.verifyModeratedObject(widget.moderatedObject)); await _requestOperation.value; widget.moderatedObject.setIsVerified(true); + _updateIsEditable(); _refreshLogs(); } catch (error) { _onError(error); @@ -176,6 +202,7 @@ class OBModeratedObjectGlobalReviewPageState _userService.unverifyModeratedObject(widget.moderatedObject)); await _requestOperation.value; widget.moderatedObject.setIsVerified(false); + _updateIsEditable(); _refreshLogs(); } catch (error) { _onError(error); @@ -188,6 +215,12 @@ class OBModeratedObjectGlobalReviewPageState _isEditable = !widget.moderatedObject.verified; } + void _updateIsEditable() { + setState(() { + _isEditable = !widget.moderatedObject.verified; + }); + } + void _onError(error) async { if (error is HttpieConnectionRefusedError) { _toastService.error( diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart index 9bd93dab4..e7c3bbc30 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/widgets/moderated_object_log_tile/widgets/moderated_object_description_changed_log_tile.dart @@ -28,7 +28,16 @@ class OBModeratedObjectDescriptionChangedLogTile extends StatelessWidget { 'Description changed from:', style: TextStyle(fontWeight: FontWeight.bold), ), - OBSecondaryText(moderatedObjectDescriptionChangedLog.changedFrom), + OBSecondaryText( + moderatedObjectDescriptionChangedLog.changedFrom != null + ? moderatedObjectDescriptionChangedLog.changedFrom + : 'No description', + style: TextStyle( + fontStyle: + moderatedObjectDescriptionChangedLog.changedFrom != null + ? FontStyle.normal + : FontStyle.italic), + ), const SizedBox( height: 10, ), diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index 6379ac8cf..ce3240982 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -218,6 +218,8 @@ class OBIcons { static const openPost = OBIconData(nativeIcon: Icons.lock_open); static const block = OBIconData(nativeIcon: Icons.block); static const chevronRight = OBIconData(nativeIcon: Icons.chevron_right); + static const verify = OBIconData(nativeIcon: Icons.lock_outline); + static const unverify = OBIconData(nativeIcon: Icons.lock_open); static const success = OBIconData(filename: 'success-icon.png'); static const error = OBIconData(filename: 'error-icon.png'); static const warning = OBIconData(filename: 'warning-icon.png'); From 707b1f83ca6008c80939e9f5053e5f5f28370ebd Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 18:56:40 +0200 Subject: [PATCH 50/65] :sparkles: update community moderated object review to match global --- .../moderated_object_community_review.dart | 47 ++++++++++++-- .../pages/moderated_object_global_review.dart | 8 +++ .../moderated_object/moderated_object.dart | 50 ++------------- .../widgets/moderated_object_preview.dart | 64 +++++++++++++++++++ 4 files changed, 117 insertions(+), 52 deletions(-) create mode 100644 lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart index f17ad1c70..d61b60138 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart @@ -1,8 +1,11 @@ import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; @@ -10,6 +13,7 @@ import 'package:Openbook/widgets/buttons/button.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; @@ -37,12 +41,14 @@ class OBModeratedObjectCommunityReviewPageState bool _needsBootstrap; CancelableOperation _requestOperation; + OBModeratedObjectLogsController _logsController; @override void initState() { super.initState(); _needsBootstrap = true; _isEditable = false; + _logsController = OBModeratedObjectLogsController(); } @override @@ -70,17 +76,27 @@ class OBModeratedObjectCommunityReviewPageState Expanded( child: ListView( children: [ - OBModeratedObjectDescription( - isEditable: _isEditable, + OBTileGroupTitle( + title: 'Object', + ), + OBModeratedObjectPreview( moderatedObject: widget.moderatedObject, ), + OBModeratedObjectDescription( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + onDescriptionChanged: _onDescriptionChanged), OBModeratedObjectCategory( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + onCategoryChanged: _onCategoryChanged), + OBModeratedObjectReportsPreview( isEditable: _isEditable, moderatedObject: widget.moderatedObject, ), - OBModeratedObjectReportsPreview( - isEditable: _isEditable, + OBModeratedObjectLogs( moderatedObject: widget.moderatedObject, + controller: _logsController, ) ], ), @@ -138,13 +154,25 @@ class OBModeratedObjectCommunityReviewPageState ); } + void _onDescriptionChanged(String newDescription) { + _refreshLogs(); + } + + void _onCategoryChanged(ModerationCategory newCategory) { + _refreshLogs(); + } + + void _refreshLogs() { + _logsController.refreshLogs(); + } + void _onWantsToApproveModeratedObject() async { try { _requestOperation = CancelableOperation.fromFuture( _userService.approveModeratedObject(widget.moderatedObject)); await _requestOperation.value; widget.moderatedObject.setIsApproved(); - Navigator.pop(context); + _updateIsEditable(); } catch (error) { _onError(error); } @@ -156,7 +184,7 @@ class OBModeratedObjectCommunityReviewPageState _userService.rejectModeratedObject(widget.moderatedObject)); await _requestOperation.value; widget.moderatedObject.setIsRejected(); - Navigator.pop(context); + _updateIsEditable(); } catch (error) { _onError(error); } @@ -167,6 +195,13 @@ class OBModeratedObjectCommunityReviewPageState widget.moderatedObject.status == ModeratedObjectStatus.pending; } + void _updateIsEditable() { + setState(() { + _isEditable = + widget.moderatedObject.status == ModeratedObjectStatus.pending; + }); + } + void _onError(error) async { if (error is HttpieConnectionRefusedError) { _toastService.error( diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index 74f3f4407..a60306742 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -5,6 +5,7 @@ import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/modera import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; @@ -12,6 +13,7 @@ import 'package:Openbook/widgets/buttons/button.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; @@ -68,6 +70,12 @@ class OBModeratedObjectGlobalReviewPageState Expanded( child: ListView( children: [ + OBTileGroupTitle( + title: 'Object', + ), + OBModeratedObjectPreview( + moderatedObject: widget.moderatedObject, + ), OBModeratedObjectDescription( isEditable: _isEditable, moderatedObject: widget.moderatedObject, diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index 287f3b81f..391d20e23 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -2,6 +2,7 @@ import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; @@ -20,55 +21,12 @@ class OBModeratedObject extends StatelessWidget { @override Widget build(BuildContext context) { - Widget widget; - - switch (moderatedObject.type) { - case ModeratedObjectType.post: - widget = Column( - mainAxisSize: MainAxisSize.min, - children: [ - OBPostHeader( - post: moderatedObject.contentObject, - hasActions: false, - ), - OBPostBody(moderatedObject.contentObject), - ], - ); - break; - case ModeratedObjectType.community: - widget = Column( - mainAxisSize: MainAxisSize.min, - children: [ - OBCommunityTile(moderatedObject.contentObject), - ], - ); - break; - case ModeratedObjectType.postComment: - PostComment postComment = moderatedObject.contentObject; - widget = Column( - children: [ - OBPostComment( - post: postComment.post, - postComment: moderatedObject.contentObject, - ), - ], - ); - break; - case ModeratedObjectType.user: - widget = Column( - children: [ - OBUserTile(moderatedObject.contentObject), - ], - ); - break; - default: - widget = const SizedBox(); - } - return Column( mainAxisSize: MainAxisSize.min, children: [ - widget, + OBModeratedObjectPreview( + moderatedObject: moderatedObject, + ), OBModeratedObjectActions( moderatedObject: moderatedObject, community: community, diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart new file mode 100644 index 000000000..7ecbbf984 --- /dev/null +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart @@ -0,0 +1,64 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; +import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; +import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; +import 'package:Openbook/widgets/tiles/community_tile.dart'; +import 'package:Openbook/widgets/tiles/user_tile.dart'; +import 'package:flutter/material.dart'; + +class OBModeratedObjectPreview extends StatelessWidget { + final ModeratedObject moderatedObject; + + const OBModeratedObjectPreview({Key key, @required this.moderatedObject}) + : super(key: key); + + @override + Widget build(BuildContext context) { + Widget widget; + + switch (moderatedObject.type) { + case ModeratedObjectType.post: + widget = Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBPostHeader( + post: moderatedObject.contentObject, + hasActions: false, + ), + OBPostBody(moderatedObject.contentObject), + ], + ); + break; + case ModeratedObjectType.community: + widget = Column( + mainAxisSize: MainAxisSize.min, + children: [ + OBCommunityTile(moderatedObject.contentObject), + ], + ); + break; + case ModeratedObjectType.postComment: + PostComment postComment = moderatedObject.contentObject; + widget = Column( + children: [ + OBPostComment( + post: postComment.post, + postComment: moderatedObject.contentObject, + ), + ], + ); + break; + case ModeratedObjectType.user: + widget = Column( + children: [ + OBUserTile(moderatedObject.contentObject), + ], + ); + break; + default: + widget = const SizedBox(); + } + return widget; + } +} From ec0ccc481efa27809b46f2f96fd8baeccb1bf46a Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 19:01:08 +0200 Subject: [PATCH 51/65] :sparkles: add no logs message --- .../moderated_object_community_review.dart | 3 +++ .../pages/moderated_object_global_review.dart | 1 + .../moderated_object_logs.dart | 20 +++++++++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart index d61b60138..ea69a2c9c 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart @@ -82,6 +82,9 @@ class OBModeratedObjectCommunityReviewPageState OBModeratedObjectPreview( moderatedObject: widget.moderatedObject, ), + SizedBox( + height: 10, + ), OBModeratedObjectDescription( isEditable: _isEditable, moderatedObject: widget.moderatedObject, diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index a60306742..26aacfc7d 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -76,6 +76,7 @@ class OBModeratedObjectGlobalReviewPageState OBModeratedObjectPreview( moderatedObject: widget.moderatedObject, ), + SizedBox(height: 10,), OBModeratedObjectDescription( isEditable: _isEditable, moderatedObject: widget.moderatedObject, diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart index 1e40ac65d..dcdfc57b1 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart @@ -7,6 +7,7 @@ import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/progress_indicator.dart'; import 'package:Openbook/widgets/theming/divider.dart'; +import 'package:Openbook/widgets/theming/secondary_text.dart'; import 'package:Openbook/widgets/tile_group_title.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; @@ -79,12 +80,19 @@ class OBModeratedObjectLogsState extends State { ) ], ) - : ListView.builder( - padding: EdgeInsets.all(0), - itemBuilder: _buildModerationLog, - itemCount: _logs.length, - shrinkWrap: true, - ), + : _logs.length > 0 + ? ListView.builder( + padding: EdgeInsets.all(0), + itemBuilder: _buildModerationLog, + itemCount: _logs.length, + shrinkWrap: true, + ) + : ListTile( + title: OBSecondaryText( + 'No logs available', + style: TextStyle(fontStyle: FontStyle.italic), + ), + ), ], ); } From 14b03368a54c36d666724cc11f05e15e97a55037 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 19:48:00 +0200 Subject: [PATCH 52/65] :sparkles: add status color to moderated object item --- .../moderated_object_update_status.dart | 17 ++++-- .../moderated_object_status.dart | 18 ++++--- .../moderated_object/moderated_object.dart | 5 +- .../moderated_object_status_circle.dart | 43 +++++++++++++++ .../tiles/moderated_object_status_tile.dart | 52 +++++++++++++++++++ 5 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 lib/widgets/moderated_object_status_circle.dart create mode 100644 lib/widgets/tiles/moderated_object_status_tile.dart diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart index 20f3babf9..038173588 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/modals/moderated_object_update_status.dart @@ -3,6 +3,7 @@ import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/buttons/button.dart'; import 'package:Openbook/widgets/checkbox.dart'; +import 'package:Openbook/widgets/moderated_object_status_circle.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; @@ -110,9 +111,19 @@ class OBModeratedObjectUpdateStatusModalState children: [ Expanded( child: ListTile( - title: OBText( - statusString, - style: TextStyle(fontWeight: FontWeight.bold), + title: Row( + children: [ + OBModeratedObjectStatusCircle( + status: status, + ), + const SizedBox( + width: 10, + ), + OBText( + statusString, + style: TextStyle(fontWeight: FontWeight.bold), + ) + ], ), //trailing: OBIcon(OBIcons.chevronRight), ), diff --git a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart index 304985278..6e6b6f506 100644 --- a/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart +++ b/lib/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart @@ -3,6 +3,7 @@ import 'package:Openbook/provider.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:Openbook/widgets/tiles/moderated_object_status_tile.dart'; import 'package:flutter/material.dart'; class OBModeratedObjectStatus extends StatelessWidget { @@ -31,10 +32,15 @@ class OBModeratedObjectStatus extends StatelessWidget { stream: moderatedObject.updateSubject, builder: (BuildContext context, AsyncSnapshot snapshot) { - return ListTile( - title: OBText(ModeratedObject.factory - .convertStatusToHumanReadableString(moderatedObject.status, capitalize: true)), - onTap: () async { + return OBModeratedObjectStatusTile( + moderatedObject: moderatedObject, + trailing: isEditable + ? const OBIcon( + OBIcons.edit, + themeColor: OBIconThemeColor.secondaryText, + ) + : null, + onPressed: (moderatedObject) async { if (!isEditable) return; OpenbookProviderState openbookProvider = OpenbookProvider.of(context); @@ -45,10 +51,6 @@ class OBModeratedObjectStatus extends StatelessWidget { if (newModerationStatus != null && onStatusChanged != null) onStatusChanged(newModerationStatus); }, - trailing: isEditable ? const OBIcon( - OBIcons.edit, - themeColor: OBIconThemeColor.secondaryText, - ) : null, ); }, ), diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index 391d20e23..6359bf951 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -31,7 +31,10 @@ class OBModeratedObject extends StatelessWidget { moderatedObject: moderatedObject, community: community, ), - OBDivider() + const SizedBox( + height: 10, + ), + const OBDivider() ], ); } diff --git a/lib/widgets/moderated_object_status_circle.dart b/lib/widgets/moderated_object_status_circle.dart new file mode 100644 index 000000000..cad19d433 --- /dev/null +++ b/lib/widgets/moderated_object_status_circle.dart @@ -0,0 +1,43 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/theme.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; +import 'package:pigment/pigment.dart'; + +class OBModeratedObjectStatusCircle extends StatelessWidget { + final ModeratedObjectStatus status; + + static double statusCircleSize = 10; + static String pendingColor = '#f48c42'; + + const OBModeratedObjectStatusCircle({Key key, @required this.status}) + : super(key: key); + + @override + Widget build(BuildContext context) { + OpenbookProviderState openbookProvider = OpenbookProvider.of(context); + OBTheme currentTheme = openbookProvider.themeService.getActiveTheme(); + + String circleColor; + switch (status) { + case ModeratedObjectStatus.rejected: + circleColor = currentTheme.dangerColor; + break; + case ModeratedObjectStatus.approved: + circleColor = currentTheme.successColor; + break; + case ModeratedObjectStatus.pending: + circleColor = pendingColor; + break; + default: + } + + return Container( + height: statusCircleSize, + width: statusCircleSize, + decoration: BoxDecoration( + color: Pigment.fromString(circleColor), + borderRadius: BorderRadius.circular(50))); + } +} diff --git a/lib/widgets/tiles/moderated_object_status_tile.dart b/lib/widgets/tiles/moderated_object_status_tile.dart new file mode 100644 index 000000000..90b2d7b7a --- /dev/null +++ b/lib/widgets/tiles/moderated_object_status_tile.dart @@ -0,0 +1,52 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/theme.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; +import 'package:pigment/pigment.dart'; + +import '../moderated_object_status_circle.dart'; + +class OBModeratedObjectStatusTile extends StatelessWidget { + final ModeratedObject moderatedObject; + final ValueChanged onPressed; + final Widget trailing; + + static double statusCircleSize = 10; + static String pendingColor = '#f48c42'; + + const OBModeratedObjectStatusTile( + {Key key, @required this.moderatedObject, this.onPressed, this.trailing}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: moderatedObject.updateSubject, + initialData: moderatedObject, + builder: (BuildContext context, AsyncSnapshot snapshot) { + ModeratedObject currentModeratedObject = snapshot.data; + + return ListTile( + title: Row( + children: [ + OBModeratedObjectStatusCircle( + status: moderatedObject.status, + ), + const SizedBox( + width: 10, + ), + OBText(ModeratedObject.factory.convertStatusToHumanReadableString( + currentModeratedObject.status, + capitalize: true)) + ], + ), + onTap: () async { + if (onPressed != null) onPressed(moderatedObject); + }, + trailing: trailing, + ); + }, + ); + } +} From 7a57e63838b86c12b4e69b6354c188601c3490f7 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 20:04:59 +0200 Subject: [PATCH 53/65] :sparkles: add reports count and status to moderated objs stream --- .../moderated_object/moderated_object.dart | 44 +++++++++++++++++++ lib/widgets/icon.dart | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index 6359bf951..e283b6390 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -1,15 +1,20 @@ import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/post_comment.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; import 'package:Openbook/widgets/theming/divider.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; import 'package:Openbook/widgets/tiles/community_tile.dart'; +import 'package:Openbook/widgets/tiles/moderated_object_status_tile.dart'; import 'package:Openbook/widgets/tiles/user_tile.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; class OBModeratedObject extends StatelessWidget { final ModeratedObject moderatedObject; @@ -22,11 +27,50 @@ class OBModeratedObject extends StatelessWidget { @override Widget build(BuildContext context) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + OBTileGroupTitle( + title: 'Object', + ), OBModeratedObjectPreview( moderatedObject: moderatedObject, ), + OBModeratedObjectCategory( + moderatedObject: moderatedObject, + isEditable: false, + ), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBTileGroupTitle( + title: 'Status', + ), + OBModeratedObjectStatusTile( + moderatedObject: moderatedObject, + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBTileGroupTitle( + title: 'Reports count', + ), + ListTile( + title: OBText(moderatedObject.reportsCount.toString())), + ], + ), + ), + ], + ), OBModeratedObjectActions( moderatedObject: moderatedObject, community: community, diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index ce3240982..8e75c58d7 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -197,7 +197,7 @@ class OBIcons { static const mutePost = OBIconData(nativeIcon: Icons.notifications_active); static const editPost = OBIconData(nativeIcon: Icons.edit); static const edit = OBIconData(nativeIcon: Icons.edit); - static const reviewModeratedObject = OBIconData(nativeIcon: Icons.edit); + static const reviewModeratedObject = OBIconData(nativeIcon: Icons.gavel); static const unmutePost = OBIconData(nativeIcon: Icons.notifications_off); static const deleteAccount = OBIconData(nativeIcon: Icons.delete_forever); static const account = OBIconData(nativeIcon: Icons.account_circle); From 6569b9d8ff5e977b6055feb3ff840c43df7b9dc9 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 20:45:35 +0200 Subject: [PATCH 54/65] :sparkles: add verified to moderated_object item --- .../pages/moderated_object_global_review.dart | 7 +++- .../moderated_object/moderated_object.dart | 39 ++++++++++++++++--- .../widgets/moderated_object_preview.dart | 8 ++-- lib/widgets/icon.dart | 4 +- lib/widgets/tile_group_title.dart | 8 +++- 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index 26aacfc7d..b8d3dac03 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -16,6 +16,7 @@ import 'package:Openbook/widgets/page_scaffold.dart'; import 'package:Openbook/widgets/tile_group_title.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; +import 'package:pigment/pigment.dart'; class OBModeratedObjectGlobalReviewPage extends StatefulWidget { final ModeratedObject moderatedObject; @@ -76,7 +77,9 @@ class OBModeratedObjectGlobalReviewPageState OBModeratedObjectPreview( moderatedObject: widget.moderatedObject, ), - SizedBox(height: 10,), + SizedBox( + height: 10, + ), OBModeratedObjectDescription( isEditable: _isEditable, moderatedObject: widget.moderatedObject, @@ -143,7 +146,6 @@ class OBModeratedObjectGlobalReviewPageState actions.add(Expanded( child: OBButton( size: OBButtonSize.large, - type: OBButtonType.success, child: Row( children: [ const OBIcon( @@ -159,6 +161,7 @@ class OBModeratedObjectGlobalReviewPageState ), onPressed: _onWantsToVerifyModeratedObject, isLoading: _requestInProgress, + color: Pigment.fromString('#5e9bff'), ), )); } diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index e283b6390..29e90993a 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -1,18 +1,13 @@ import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; -import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart'; -import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; -import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; -import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; +import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/theming/divider.dart'; import 'package:Openbook/widgets/theming/text.dart'; import 'package:Openbook/widgets/tile_group_title.dart'; -import 'package:Openbook/widgets/tiles/community_tile.dart'; import 'package:Openbook/widgets/tiles/moderated_object_status_tile.dart'; -import 'package:Openbook/widgets/tiles/user_tile.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -36,6 +31,9 @@ class OBModeratedObject extends StatelessWidget { OBModeratedObjectPreview( moderatedObject: moderatedObject, ), + const SizedBox( + height: 10, + ), OBModeratedObjectCategory( moderatedObject: moderatedObject, isEditable: false, @@ -71,6 +69,35 @@ class OBModeratedObject extends StatelessWidget { ), ], ), + OBTileGroupTitle( + title: community != null ? 'Verified by Openbook staff' : 'Verified', + ), + StreamBuilder( + stream: moderatedObject.updateSubject, + initialData: moderatedObject, + builder: + (BuildContext context, AsyncSnapshot snapshot) { + return Padding( + padding: EdgeInsets.all(15), + child: Row( + children: [ + OBIcon( + moderatedObject.verified + ? OBIcons.verify + : OBIcons.unverify, + size: OBIconSize.small, + ), + const SizedBox( + width: 10, + ), + OBText( + moderatedObject.verified ? 'True' : 'False', + ) + ], + ), + ); + }, + ), OBModeratedObjectActions( moderatedObject: moderatedObject, community: community, diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart index 7ecbbf984..eecf1ef7a 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart @@ -31,11 +31,9 @@ class OBModeratedObjectPreview extends StatelessWidget { ); break; case ModeratedObjectType.community: - widget = Column( - mainAxisSize: MainAxisSize.min, - children: [ - OBCommunityTile(moderatedObject.contentObject), - ], + widget = Padding( + padding: EdgeInsets.all(10), + child: OBCommunityTile(moderatedObject.contentObject), ); break; case ModeratedObjectType.postComment: diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index 8e75c58d7..5564d1978 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -218,8 +218,8 @@ class OBIcons { static const openPost = OBIconData(nativeIcon: Icons.lock_open); static const block = OBIconData(nativeIcon: Icons.block); static const chevronRight = OBIconData(nativeIcon: Icons.chevron_right); - static const verify = OBIconData(nativeIcon: Icons.lock_outline); - static const unverify = OBIconData(nativeIcon: Icons.lock_open); + static const verify = OBIconData(nativeIcon: Icons.check); + static const unverify = OBIconData(nativeIcon: Icons.close); static const success = OBIconData(filename: 'success-icon.png'); static const error = OBIconData(filename: 'error-icon.png'); static const warning = OBIconData(filename: 'warning-icon.png'); diff --git a/lib/widgets/tile_group_title.dart b/lib/widgets/tile_group_title.dart index b47638c07..c8fc6874c 100644 --- a/lib/widgets/tile_group_title.dart +++ b/lib/widgets/tile_group_title.dart @@ -3,17 +3,21 @@ import 'package:flutter/material.dart'; class OBTileGroupTitle extends StatelessWidget { final String title; + final TextStyle style; - const OBTileGroupTitle({Key key, this.title}) : super(key: key); + const OBTileGroupTitle({Key key, this.title, this.style}) : super(key: key); @override Widget build(BuildContext context) { + var finalStyle = TextStyle(fontWeight: FontWeight.bold); + if (style != null) finalStyle = finalStyle.merge(style); + return Padding( padding: EdgeInsets.symmetric(horizontal: 15, vertical: 5), child: OBText( title, size: OBTextSize.large, - style: TextStyle(fontWeight: FontWeight.bold), + style: finalStyle, ), ); } From dddb579d24011716b3afdbe2f314f15c5c4d84af Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 21:56:31 +0200 Subject: [PATCH 55/65] :sparkles: most filters working, lacking verified --- .../moderated_objects_filters.dart | 77 +++++++++--- .../moderated_objects/moderated_objects.dart | 112 ++++++++++++++---- .../moderated_object/moderated_object.dart | 1 + .../widgets/no_moderated_objects.dart | 3 - lib/services/user.dart | 29 +++-- lib/widgets/fields/checkbox_field.dart | 11 +- 6 files changed, 174 insertions(+), 59 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart index c2c598135..e97fa33c0 100644 --- a/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart +++ b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart @@ -2,6 +2,7 @@ import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/moderated_objects.dart'; import 'package:Openbook/widgets/fields/checkbox_field.dart'; import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/moderated_object_status_circle.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/buttons/button.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; @@ -58,8 +59,8 @@ class OBModeratedObjectsFiltersModalState ]; } - _selectedTypes = currentFilters.types; - _selectedStatuses = currentFilters.statuses; + _selectedTypes = currentFilters.types.toList(); + _selectedStatuses = currentFilters.statuses.toList(); _onlyVerified = currentFilters.onlyVerified; } @@ -82,8 +83,8 @@ class OBModeratedObjectsFiltersModalState child: OBButton( size: OBButtonSize.large, type: OBButtonType.highlight, - child: Text('Clear all'), - onPressed: _onWantsToClearFilters, + child: Text('Reset'), + onPressed: _onWantsToResetFilters, ), ), const SizedBox( @@ -146,11 +147,12 @@ class OBModeratedObjectsFiltersModalState String typeString = ModeratedObject.factory .convertTypeToHumanReadableString(type, capitalize: true); return OBCheckboxField( + titleStyle: TextStyle(fontWeight: FontWeight.normal), onTap: () { _onTypePressed(type); }, title: typeString, - value: _types.contains(type), + value: _selectedTypes.contains(type), ); } @@ -158,19 +160,49 @@ class OBModeratedObjectsFiltersModalState ModeratedObjectStatus status = _statuses[index]; String statusString = ModeratedObject.factory .convertStatusToHumanReadableString(status, capitalize: true); - return OBCheckboxField( - onTap: () { - _onStatusPressed(status); - }, - title: statusString, - value: _statuses.contains(status), + return Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsets.only(left: 15), + child: OBModeratedObjectStatusCircle( + status: status, + ), + ), + Expanded( + child: OBCheckboxField( + titleStyle: TextStyle(fontWeight: FontWeight.normal), + onTap: () { + _onStatusPressed(status); + }, + title: statusString, + value: _selectedStatuses.contains(status), + ), + ) + ], ); } Widget _buildIsVerifiedListTile() { - return OBCheckboxField( - title: 'Are verified', - value: _onlyVerified, + return Row( + children: [ + Padding( + padding: EdgeInsets.only(left: 15), + child: OBIcon(OBIcons.verify), + ), + Expanded( + child: OBCheckboxField( + titleStyle: TextStyle(fontWeight: FontWeight.normal), + title: 'Verified', + value: _onlyVerified, + onTap: () { + setState(() { + _onlyVerified = !_onlyVerified; + }); + }, + ), + ) + ], ); } @@ -189,20 +221,28 @@ class OBModeratedObjectsFiltersModalState _setRequestInProgress(true); await widget.moderatedObjectsPageController.setFilters( OBModeratedObjectsFilters( - types: _types, statuses: _statuses, onlyVerified: _onlyVerified)); + types: _selectedTypes, + statuses: _selectedStatuses, + onlyVerified: _onlyVerified)); _setRequestInProgress(false); Navigator.pop(context); } - void _onWantsToClearFilters() async { + void _onWantsToResetFilters() async { + OBModeratedObjectsFilters _defaultFilters = + OBModeratedObjectsFilters.makeDefault( + isGlobalModeration: + !widget.moderatedObjectsPageController.hasCommunity()); setState(() { - _selectedTypes = []; - _selectedStatuses = []; + _selectedTypes = _defaultFilters.types; + _selectedStatuses = _defaultFilters.statuses; + _onlyVerified = _defaultFilters.onlyVerified; }); } void _onTypePressed(ModeratedObjectType pressedType) { if (_selectedTypes.contains(pressedType)) { + if (_selectedTypes.length == 1) return; // Remove _removeSelectedType(pressedType); } else { @@ -225,6 +265,7 @@ class OBModeratedObjectsFiltersModalState void _onStatusPressed(ModeratedObjectStatus pressedStatus) { if (_selectedStatuses.contains(pressedStatus)) { + if (_selectedStatuses.length == 1) return; // Remove _removeSelectedStatus(pressedStatus); } else { diff --git a/lib/pages/home/pages/moderated_objects/moderated_objects.dart b/lib/pages/home/pages/moderated_objects/moderated_objects.dart index 9d9c538f3..65e7e376b 100644 --- a/lib/pages/home/pages/moderated_objects/moderated_objects.dart +++ b/lib/pages/home/pages/moderated_objects/moderated_objects.dart @@ -4,11 +4,14 @@ import 'package:Openbook/models/moderation/moderated_object_list.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart'; import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/modal_service.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/badges/badge.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/icon_button.dart'; import 'package:Openbook/widgets/load_more.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; -import 'package:Openbook/widgets/progress_indicator.dart'; import 'package:Openbook/widgets/theming/primary_color_container.dart'; import 'package:Openbook/widgets/tiles/loading_indicator_tile.dart'; import 'package:Openbook/widgets/tiles/retry_tile.dart'; @@ -41,6 +44,7 @@ class OBModeratedObjectsPageState extends State { UserService _userService; ToastService _toastService; + ModalService _modalService; CancelableOperation _loadMoreOperation; CancelableOperation _refreshModeratedObjectsOperation; @@ -53,8 +57,8 @@ class OBModeratedObjectsPageState extends State { void initState() { super.initState(); _community = widget.community; - _filters = - OBModeratedObjectsFilters(statuses: [], types: [], onlyVerified: false); + _filters = OBModeratedObjectsFilters.makeDefault( + isGlobalModeration: widget.community == null); _controller = OBModeratedObjectsPageController(state: this); _scrollController = ScrollController(); _moderatedObjects = []; @@ -73,11 +77,11 @@ class OBModeratedObjectsPageState extends State { @override Widget build(BuildContext context) { - var openbookProvider = OpenbookProvider.of(context); - _userService = openbookProvider.userService; - _toastService = openbookProvider.toastService; - if (_needsBootstrap) { + var openbookProvider = OpenbookProvider.of(context); + _userService = openbookProvider.userService; + _toastService = openbookProvider.toastService; + _modalService = openbookProvider.modalService; _bootstrap(); _needsBootstrap = false; } @@ -88,6 +92,7 @@ class OBModeratedObjectsPageState extends State { title: widget.community != null ? 'Community moderated objects' : 'Globally moderated objects', + trailing: _buildFiltersButton(), ), child: OBPrimaryColorContainer( child: Column( @@ -104,7 +109,7 @@ class OBModeratedObjectsPageState extends State { controller: _scrollController, physics: const ClampingScrollPhysics(), padding: EdgeInsets.all(0), - itemCount: _moderatedObjects.length, + itemCount: _moderatedObjects.length + 1, itemBuilder: _buildModeratedObject, ), onLoadMore: _loadMoreModeratedObjects), @@ -119,16 +124,7 @@ class OBModeratedObjectsPageState extends State { if (index == 0) { Widget moderatedObjectItem; - if (_refreshModeratedObjectsInProgress && _moderatedObjects.isEmpty) { - moderatedObjectItem = SizedBox( - child: Center( - child: Padding( - padding: EdgeInsets.only(top: 20), - child: OBProgressIndicator(), - ), - ), - ); - } else if (_moderatedObjects.length == 0) { + if (_moderatedObjects.isEmpty && !_refreshModeratedObjectsInProgress) { moderatedObjectItem = OBNoModeratedObjects( onWantsToRefreshModeratedObjects: _refresh, ); @@ -141,15 +137,39 @@ class OBModeratedObjectsPageState extends State { return moderatedObjectItem; } - int postIndex = index - 1; + int moderatedObjectIndex = index - 1; - var moderatedObject = _moderatedObjects[postIndex]; + var moderatedObject = _moderatedObjects[moderatedObjectIndex]; return OBModeratedObject( moderatedObject: moderatedObject, key: Key(moderatedObject.id.toString())); } + Widget _buildFiltersButton() { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + OBBadge( + count: _filters.count(), + ), + const SizedBox( + width: 10, + ), + OBIconButton( + OBIcons.filter, + themeColor: OBIconThemeColor.primaryAccent, + onPressed: _onWantsToOpenFilters, + ) + ], + ); + } + + void _onWantsToOpenFilters() { + _modalService.openModeratedObjectsFilters( + context: context, moderatedObjectsPageController: _controller); + } + void _bootstrap() { Future.delayed(Duration(milliseconds: 100), () { _refresh(); @@ -180,14 +200,23 @@ class OBModeratedObjectsPageState extends State { } Future _refreshModeratedObjects() async { + _setRefreshModeratedObjectsInProgress(true); try { if (widget.community == null) { _refreshModeratedObjectsOperation = CancelableOperation.fromFuture( - _userService.getGlobalModeratedObjects(count: itemsLoadMoreCount)); + _userService.getGlobalModeratedObjects( + count: itemsLoadMoreCount, + verified: _filters.onlyVerified, + statuses: _filters.statuses, + types: _filters.types)); } else { _refreshModeratedObjectsOperation = CancelableOperation.fromFuture( _userService.getCommunityModeratedObjects( - community: widget.community, count: itemsLoadMoreCount)); + community: widget.community, + count: itemsLoadMoreCount, + verified: _filters.onlyVerified, + statuses: _filters.statuses, + types: _filters.types)); } ModeratedObjectsList moderatedObjectList = @@ -195,6 +224,8 @@ class OBModeratedObjectsPageState extends State { _setModeratedObjects(moderatedObjectList.moderatedObjects); } catch (error) { _onError(error); + } finally { + _setRefreshModeratedObjectsInProgress(false); } } @@ -211,13 +242,20 @@ class OBModeratedObjectsPageState extends State { if (widget.community == null) { _loadMoreOperation = CancelableOperation.fromFuture( _userService.getGlobalModeratedObjects( - maxId: lastModeratedObjectId, count: itemsLoadMoreCount)); + maxId: lastModeratedObjectId, + count: itemsLoadMoreCount, + verified: _filters.onlyVerified, + statuses: _filters.statuses, + types: _filters.types)); } else { _loadMoreOperation = CancelableOperation.fromFuture( _userService.getCommunityModeratedObjects( community: _community, maxId: lastModeratedObjectId, - count: itemsLoadMoreCount)); + count: itemsLoadMoreCount, + verified: _filters.onlyVerified, + statuses: _filters.statuses, + types: _filters.types)); } var moreModeratedObjects = @@ -252,6 +290,13 @@ class OBModeratedObjectsPageState extends State { }); } + void _setRefreshModeratedObjectsInProgress( + bool refreshModeratedObjectsInProgress) { + setState(() { + _refreshModeratedObjectsInProgress = refreshModeratedObjectsInProgress; + }); + } + OBModeratedObjectsFilters getFilters() { return _filters.clone(); } @@ -279,6 +324,21 @@ class OBModeratedObjectsFilters { final List statuses; final bool onlyVerified; + static OBModeratedObjectsFilters makeDefault({@required isGlobalModeration}) { + List filterTypes = [ + ModeratedObjectType.postComment, + ModeratedObjectType.post, + ]; + if (isGlobalModeration) + filterTypes + .addAll([ModeratedObjectType.user, ModeratedObjectType.community]); + + return OBModeratedObjectsFilters( + statuses: [ModeratedObjectStatus.pending], + types: filterTypes, + onlyVerified: false); + } + OBModeratedObjectsFilters( {@required this.types, @required this.statuses, @@ -290,6 +350,10 @@ class OBModeratedObjectsFilters { statuses: statuses.toList(), onlyVerified: onlyVerified); } + + int count() { + return statuses.length + types.length + (onlyVerified ? 1 : 0); + } } class OBModeratedObjectsPageController { diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index 29e90993a..3f2e1400c 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -21,6 +21,7 @@ class OBModeratedObject extends StatelessWidget { @override Widget build(BuildContext context) { + return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart b/lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart index 87b6ecea2..a6377cc49 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/no_moderated_objects.dart @@ -1,6 +1,3 @@ -import 'package:Openbook/models/user.dart'; -import 'package:Openbook/provider.dart'; -import 'package:Openbook/services/user.dart'; import 'package:Openbook/widgets/alerts/button_alert.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:flutter/material.dart'; diff --git a/lib/services/user.dart b/lib/services/user.dart index a2675afe7..8d09f84b3 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -1510,21 +1510,24 @@ class UserService { maxId: maxId, verified: verified, types: types != null - ? types.map((status) => - ModeratedObject.factory.convertTypeToString(status)) + ? types + .map((ModeratedObjectType type) => + ModeratedObject.factory.convertTypeToString(type)) + .toList() : null, statuses: statuses != null - ? statuses.map((status) => ModeratedObject.factory - .convertStatusToString(status)) + ? statuses + .map( + (ModeratedObjectStatus status) => + ModeratedObject.factory + .convertStatusToString(status)) + .toList() : null, count: count); _checkResponseIsOk(response); - var what = json.decode(response.body); - print(what); - return ModeratedObjectsList.fromJson(json.decode(response.body)); } @@ -1540,12 +1543,16 @@ class UserService { maxId: maxId, verified: verified, types: types != null - ? types.map( - (status) => ModeratedObject.factory.convertTypeToString(status)) + ? types + .map((status) => + ModeratedObject.factory.convertTypeToString(status)) + .toList() : null, statuses: statuses != null - ? statuses.map((status) => - ModeratedObject.factory.convertStatusToString(status)) + ? statuses + .map((status) => + ModeratedObject.factory.convertStatusToString(status)) + .toList() : null, count: count); diff --git a/lib/widgets/fields/checkbox_field.dart b/lib/widgets/fields/checkbox_field.dart index b43df3cab..32eb0afc6 100644 --- a/lib/widgets/fields/checkbox_field.dart +++ b/lib/widgets/fields/checkbox_field.dart @@ -10,6 +10,7 @@ class OBCheckboxField extends StatelessWidget { final String title; final String subtitle; final bool isDisabled; + final TextStyle titleStyle; OBCheckboxField( {@required this.value, @@ -17,17 +18,21 @@ class OBCheckboxField extends StatelessWidget { this.onTap, this.leading, @required this.title, - this.isDisabled = false}); + this.isDisabled = false, + this.titleStyle}); @override Widget build(BuildContext context) { + TextStyle finalTitleStyle = TextStyle(fontWeight: FontWeight.bold); + if (titleStyle != null) finalTitleStyle = finalTitleStyle.merge(titleStyle); + Widget field = MergeSemantics( child: ListTile( selected: value, leading: leading, title: OBText( title, - style: TextStyle(fontWeight: FontWeight.bold), + style: finalTitleStyle, ), subtitle: subtitle != null ? OBText(subtitle) : null, trailing: Row( @@ -39,7 +44,7 @@ class OBCheckboxField extends StatelessWidget { ], ), onTap: () { - if (!isDisabled) onTap(); + if (!isDisabled && onTap != null) onTap(); }), ); From 688a48630e8d39414b4e1365eb296783beb08263 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 21:58:51 +0200 Subject: [PATCH 56/65] :bug: fix background is other color --- .../pages/moderated_object_global_review.dart | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart index b8d3dac03..2e52c5222 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_global_review.dart @@ -13,6 +13,7 @@ import 'package:Openbook/widgets/buttons/button.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; import 'package:Openbook/widgets/tile_group_title.dart'; import 'package:async/async.dart'; import 'package:flutter/material.dart'; @@ -66,49 +67,51 @@ class OBModeratedObjectGlobalReviewPageState navigationBar: OBThemedNavigationBar( title: 'Review moderated object', ), - child: Column( - children: [ - Expanded( - child: ListView( - children: [ - OBTileGroupTitle( - title: 'Object', - ), - OBModeratedObjectPreview( - moderatedObject: widget.moderatedObject, - ), - SizedBox( - height: 10, - ), - OBModeratedObjectDescription( - isEditable: _isEditable, + child: OBPrimaryColorContainer( + child: Column( + children: [ + Expanded( + child: ListView( + children: [ + OBTileGroupTitle( + title: 'Object', + ), + OBModeratedObjectPreview( + moderatedObject: widget.moderatedObject, + ), + SizedBox( + height: 10, + ), + OBModeratedObjectDescription( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + onDescriptionChanged: _onDescriptionChanged), + OBModeratedObjectCategory( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + onCategoryChanged: _onCategoryChanged), + OBModeratedObjectStatus( moderatedObject: widget.moderatedObject, - onDescriptionChanged: _onDescriptionChanged), - OBModeratedObjectCategory( isEditable: _isEditable, + onStatusChanged: _onStatusChanged, + ), + OBModeratedObjectReportsPreview( + isEditable: _isEditable, + moderatedObject: widget.moderatedObject, + ), + OBModeratedObjectLogs( moderatedObject: widget.moderatedObject, - onCategoryChanged: _onCategoryChanged), - OBModeratedObjectStatus( - moderatedObject: widget.moderatedObject, - isEditable: _isEditable, - onStatusChanged: _onStatusChanged, - ), - OBModeratedObjectReportsPreview( - isEditable: _isEditable, - moderatedObject: widget.moderatedObject, - ), - OBModeratedObjectLogs( - moderatedObject: widget.moderatedObject, - controller: _logsController, - ) - ], + controller: _logsController, + ) + ], + ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), - child: _buildPrimaryActions(), - ) - ], + Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20), + child: _buildPrimaryActions(), + ) + ], + ), ), ); } From bd78fa309e86b395190b27fc987bf583ff954ea3 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 22:02:23 +0200 Subject: [PATCH 57/65] :bug: filters had weird white bg --- lib/widgets/checkbox.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/checkbox.dart b/lib/widgets/checkbox.dart index e67c628bb..53ef7a931 100644 --- a/lib/widgets/checkbox.dart +++ b/lib/widgets/checkbox.dart @@ -16,7 +16,7 @@ class OBCheckbox extends StatelessWidget { return GestureDetector( child: DecoratedBox( decoration: BoxDecoration( - color: Colors.white, borderRadius: BorderRadius.circular(50)), + borderRadius: BorderRadius.circular(50)), child: Center( child: OBIcon( value ? OBIcons.checkCircleSelected : OBIcons.checkCircle, From 5a9396c64149c4310145fc23520e7aeaa599e78d Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Mon, 3 Jun 2019 22:05:26 +0200 Subject: [PATCH 58/65] :sparkles: go to community when tapping community tile --- .../widgets/moderated_object_preview.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart index eecf1ef7a..f32d3ac4d 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart @@ -1,6 +1,8 @@ +import 'package:Openbook/models/community.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/post_comment.dart'; import 'package:Openbook/pages/home/pages/post_comments/widgets/post_comment/post_comment.dart'; +import 'package:Openbook/provider.dart'; import 'package:Openbook/widgets/post/widgets/post-body/post_body.dart'; import 'package:Openbook/widgets/post/widgets/post_header/post_header.dart'; import 'package:Openbook/widgets/tiles/community_tile.dart'; @@ -33,7 +35,15 @@ class OBModeratedObjectPreview extends StatelessWidget { case ModeratedObjectType.community: widget = Padding( padding: EdgeInsets.all(10), - child: OBCommunityTile(moderatedObject.contentObject), + child: OBCommunityTile( + moderatedObject.contentObject, + onCommunityTilePressed: (Community community) { + OpenbookProviderState openbookProvider = + OpenbookProvider.of(context); + openbookProvider.navigationService + .navigateToCommunity(community: community, context: context); + }, + ), ); break; case ModeratedObjectType.postComment: From 3bc95656d871e4d1fc543e0381e40977a4a67a7d Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Tue, 4 Jun 2019 10:57:13 +0200 Subject: [PATCH 59/65] :recycle: make verified filter verified only --- .../moderated_objects_filters/moderated_objects_filters.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart index e97fa33c0..0af6af9ac 100644 --- a/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart +++ b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart @@ -193,7 +193,7 @@ class OBModeratedObjectsFiltersModalState Expanded( child: OBCheckboxField( titleStyle: TextStyle(fontWeight: FontWeight.normal), - title: 'Verified', + title: 'Only verified', value: _onlyVerified, onTap: () { setState(() { From 25e4a600c7e8e85a46635ef2825fcc8197044973 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Tue, 4 Jun 2019 11:51:08 +0200 Subject: [PATCH 60/65] :sparkles: community moderated objects view works --- .../manage_community/manage_community.dart | 5 +- .../moderated_objects/moderated_objects.dart | 1 + .../moderated_object_community_review.dart | 96 +++++++++++++------ .../moderated_object/moderated_object.dart | 1 - .../widgets/moderated_object_actions.dart | 1 - 5 files changed, 71 insertions(+), 33 deletions(-) diff --git a/lib/pages/home/pages/community/pages/manage_community/manage_community.dart b/lib/pages/home/pages/community/pages/manage_community/manage_community.dart index d6bfbe720..fc3a4782d 100644 --- a/lib/pages/home/pages/community/pages/manage_community/manage_community.dart +++ b/lib/pages/home/pages/community/pages/manage_community/manage_community.dart @@ -14,6 +14,7 @@ import 'package:flutter/material.dart'; class OBManageCommunityPage extends StatelessWidget { final Community community; + const OBManageCommunityPage({@required this.community}); @override @@ -90,10 +91,10 @@ class OBManageCommunityPage extends StatelessWidget { if (loggedInUser.canBanOrUnbanUsersInCommunity(community)) { menuListTiles.add(ListTile( - leading: const OBIcon(OBIcons.communityBannedUsers), + leading: const OBIcon(OBIcons.communityModerators), title: const OBText('Moderation reports'), subtitle: const OBText( - 'See the community moderation reports.', + 'Review the community moderation reports.', style: listItemSubtitleStyle, ), onTap: () { diff --git a/lib/pages/home/pages/moderated_objects/moderated_objects.dart b/lib/pages/home/pages/moderated_objects/moderated_objects.dart index 65e7e376b..3b6389031 100644 --- a/lib/pages/home/pages/moderated_objects/moderated_objects.dart +++ b/lib/pages/home/pages/moderated_objects/moderated_objects.dart @@ -143,6 +143,7 @@ class OBModeratedObjectsPageState extends State { return OBModeratedObject( moderatedObject: moderatedObject, + community: widget.community, key: Key(moderatedObject.id.toString())); } diff --git a/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart index ea69a2c9c..cd3c70ada 100644 --- a/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart +++ b/lib/pages/home/pages/moderated_objects/pages/moderated_object_community_review.dart @@ -5,6 +5,7 @@ import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/modera import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_description/moderated_object_description.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_logs/moderated_object_logs.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_reports_preview/moderated_object_reports_preview.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_status/moderated_object_status.dart'; import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart'; import 'package:Openbook/provider.dart'; import 'package:Openbook/services/toast.dart'; @@ -49,6 +50,7 @@ class OBModeratedObjectCommunityReviewPageState _needsBootstrap = true; _isEditable = false; _logsController = OBModeratedObjectLogsController(); + _requestInProgress = false; } @override @@ -93,6 +95,10 @@ class OBModeratedObjectCommunityReviewPageState isEditable: _isEditable, moderatedObject: widget.moderatedObject, onCategoryChanged: _onCategoryChanged), + OBModeratedObjectStatus( + moderatedObject: widget.moderatedObject, + isEditable: false, + ), OBModeratedObjectReportsPreview( isEditable: _isEditable, moderatedObject: widget.moderatedObject, @@ -116,40 +122,23 @@ class OBModeratedObjectCommunityReviewPageState Widget _buildPrimaryActions() { List actions = []; - if (widget.moderatedObject.status == ModeratedObjectStatus.pending) { + if (widget.moderatedObject.verified) { + actions.add(_buildVerifiedButton()); + } else if (widget.moderatedObject.status != ModeratedObjectStatus.pending) { + if (widget.moderatedObject.status == ModeratedObjectStatus.approved) { + actions.add(_buildRejectButton()); + } else if (widget.moderatedObject.status == + ModeratedObjectStatus.rejected) { + actions.add(_buildApproveButton()); + } + } else { actions.addAll([ - Expanded( - child: OBButton( - size: OBButtonSize.large, - type: OBButtonType.danger, - child: Text('Reject'), - onPressed: _onWantsToRejectModeratedObject, - isLoading: _requestInProgress, - ), - ), + _buildRejectButton(), const SizedBox( width: 20, ), - Expanded( - child: OBButton( - size: OBButtonSize.large, - child: Text('Approve'), - onPressed: _onWantsToApproveModeratedObject, - isLoading: _requestInProgress, - ), - ) + _buildApproveButton() ]); - } else { - actions.add( - Expanded( - child: OBButton( - size: OBButtonSize.large, - type: OBButtonType.primary, - child: Text('This item has been verified'), - onPressed: null, - ), - ), - ); } return Row( @@ -157,6 +146,41 @@ class OBModeratedObjectCommunityReviewPageState ); } + Widget _buildRejectButton() { + return Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.danger, + child: Text('Reject'), + onPressed: _onWantsToRejectModeratedObject, + isLoading: _requestInProgress, + ), + ); + } + + Widget _buildApproveButton() { + return Expanded( + child: OBButton( + size: OBButtonSize.large, + child: Text('Approve'), + type: OBButtonType.success, + onPressed: _onWantsToApproveModeratedObject, + isLoading: _requestInProgress, + ), + ); + } + + Widget _buildVerifiedButton() { + return Expanded( + child: OBButton( + size: OBButtonSize.large, + type: OBButtonType.highlight, + child: Text('This item has been verified'), + onPressed: null, + ), + ); + } + void _onDescriptionChanged(String newDescription) { _refreshLogs(); } @@ -170,6 +194,8 @@ class OBModeratedObjectCommunityReviewPageState } void _onWantsToApproveModeratedObject() async { + _setRequestInProgress(true); + try { _requestOperation = CancelableOperation.fromFuture( _userService.approveModeratedObject(widget.moderatedObject)); @@ -178,10 +204,14 @@ class OBModeratedObjectCommunityReviewPageState _updateIsEditable(); } catch (error) { _onError(error); + } finally { + _setRequestInProgress(false); } } void _onWantsToRejectModeratedObject() async { + _setRequestInProgress(true); + try { _requestOperation = CancelableOperation.fromFuture( _userService.rejectModeratedObject(widget.moderatedObject)); @@ -190,9 +220,17 @@ class OBModeratedObjectCommunityReviewPageState _updateIsEditable(); } catch (error) { _onError(error); + } finally { + _setRequestInProgress(false); } } + void _setRequestInProgress(requestInProgress) { + setState(() { + _requestInProgress = requestInProgress; + }); + } + void _bootstrap() { _isEditable = widget.moderatedObject.status == ModeratedObjectStatus.pending; diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart index 3f2e1400c..29e90993a 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/moderated_object.dart @@ -21,7 +21,6 @@ class OBModeratedObject extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart index 4779044ca..53713969a 100644 --- a/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart +++ b/lib/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_actions.dart @@ -35,7 +35,6 @@ class OBModeratedObjectActions extends StatelessWidget { onPressed: () { OpenbookProviderState openbookProvider = OpenbookProvider.of(context); - if (community != null) { openbookProvider.navigationService .navigateToModeratedObjectCommunityReview( From aaa928c41265d9966ea4d6dd93877fb31a10ae89 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Tue, 4 Jun 2019 14:34:20 +0200 Subject: [PATCH 61/65] :sparkles: add my pending moderation tasks page --- lib/models/community.dart | 8 ++ lib/models/moderation/moderation_penalty.dart | 42 +++++++ .../moderation/moderation_penalty_list.dart | 20 ++++ lib/pages/home/pages/menu/menu.dart | 10 +- .../my_moderation_tasks.dart | 112 ++++++++++++++++++ lib/services/moderation_api.dart | 29 +++++ lib/services/navigation_service.dart | 10 ++ lib/services/user.dart | 19 +++ lib/widgets/icon.dart | 3 +- 9 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 lib/models/moderation/moderation_penalty.dart create mode 100644 lib/models/moderation/moderation_penalty_list.dart create mode 100644 lib/pages/home/pages/menu/pages/my_moderation_tasks/my_moderation_tasks.dart diff --git a/lib/models/community.dart b/lib/models/community.dart index a2a5dd175..c6571b1ca 100644 --- a/lib/models/community.dart +++ b/lib/models/community.dart @@ -53,6 +53,8 @@ class Community extends UpdatableModel { String userAdjective; String usersAdjective; int membersCount; + int pendingModeratedObjectsCount; + CommunityType type; // Whether the user has been invited to the community @@ -97,6 +99,7 @@ class Community extends UpdatableModel { this.isFavorite, this.invitesEnabled, this.membersCount, + this.pendingModeratedObjectsCount, this.categories}); bool hasDescription() { @@ -209,6 +212,10 @@ class Community extends UpdatableModel { isReported = json['is_reported']; } + if (json.containsKey('pending_moderated_objects_count')) { + pendingModeratedObjectsCount = json['pending_moderated_objects_count']; + } + if (json.containsKey('color')) { color = json['color']; } @@ -280,6 +287,7 @@ class CommunityFactory extends UpdatableModelFactory { isReported: json['is_reported'], isFavorite: json['is_favorite'], invitesEnabled: json['invites_enabled'], + pendingModeratedObjectsCount: json['pending_moderated_objects_count'], cover: json['cover'], color: json['color'], memberships: parseMemberships(json['memberships']), diff --git a/lib/models/moderation/moderation_penalty.dart b/lib/models/moderation/moderation_penalty.dart new file mode 100644 index 000000000..2ba9d53da --- /dev/null +++ b/lib/models/moderation/moderation_penalty.dart @@ -0,0 +1,42 @@ +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_category.dart'; +import 'package:Openbook/models/user.dart'; + +class ModerationPenalty { + final User user; + final DateTime expiration; + final ModeratedObject moderatedObject; + + ModerationPenalty({this.user, this.expiration, this.moderatedObject}); + + factory ModerationPenalty.fromJson(Map parsedJson) { + return ModerationPenalty( + user: parseUser( + parsedJson['user'], + ), + moderatedObject: parseModeratedObject( + parsedJson['moderated_object'], + ), + expiration: parseExpiration(parsedJson['expiration'])); + } + + static User parseUser(Map rawActor) { + if (rawActor == null) return null; + return User.fromJson(rawActor); + } + + static ModerationCategory parseCategory(Map rawModerationCategory) { + if (rawModerationCategory == null) return null; + return ModerationCategory.fromJson(rawModerationCategory); + } + + static DateTime parseExpiration(String expiration) { + if (expiration == null) return null; + return DateTime.parse(expiration).toLocal(); + } + + static ModeratedObject parseModeratedObject(Map rawModeratedObject) { + if (rawModeratedObject == null) return null; + return ModeratedObject.fromJSON(rawModeratedObject); + } +} diff --git a/lib/models/moderation/moderation_penalty_list.dart b/lib/models/moderation/moderation_penalty_list.dart new file mode 100644 index 000000000..7dae428a0 --- /dev/null +++ b/lib/models/moderation/moderation_penalty_list.dart @@ -0,0 +1,20 @@ +import 'package:Openbook/models/moderation/moderation_penalty.dart'; + +class ModerationPenaltiesList { + final List moderationPenalties; + + ModerationPenaltiesList({ + this.moderationPenalties, + }); + + factory ModerationPenaltiesList.fromJson(List parsedJson) { + List moderationPenalties = parsedJson + .map((moderationPenaltyJson) => + ModerationPenalty.fromJson(moderationPenaltyJson)) + .toList(); + + return new ModerationPenaltiesList( + moderationPenalties: moderationPenalties, + ); + } +} diff --git a/lib/pages/home/pages/menu/menu.dart b/lib/pages/home/pages/menu/menu.dart index 98d810392..186314445 100644 --- a/lib/pages/home/pages/menu/menu.dart +++ b/lib/pages/home/pages/menu/menu.dart @@ -71,6 +71,14 @@ class OBMainMenuPage extends StatelessWidget { navigationService.navigateToUserInvites(context: context); }, ), + ListTile( + leading: const OBIcon(OBIcons.communityModerators), + title: OBText('My pending moderation tasks'), + onTap: () { + navigationService.navigateToMyModerationTasksPage( + context: context); + }, + ), ListTile( leading: const OBIcon(OBIcons.settings), title: OBText('Settings'), @@ -115,7 +123,7 @@ class OBMainMenuPage extends StatelessWidget { return const SizedBox(); return ListTile( - leading: const OBIcon(OBIcons.communityModerators), + leading: const OBIcon(OBIcons.globalModerator), title: OBText('Global moderation'), onTap: () async { navigationService.navigateToGlobalModeratedObjects( diff --git a/lib/pages/home/pages/menu/pages/my_moderation_tasks/my_moderation_tasks.dart b/lib/pages/home/pages/menu/pages/my_moderation_tasks/my_moderation_tasks.dart new file mode 100644 index 000000000..c4107e295 --- /dev/null +++ b/lib/pages/home/pages/menu/pages/my_moderation_tasks/my_moderation_tasks.dart @@ -0,0 +1,112 @@ +import 'dart:async'; + +import 'package:Openbook/models/communities_list.dart'; +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/services/navigation_service.dart'; +import 'package:Openbook/widgets/badges/badge.dart'; +import 'package:Openbook/widgets/http_list.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:Openbook/widgets/tiles/community_tile.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBMyModerationTasksPage extends StatefulWidget { + @override + State createState() { + return OBMyModerationTasksPageState(); + } +} + +class OBMyModerationTasksPageState extends State { + UserService _userService; + NavigationService _navigationService; + + OBHttpListController _httpListController; + bool _needsBootstrap; + + @override + void initState() { + super.initState(); + _httpListController = OBHttpListController(); + _needsBootstrap = true; + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + var provider = OpenbookProvider.of(context); + _userService = provider.userService; + _navigationService = provider.navigationService; + _needsBootstrap = false; + } + + return OBCupertinoPageScaffold( + navigationBar: OBThemedNavigationBar( + title: 'Pending moderation tasks', + ), + child: OBPrimaryColorContainer( + child: OBHttpList( + padding: EdgeInsets.all(15), + controller: _httpListController, + listItemBuilder: _buildPendingModeratedObjectsCommunityListItem, + listRefresher: _refreshPendingModeratedObjectsCommunities, + listOnScrollLoader: _loadMorePendingModeratedObjectsCommunities, + resourceSingularName: 'pending moderation task', + resourcePluralName: 'pending moderation tasks', + ), + ), + ); + } + + Widget _buildPendingModeratedObjectsCommunityListItem( + BuildContext context, Community community) { + return GestureDetector( + onTap: () => + _onPendingModeratedObjectsCommunityListItemPressed(community), + child: Row( + children: [ + Expanded( + child: OBCommunityTile(community), + ), + SizedBox( + width: 20, + ), + OBBadge( + size: 25, + count: community.pendingModeratedObjectsCount, + ) + ], + ), + ); + } + + void _onPendingModeratedObjectsCommunityListItemPressed(Community community) { + _navigationService.navigateToCommunityModeratedObjects( + community: community, context: context); + } + + Future> _refreshPendingModeratedObjectsCommunities() async { + CommunitiesList pendingModeratedObjectsCommunities = + await _userService.getPendingModeratedObjectsCommunities(); + return pendingModeratedObjectsCommunities.communities; + } + + Future> _loadMorePendingModeratedObjectsCommunities( + List pendingModeratedObjectsCommunitiesList) async { + var lastPendingModeratedObjectsCommunity = + pendingModeratedObjectsCommunitiesList.last; + var lastPendingModeratedObjectsCommunityId = + lastPendingModeratedObjectsCommunity.id; + var morePendingModeratedObjectsCommunities = + (await _userService.getPendingModeratedObjectsCommunities( + maxId: lastPendingModeratedObjectsCommunityId, + count: 10, + )) + .communities; + return morePendingModeratedObjectsCommunities; + } +} diff --git a/lib/services/moderation_api.dart b/lib/services/moderation_api.dart index 823768862..03935869a 100644 --- a/lib/services/moderation_api.dart +++ b/lib/services/moderation_api.dart @@ -9,6 +9,10 @@ class ModerationApiService { static const GET_GLOBAL_MODERATED_OBJECTS_PATH = 'api/moderation/moderated-objects/global/'; + static const USER_MODERATION_PENALTIES_PATH = + 'api/moderation/user/penalties/'; + static const USER_PENDING_MODERATED_OBJECTS_COMMUNITIES_PATH = + 'api/moderation/user/pending-moderated-objects-communities/'; static const GET_MODERATION_CATEGORIES_PATH = 'api/moderation/categories/'; static const MODERATED_OBJECT_PATH = 'api/moderation/moderated-objects/{moderatedObjectId}/'; @@ -100,6 +104,31 @@ class ModerationApiService { return _httpService.get(_makeApiUrl(path), appendAuthorizationToken: true); } + Future getUserModerationPenalties({int maxId, int count}) { + Map queryParams = {}; + if (count != null) queryParams['count'] = count; + + if (maxId != null) queryParams['max_id'] = maxId; + + String path = USER_MODERATION_PENALTIES_PATH; + + return _httpService.get(_makeApiUrl(path), + queryParameters: queryParams, appendAuthorizationToken: true); + } + + Future getUserPendingModeratedObjectsCommunities( + {int maxId, int count}) { + Map queryParams = {}; + if (count != null) queryParams['count'] = count; + + if (maxId != null) queryParams['max_id'] = maxId; + + String path = USER_PENDING_MODERATED_OBJECTS_COMMUNITIES_PATH; + + return _httpService.get(_makeApiUrl(path), + queryParameters: queryParams, appendAuthorizationToken: true); + } + Future verifyModeratedObjectWithId(int moderatedObjectId) { String path = _makeVerifyModeratedObjectsPath(moderatedObjectId); diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index f637fc303..1caf27e08 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -36,6 +36,7 @@ import 'package:Openbook/pages/home/pages/menu/pages/followers.dart'; import 'package:Openbook/pages/home/pages/menu/pages/following.dart'; import 'package:Openbook/pages/home/pages/menu/pages/follows_list/follows_list.dart'; import 'package:Openbook/pages/home/pages/menu/pages/follows_lists/follows_lists.dart'; +import 'package:Openbook/pages/home/pages/menu/pages/my_moderation_tasks/my_moderation_tasks.dart'; import 'package:Openbook/pages/home/pages/menu/pages/settings/pages/account_settings/account_settings.dart'; import 'package:Openbook/pages/home/pages/menu/pages/settings/pages/account_settings/pages/blocked_users.dart'; import 'package:Openbook/pages/home/pages/menu/pages/settings/pages/application_settings.dart'; @@ -611,6 +612,15 @@ class NavigationService { ))); } + Future navigateToMyModerationTasksPage( + {@required BuildContext context}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obMyModerationTasksPage'), + widget: OBMyModerationTasksPage())); + } + Future navigateToBlankPageWithWidget( {@required BuildContext context, @required String navBarTitle, diff --git a/lib/services/user.dart b/lib/services/user.dart index 8d09f84b3..b830273b6 100644 --- a/lib/services/user.dart +++ b/lib/services/user.dart @@ -22,6 +22,7 @@ import 'package:Openbook/models/moderation/moderated_object_list.dart'; import 'package:Openbook/models/moderation/moderated_object_log_list.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/moderation/moderation_category_list.dart'; +import 'package:Openbook/models/moderation/moderation_penalty_list.dart'; import 'package:Openbook/models/moderation/moderation_report_list.dart'; import 'package:Openbook/models/notifications/notification.dart'; import 'package:Openbook/models/notifications/notifications_list.dart'; @@ -1599,6 +1600,24 @@ class UserService { return ModerationReportsList.fromJson(json.decode(response.body)); } + Future getModerationPenalties( + {int maxId, int count}) async { + HttpieResponse response = await _moderationApiService + .getUserModerationPenalties(maxId: maxId, count: count); + _checkResponseIsOk(response); + + return ModerationPenaltiesList.fromJson(json.decode(response.body)); + } + + Future getPendingModeratedObjectsCommunities( + {int maxId, int count}) async { + HttpieResponse response = await _moderationApiService + .getUserPendingModeratedObjectsCommunities(maxId: maxId, count: count); + _checkResponseIsOk(response); + + return CommunitiesList.fromJson(json.decode(response.body)); + } + Future unverifyModeratedObject(ModeratedObject moderatedObject) async { HttpieResponse response = await _moderationApiService .unverifyModeratedObjectWithId(moderatedObject.id); diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index 5564d1978..07f2a6861 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -167,7 +167,7 @@ class OBIcons { static const disconnect = OBIconData(nativeIcon: Icons.remove_circle_outline); static const deletePost = OBIconData(nativeIcon: Icons.delete); static const clear = OBIconData(nativeIcon: Icons.delete); - static const report = OBIconData(nativeIcon: Icons.report); + static const report = OBIconData(nativeIcon: Icons.flag); static const filter = OBIconData(nativeIcon: Icons.tune); static const gallery = OBIconData(nativeIcon: Icons.apps); static const camera = OBIconData(nativeIcon: Icons.camera_alt); @@ -220,6 +220,7 @@ class OBIcons { static const chevronRight = OBIconData(nativeIcon: Icons.chevron_right); static const verify = OBIconData(nativeIcon: Icons.check); static const unverify = OBIconData(nativeIcon: Icons.close); + static const globalModerator = OBIconData(nativeIcon: Icons.account_balance); static const success = OBIconData(filename: 'success-icon.png'); static const error = OBIconData(filename: 'error-icon.png'); static const warning = OBIconData(filename: 'warning-icon.png'); From 728b7b86ffde565a4770d7620e0e9cb9978d26e4 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Tue, 4 Jun 2019 15:15:07 +0200 Subject: [PATCH 62/65] :sparkles: add my moderation penalties page --- lib/models/moderation/moderation_penalty.dart | 48 ++++++++++- lib/pages/home/pages/menu/menu.dart | 8 ++ .../my_moderation_penalties.dart | 86 +++++++++++++++++++ .../moderation_penalty.dart | 80 +++++++++++++++++ .../widgets/moderation_penalty_actions.dart | 52 +++++++++++ lib/services/navigation_service.dart | 10 +++ lib/widgets/icon.dart | 3 +- 7 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 lib/pages/home/pages/menu/pages/my_moderation_penalties/my_moderation_penalties.dart create mode 100644 lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/moderation_penalty.dart create mode 100644 lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/widgets/moderation_penalty_actions.dart diff --git a/lib/models/moderation/moderation_penalty.dart b/lib/models/moderation/moderation_penalty.dart index 2ba9d53da..e9039d738 100644 --- a/lib/models/moderation/moderation_penalty.dart +++ b/lib/models/moderation/moderation_penalty.dart @@ -1,19 +1,34 @@ +import 'package:Openbook/libs/str_utils.dart'; import 'package:Openbook/models/moderation/moderated_object.dart'; import 'package:Openbook/models/moderation/moderation_category.dart'; import 'package:Openbook/models/user.dart'; class ModerationPenalty { + static String moderationPenaltyTypeSuspension = 'S'; + + final int id; final User user; final DateTime expiration; final ModeratedObject moderatedObject; + final ModerationPenaltyType type; - ModerationPenalty({this.user, this.expiration, this.moderatedObject}); + ModerationPenalty({ + this.user, + this.expiration, + this.moderatedObject, + this.id, + this.type, + }); factory ModerationPenalty.fromJson(Map parsedJson) { return ModerationPenalty( + id: parsedJson['id'], user: parseUser( parsedJson['user'], ), + type: parseType( + parsedJson['type'], + ), moderatedObject: parseModeratedObject( parsedJson['moderated_object'], ), @@ -39,4 +54,35 @@ class ModerationPenalty { if (rawModeratedObject == null) return null; return ModeratedObject.fromJSON(rawModeratedObject); } + + static ModerationPenaltyType parseType(String moderationPenaltyTypeStr) { + if (moderationPenaltyTypeStr == null) return null; + + ModerationPenaltyType moderationPenaltyType; + if (moderationPenaltyTypeStr == + ModerationPenalty.moderationPenaltyTypeSuspension) { + moderationPenaltyType = ModerationPenaltyType.suspension; + } else { + // Don't throw as we might introduce new moderation penalties on the API which might not be yet in code + print('Unsupported moderation penalty type'); + } + + return moderationPenaltyType; + } + + static String convertModerationPenaltyTypeToHumanReadableString( + ModerationPenaltyType type, + {bool capitalize}) { + String result; + switch (type) { + case ModerationPenaltyType.suspension: + result = 'Account suspension'; + break; + default: + result = 'unknown'; + } + return capitalize ? toCapital(result) : result; + } } + +enum ModerationPenaltyType { suspension } diff --git a/lib/pages/home/pages/menu/menu.dart b/lib/pages/home/pages/menu/menu.dart index 186314445..99c8925fb 100644 --- a/lib/pages/home/pages/menu/menu.dart +++ b/lib/pages/home/pages/menu/menu.dart @@ -79,6 +79,14 @@ class OBMainMenuPage extends StatelessWidget { context: context); }, ), + ListTile( + leading: const OBIcon(OBIcons.moderationPenalties), + title: OBText('My moderation penalties'), + onTap: () { + navigationService.navigateToMyModerationPenaltiesPage( + context: context); + }, + ), ListTile( leading: const OBIcon(OBIcons.settings), title: OBText('Settings'), diff --git a/lib/pages/home/pages/menu/pages/my_moderation_penalties/my_moderation_penalties.dart b/lib/pages/home/pages/menu/pages/my_moderation_penalties/my_moderation_penalties.dart new file mode 100644 index 000000000..95368f1a9 --- /dev/null +++ b/lib/pages/home/pages/menu/pages/my_moderation_penalties/my_moderation_penalties.dart @@ -0,0 +1,86 @@ +import 'dart:async'; + +import 'package:Openbook/models/moderation/moderation_penalty_list.dart'; +import 'package:Openbook/models/moderation/moderation_penalty.dart'; +import 'package:Openbook/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/moderation_penalty.dart'; +import 'package:Openbook/widgets/http_list.dart'; +import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; +import 'package:Openbook/widgets/page_scaffold.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/services/user.dart'; +import 'package:Openbook/widgets/theming/primary_color_container.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class OBMyModerationPenaltiesPage extends StatefulWidget { + @override + State createState() { + return OBMyModerationPenaltiesPageState(); + } +} + +class OBMyModerationPenaltiesPageState + extends State { + UserService _userService; + + OBHttpListController _httpListController; + bool _needsBootstrap; + + @override + void initState() { + super.initState(); + _httpListController = OBHttpListController(); + _needsBootstrap = true; + } + + @override + Widget build(BuildContext context) { + if (_needsBootstrap) { + var provider = OpenbookProvider.of(context); + _userService = provider.userService; + _needsBootstrap = false; + } + + return OBCupertinoPageScaffold( + navigationBar: OBThemedNavigationBar( + title: 'Moderation penalties', + ), + child: OBPrimaryColorContainer( + child: OBHttpList( + padding: EdgeInsets.all(15), + controller: _httpListController, + listItemBuilder: _buildModerationPenaltyListItem, + listRefresher: _refreshModerationPenalties, + listOnScrollLoader: _loadMoreModerationPenalties, + resourceSingularName: 'moderation penalty', + resourcePluralName: 'moderation penalties', + ), + ), + ); + } + + Widget _buildModerationPenaltyListItem( + BuildContext context, ModerationPenalty moderationPenalty) { + return OBModerationPenaltyTile( + moderationPenalty: moderationPenalty, + ); + } + + Future> _refreshModerationPenalties() async { + ModerationPenaltiesList moderationPenalties = + await _userService.getModerationPenalties(); + return moderationPenalties.moderationPenalties; + } + + Future> _loadMoreModerationPenalties( + List moderationPenaltiesList) async { + var lastModerationPenalty = moderationPenaltiesList.last; + var lastModerationPenaltyId = lastModerationPenalty.id; + var moreModerationPenalties = (await _userService.getModerationPenalties( + maxId: lastModerationPenaltyId, + count: 10, + )) + .moderationPenalties; + return moreModerationPenalties; + } +} diff --git a/lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/moderation_penalty.dart b/lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/moderation_penalty.dart new file mode 100644 index 000000000..720ea7c02 --- /dev/null +++ b/lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/moderation_penalty.dart @@ -0,0 +1,80 @@ +import 'package:Openbook/models/moderation/moderation_penalty.dart'; +import 'package:Openbook/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/widgets/moderation_penalty_actions.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/pages/widgets/moderated_object_category/moderated_object_category.dart'; +import 'package:Openbook/pages/home/pages/moderated_objects/widgets/moderated_object/widgets/moderated_object_preview.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:Openbook/widgets/tile_group_title.dart'; +import 'package:Openbook/widgets/tiles/moderated_object_status_tile.dart'; +import 'package:flutter/material.dart'; + +class OBModerationPenaltyTile extends StatelessWidget { + final ModerationPenalty moderationPenalty; + + const OBModerationPenaltyTile({Key key, @required this.moderationPenalty}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBTileGroupTitle( + title: 'Object', + ), + OBModeratedObjectPreview( + moderatedObject: moderationPenalty.moderatedObject, + ), + const SizedBox( + height: 10, + ), + OBModeratedObjectCategory( + moderatedObject: moderationPenalty.moderatedObject, + isEditable: false, + ), + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + OBTileGroupTitle( + title: 'Status', + ), + OBModeratedObjectStatusTile( + moderatedObject: moderationPenalty.moderatedObject, + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + OBTileGroupTitle( + title: 'Type', + ), + ListTile( + title: OBText(ModerationPenalty + .convertModerationPenaltyTypeToHumanReadableString( + moderationPenalty.type, + capitalize: true)), + ) + ], + ), + ) + ], + ), + OBTileGroupTitle( + title: 'Expiration', + ), + ListTile( + title: OBText(moderationPenalty.expiration.toString()), + ), + OBModerationPenaltyActions( + moderationPenalty: moderationPenalty, + ) + ], + ); + } +} diff --git a/lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/widgets/moderation_penalty_actions.dart b/lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/widgets/moderation_penalty_actions.dart new file mode 100644 index 000000000..8e6345f03 --- /dev/null +++ b/lib/pages/home/pages/menu/pages/my_moderation_penalties/widgets/moderation_penalty/widgets/moderation_penalty_actions.dart @@ -0,0 +1,52 @@ +import 'package:Openbook/models/community.dart'; +import 'package:Openbook/models/moderation/moderated_object.dart'; +import 'package:Openbook/models/moderation/moderation_penalty.dart'; +import 'package:Openbook/provider.dart'; +import 'package:Openbook/widgets/buttons/button.dart'; +import 'package:Openbook/widgets/icon.dart'; +import 'package:Openbook/widgets/theming/text.dart'; +import 'package:flutter/material.dart'; + +class OBModerationPenaltyActions extends StatelessWidget { + final ModerationPenalty moderationPenalty; + + OBModerationPenaltyActions({@required this.moderationPenalty}); + + @override + Widget build(BuildContext context) { + List moderationPenaltyActions = [ + Expanded( + child: OBButton( + type: OBButtonType.highlight, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const OBIcon( + OBIcons.chat, + customSize: 20.0, + ), + const SizedBox( + width: 10.0, + ), + const OBText('Chat with the team'), + ], + ), + onPressed: () { + OpenbookProviderState openbookProvider = + OpenbookProvider.of(context); + openbookProvider.intercomService.displayMessenger(); + })), + ]; + + return Padding( + padding: EdgeInsets.only(left: 20.0, top: 10.0, right: 20.0), + child: Column( + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: moderationPenaltyActions, + ) + ], + )); + } +} diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index 1caf27e08..0548eb847 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -36,6 +36,7 @@ import 'package:Openbook/pages/home/pages/menu/pages/followers.dart'; import 'package:Openbook/pages/home/pages/menu/pages/following.dart'; import 'package:Openbook/pages/home/pages/menu/pages/follows_list/follows_list.dart'; import 'package:Openbook/pages/home/pages/menu/pages/follows_lists/follows_lists.dart'; +import 'package:Openbook/pages/home/pages/menu/pages/my_moderation_penalties/my_moderation_penalties.dart'; import 'package:Openbook/pages/home/pages/menu/pages/my_moderation_tasks/my_moderation_tasks.dart'; import 'package:Openbook/pages/home/pages/menu/pages/settings/pages/account_settings/account_settings.dart'; import 'package:Openbook/pages/home/pages/menu/pages/settings/pages/account_settings/pages/blocked_users.dart'; @@ -621,6 +622,15 @@ class NavigationService { widget: OBMyModerationTasksPage())); } + Future navigateToMyModerationPenaltiesPage( + {@required BuildContext context}) async { + return Navigator.push( + context, + OBSlideRightRoute( + key: Key('obMyModerationPenaltiesPage'), + widget: OBMyModerationPenaltiesPage())); + } + Future navigateToBlankPageWithWidget( {@required BuildContext context, @required String navBarTitle, diff --git a/lib/widgets/icon.dart b/lib/widgets/icon.dart index 07f2a6861..5617e1ba7 100644 --- a/lib/widgets/icon.dart +++ b/lib/widgets/icon.dart @@ -188,7 +188,7 @@ class OBIcons { static const deleteCommunity = OBIconData(nativeIcon: Icons.delete_forever); static const seeMore = OBIconData(nativeIcon: Icons.arrow_right); static const leaveCommunity = OBIconData(nativeIcon: Icons.exit_to_app); - static const reportCommunity = OBIconData(nativeIcon: Icons.report); + static const reportCommunity = OBIconData(nativeIcon: Icons.flag); static const communityInvites = OBIconData(nativeIcon: Icons.email); static const favoriteCommunity = OBIconData(nativeIcon: Icons.favorite); static const unfavoriteCommunity = @@ -221,6 +221,7 @@ class OBIcons { static const verify = OBIconData(nativeIcon: Icons.check); static const unverify = OBIconData(nativeIcon: Icons.close); static const globalModerator = OBIconData(nativeIcon: Icons.account_balance); + static const moderationPenalties = OBIconData(nativeIcon: Icons.flag); static const success = OBIconData(filename: 'success-icon.png'); static const error = OBIconData(filename: 'error-icon.png'); static const warning = OBIconData(filename: 'warning-icon.png'); From c6f5e5184ccec71ffdb126ca2a158df2455d00f0 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Tue, 4 Jun 2019 15:19:22 +0200 Subject: [PATCH 63/65] :recycle: load all moderated object statuses on global mod --- .../moderated_objects_filters.dart | 2 +- .../pages/moderated_objects/moderated_objects.dart | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart index 0af6af9ac..e97fa33c0 100644 --- a/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart +++ b/lib/pages/home/pages/moderated_objects/modals/moderated_objects_filters/moderated_objects_filters.dart @@ -193,7 +193,7 @@ class OBModeratedObjectsFiltersModalState Expanded( child: OBCheckboxField( titleStyle: TextStyle(fontWeight: FontWeight.normal), - title: 'Only verified', + title: 'Verified', value: _onlyVerified, onTap: () { setState(() { diff --git a/lib/pages/home/pages/moderated_objects/moderated_objects.dart b/lib/pages/home/pages/moderated_objects/moderated_objects.dart index 3b6389031..b97d3c05d 100644 --- a/lib/pages/home/pages/moderated_objects/moderated_objects.dart +++ b/lib/pages/home/pages/moderated_objects/moderated_objects.dart @@ -330,14 +330,19 @@ class OBModeratedObjectsFilters { ModeratedObjectType.postComment, ModeratedObjectType.post, ]; - if (isGlobalModeration) + List filterStatuses = [ + ModeratedObjectStatus.pending + ]; + + if (isGlobalModeration) { filterTypes .addAll([ModeratedObjectType.user, ModeratedObjectType.community]); + filterStatuses.addAll( + [ModeratedObjectStatus.approved, ModeratedObjectStatus.rejected]); + } return OBModeratedObjectsFilters( - statuses: [ModeratedObjectStatus.pending], - types: filterTypes, - onlyVerified: false); + statuses: filterStatuses, types: filterTypes, onlyVerified: false); } OBModeratedObjectsFilters( From 48acd63012fd3b4703d243d3aa6736c308eea57e Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Wed, 5 Jun 2019 14:17:05 +0200 Subject: [PATCH 64/65] :sparkles: show badge of pending moderation actions and penalties --- lib/models/user.dart | 24 +++ lib/pages/home/home.dart | 3 +- lib/pages/home/pages/menu/menu.dart | 286 ++++++++++++++++------------ 3 files changed, 190 insertions(+), 123 deletions(-) diff --git a/lib/models/user.dart b/lib/models/user.dart index 71695191d..732b0a94d 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -27,6 +27,8 @@ class User extends UpdatableModel { int unreadNotificationsCount; int postsCount; int inviteCount; + int pendingCommunitiesModeratedObjectsCount; + int activeModerationPenaltiesCount; bool areGuidelinesAccepted; bool isFollowing; bool isConnected; @@ -96,6 +98,8 @@ class User extends UpdatableModel { this.followLists, this.communitiesMemberships, this.communitiesInvites, + this.activeModerationPenaltiesCount, + this.pendingCommunitiesModeratedObjectsCount, this.areGuidelinesAccepted}); void updateFromJson(Map json) { @@ -121,6 +125,12 @@ class User extends UpdatableModel { } if (json.containsKey('followers_count')) followersCount = json['followers_count']; + if (json.containsKey('pending_communities_moderated_objects_count')) + pendingCommunitiesModeratedObjectsCount = + json['pending_communities_moderated_objects_count']; + if (json.containsKey('active_moderation_penalties_count')) + activeModerationPenaltiesCount = + json['active_moderation_penalties_count']; if (json.containsKey('following_count')) followingCount = json['following_count']; if (json.containsKey('unread_notifications_count')) @@ -303,6 +313,16 @@ class User extends UpdatableModel { } } + bool hasPendingCommunitiesModeratedObjects() { + return pendingCommunitiesModeratedObjectsCount != null && + pendingCommunitiesModeratedObjectsCount > 0; + } + + bool hasActiveModerationPenaltiesCount() { + return activeModerationPenaltiesCount != null && + activeModerationPenaltiesCount > 0; + } + void setIsReported(isReported) { this.isReported = isReported; notifyUpdate(); @@ -512,6 +532,10 @@ class UserFactory extends UpdatableModelFactory { postsCount: json['posts_count'], inviteCount: json['invite_count'], unreadNotificationsCount: json['unread_notifications_count'], + pendingCommunitiesModeratedObjectsCount: + json['pending_communities_moderated_objects_count'], + activeModerationPenaltiesCount: + json['active_moderation_penalties_count'], email: json['email'], username: json['username'], followingCount: json['following_count'], diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 131053d4a..5ccce390e 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -422,7 +422,8 @@ class OBHomePageState extends ReceiveShareState _pushNotificationSubscription = _pushNotificationsService.pushNotification .listen(_onPushNotification); - if (!newUser.areGuidelinesAccepted != null && !newUser.areGuidelinesAccepted) { + if (!newUser.areGuidelinesAccepted != null && + !newUser.areGuidelinesAccepted) { _modalService.openAcceptGuidelines(context: context); } } diff --git a/lib/pages/home/pages/menu/menu.dart b/lib/pages/home/pages/menu/menu.dart index 99c8925fb..e8d1ce9a8 100644 --- a/lib/pages/home/pages/menu/menu.dart +++ b/lib/pages/home/pages/menu/menu.dart @@ -1,5 +1,6 @@ import 'package:Openbook/models/user.dart'; import 'package:Openbook/pages/home/lib/poppable_page_controller.dart'; +import 'package:Openbook/widgets/badges/badge.dart'; import 'package:Openbook/widgets/icon.dart'; import 'package:Openbook/widgets/nav_bars/themed_nav_bar.dart'; import 'package:Openbook/provider.dart'; @@ -29,134 +30,175 @@ class OBMainMenuPage extends StatelessWidget { child: OBPrimaryColorContainer( child: Column( children: [ - Expanded( - child: ListView( - physics: const ClampingScrollPhysics(), - // Important: Remove any padding from the ListView. - padding: EdgeInsets.zero, - children: [ - ListTile( - leading: const OBIcon(OBIcons.circles), - title: const OBText('My circles'), - onTap: () { - navigationService.navigateToConnectionsCircles( - context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.lists), - title: const OBText('My lists'), - onTap: () { - navigationService.navigateToFollowsLists(context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.followers), - title: const OBText('My followers'), - onTap: () { - navigationService.navigateToFollowersPage(context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.following), - title: const OBText('My following'), - onTap: () { - navigationService.navigateToFollowingPage(context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.invite), - title: const OBText('My invites'), - onTap: () { - navigationService.navigateToUserInvites(context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.communityModerators), - title: OBText('My pending moderation tasks'), - onTap: () { - navigationService.navigateToMyModerationTasksPage( - context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.moderationPenalties), - title: OBText('My moderation penalties'), - onTap: () { - navigationService.navigateToMyModerationPenaltiesPage( - context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.settings), - title: OBText('Settings'), - onTap: () { - navigationService.navigateToSettingsPage(context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.themes), - title: OBText('Themes'), - onTap: () { - navigationService.navigateToThemesPage(context: context); - }, - ), - StreamBuilder( - stream: userService.loggedInUserChange, - initialData: userService.getLoggedInUser(), - builder: - (BuildContext context, AsyncSnapshot snapshot) { - User loggedInUser = snapshot.data; + StreamBuilder( + stream: userService.loggedInUserChange, + initialData: userService.getLoggedInUser(), + builder: (BuildContext context, + AsyncSnapshot loggedInUserSnapshot) { + User loggedInUser = loggedInUserSnapshot.data; - if (loggedInUser == null) return const SizedBox(); + if (loggedInUser == null) return const SizedBox(); - return ListTile( - leading: const OBIcon(OBIcons.help), - title: OBText(localizationService.trans('DRAWER.HELP')), - onTap: () async { - intercomService.displayMessenger(); - }, - ); - }, - ), - StreamBuilder( - stream: userService.loggedInUserChange, - initialData: userService.getLoggedInUser(), + return StreamBuilder( + stream: loggedInUserSnapshot.data.updateSubject, + initialData: loggedInUserSnapshot.data, builder: - (BuildContext context, AsyncSnapshot snapshot) { - User loggedInUser = snapshot.data; + (BuildContext context, AsyncSnapshot userSnapshot) { + User user = userSnapshot.data; - if (loggedInUser == null || - !(loggedInUser.isGlobalModerator ?? false)) - return const SizedBox(); + return Expanded( + child: ListView( + physics: const ClampingScrollPhysics(), + // Important: Remove any padding from the ListView. + padding: EdgeInsets.zero, + children: [ + ListTile( + leading: const OBIcon(OBIcons.circles), + title: const OBText('My circles'), + onTap: () { + navigationService.navigateToConnectionsCircles( + context: context); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.lists), + title: const OBText('My lists'), + onTap: () { + navigationService.navigateToFollowsLists( + context: context); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.followers), + title: const OBText('My followers'), + onTap: () { + navigationService.navigateToFollowersPage( + context: context); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.following), + title: const OBText('My following'), + onTap: () { + navigationService.navigateToFollowingPage( + context: context); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.invite), + title: const OBText('My invites'), + onTap: () { + navigationService.navigateToUserInvites( + context: context); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.communityModerators), + title: OBText('My pending moderation tasks'), + onTap: () async { + await navigationService + .navigateToMyModerationTasksPage( + context: context); + userService.refreshUser(); + }, + trailing: OBBadge( + size: 25, + count: user.pendingCommunitiesModeratedObjectsCount, + ), + ), + ListTile( + leading: const OBIcon(OBIcons.moderationPenalties), + title: OBText('My moderation penalties'), + onTap: () async { + await navigationService + .navigateToMyModerationPenaltiesPage( + context: context); + userService.refreshUser(); + }, + trailing: OBBadge( + size: 25, + count: user.activeModerationPenaltiesCount, + ), + ), + ListTile( + leading: const OBIcon(OBIcons.settings), + title: OBText('Settings'), + onTap: () { + navigationService.navigateToSettingsPage( + context: context); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.themes), + title: OBText('Themes'), + onTap: () { + navigationService.navigateToThemesPage( + context: context); + }, + ), + StreamBuilder( + stream: userService.loggedInUserChange, + initialData: userService.getLoggedInUser(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + User loggedInUser = snapshot.data; - return ListTile( - leading: const OBIcon(OBIcons.globalModerator), - title: OBText('Global moderation'), - onTap: () async { - navigationService.navigateToGlobalModeratedObjects( - context: context); - }, - ); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.link), - title: OBText('Useful links'), - onTap: () { - navigationService.navigateToUsefulLinksPage( - context: context); - }, - ), - ListTile( - leading: const OBIcon(OBIcons.logout), - title: OBText(localizationService.trans('DRAWER.LOGOUT')), - onTap: () { - userService.logout(); + if (loggedInUser == null) return const SizedBox(); + + return ListTile( + leading: const OBIcon(OBIcons.help), + title: OBText( + localizationService.trans('DRAWER.HELP')), + onTap: () async { + intercomService.displayMessenger(); + }, + ); + }, + ), + StreamBuilder( + stream: userService.loggedInUserChange, + initialData: userService.getLoggedInUser(), + builder: (BuildContext context, + AsyncSnapshot snapshot) { + User loggedInUser = snapshot.data; + + if (loggedInUser == null || + !(loggedInUser.isGlobalModerator ?? false)) + return const SizedBox(); + + return ListTile( + leading: const OBIcon(OBIcons.globalModerator), + title: OBText('Global moderation'), + onTap: () async { + navigationService + .navigateToGlobalModeratedObjects( + context: context); + }, + ); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.link), + title: OBText('Useful links'), + onTap: () { + navigationService.navigateToUsefulLinksPage( + context: context); + }, + ), + ListTile( + leading: const OBIcon(OBIcons.logout), + title: OBText( + localizationService.trans('DRAWER.LOGOUT')), + onTap: () { + userService.logout(); + }, + ) + ], + )); }, - ) - ], - )) + ); + }, + ), ], ), ), From 9de26940d9d8ad39d21398b4c3a7b65529274a68 Mon Sep 17 00:00:00 2001 From: Joel Hernandez Date: Wed, 5 Jun 2019 16:08:47 +0200 Subject: [PATCH 65/65] :sparkles: change image picker to multi_image_picker --- ...ficationServiceExtension-Bridging-Header.h | 4 + ios/Podfile | 4 +- ios/Podfile.lock | 34 +- ios/Runner-Bridging-Header.h | 4 + ios/Runner.xcodeproj/project.pbxproj | 385 ++++++++++++------ .../home/bottom_sheets/photo_picker.dart | 69 ---- .../home/modals/create_post/create_post.dart | 11 +- .../edit_user_profile/edit_user_profile.dart | 28 +- lib/pages/home/modals/save_community.dart | 12 +- lib/services/bottom_sheet.dart | 14 - lib/services/image_picker.dart | 46 ++- .../push_notifications.dart | 2 +- pubspec.lock | 29 +- pubspec.yaml | 7 +- 14 files changed, 377 insertions(+), 272 deletions(-) create mode 100644 ios/OneSignalNotificationServiceExtension-Bridging-Header.h create mode 100644 ios/Runner-Bridging-Header.h delete mode 100644 lib/pages/home/bottom_sheets/photo_picker.dart diff --git a/ios/OneSignalNotificationServiceExtension-Bridging-Header.h b/ios/OneSignalNotificationServiceExtension-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/ios/OneSignalNotificationServiceExtension-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/ios/Podfile b/ios/Podfile index 90e4a17b9..afc86d6fc 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -27,6 +27,7 @@ def parse_KV_file(file, separator='=') end target 'Runner' do + use_frameworks! # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') @@ -55,6 +56,7 @@ target 'Runner' do end target 'OneSignalNotificationServiceExtension' do + use_frameworks! pod 'OneSignal', '>= 2.9.5', '< 3.0' end @@ -62,7 +64,7 @@ post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' - config.build_settings['SWIFT_VERSION'] = '3.2' # <--- add this + config.build_settings['SWIFT_VERSION'] = '5' # <--- add this end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b489dc416..122e1fa26 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,9 @@ PODS: + - BSGridCollectionViewLayout (1.2.3) + - BSImagePicker (2.10.0): + - BSGridCollectionViewLayout (= 1.2.3) + - BSImageView (= 1.0.3) + - BSImageView (1.0.3) - device_info (0.0.1): - Flutter - Flutter (1.0.0) @@ -18,10 +23,13 @@ PODS: - intercom_flutter (1.0.7): - Flutter - Intercom - - onesignal (1.0.5): + - multi_image_picker (4.3.3): + - BSImagePicker (~> 2.10.0) + - Flutter + - OneSignal (2.10.0) + - onesignalflutter (1.0.5): - Flutter - OneSignal (< 3.0, >= 2.9.5) - - OneSignal (2.9.5) - path_provider (0.0.1): - Flutter - share (0.5.2): @@ -47,8 +55,9 @@ DEPENDENCIES: - image_cropper (from `.symlinks/plugins/image_cropper/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - intercom_flutter (from `.symlinks/plugins/intercom_flutter/ios`) + - multi_image_picker (from `.symlinks/plugins/multi_image_picker/ios`) - OneSignal (< 3.0, >= 2.9.5) - - onesignal (from `.symlinks/plugins/onesignal/ios`) + - onesignalflutter (from `.symlinks/plugins/onesignalflutter/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - share (from `.symlinks/plugins/share/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) @@ -59,6 +68,9 @@ DEPENDENCIES: SPEC REPOS: https://github.com/cocoapods/specs.git: + - BSGridCollectionViewLayout + - BSImagePicker + - BSImageView - FMDB - Intercom - OneSignal @@ -79,8 +91,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_picker/ios" intercom_flutter: :path: ".symlinks/plugins/intercom_flutter/ios" - onesignal: - :path: ".symlinks/plugins/onesignal/ios" + multi_image_picker: + :path: ".symlinks/plugins/multi_image_picker/ios" + onesignalflutter: + :path: ".symlinks/plugins/onesignalflutter/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" share: @@ -97,6 +111,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/video_player/ios" SPEC CHECKSUMS: + BSGridCollectionViewLayout: 568273e113fbc815f868f1898ef0465aee68f955 + BSImagePicker: fa0c15b6740e8aa7a7b7f0fe38a71ad4cfa0ec4a + BSImageView: a149459433a2687157d034c78e059d30ac7f2544 device_info: 76ce0b32e13034d1883be4a382433648f9dcee63 Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 flutter_exif_rotation: 458778023267a1f0157ae8d9483474749990ce24 @@ -106,8 +123,9 @@ SPEC CHECKSUMS: image_picker: ee00aab0487cedc80a304085219503cc6d0f2e22 Intercom: 53f07c24f0ca18c1e910750e79b75f9c9867338f intercom_flutter: e281b619d2fc03dccf171089c87c01a17e1ed8b1 - OneSignal: ccdeb961882f8668305e5b694e2cb7cb325fc907 - onesignal: c2122c20ffcb03d65445f3e0b49273c10f9c37a6 + multi_image_picker: 83e69aae993ddffd49db61a1d970e2916e554103 + OneSignal: 0f5ff711d9f25da54885e4ab06ef0abc221a46ef + onesignalflutter: 872db11e9c18bcc9225e06c1cc7280872813783a path_provider: 09407919825bfe3c2deae39453b7a5b44f467873 share: 222b5dcc8031238af9d7de91149df65bad1aef75 shared_preferences: 5a1d487c427ee18fcd3ea1f2a131569481834b53 @@ -117,6 +135,6 @@ SPEC CHECKSUMS: url_launcher: 92b89c1029a0373879933c21642958c874539095 video_player: 906796a841943c8d370ac7c13b18039aa9b56498 -PODFILE CHECKSUM: 6f8026df7eb2040a45b4a2313fc73d43e59d6bd6 +PODFILE CHECKSUM: d3e4257a4755deb71f0bcbcc7c1e0639177bc4ed COCOAPODS: 1.5.3 diff --git a/ios/Runner-Bridging-Header.h b/ios/Runner-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/ios/Runner-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 81cf5eb61..67300bdb5 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -8,29 +8,11 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 26B90459017411DF809A7507 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 970D6175355421F353EA64C9 /* libPods-Runner.a */; }; + 30C0AB83114DC50252134799 /* Pods_OneSignalNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE2F8A8BC5DC89A5ADEF49D1 /* Pods_OneSignalNotificationServiceExtension.framework */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 542BD8834B6D6BE2F3E671A0 /* libPods-OneSignalNotificationServiceExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A4BA49A905C89E90E88FD37 /* libPods-OneSignalNotificationServiceExtension.a */; }; - 8902A1072236D5BF005F914D /* libFMDB.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8928E31E22356450001DB32A /* libFMDB.a */; }; - 8902A1082236D5BF005F914D /* libintercom_flutter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8902A1062236D5BF005F914D /* libintercom_flutter.a */; }; - 8902A1092236D5BF005F914D /* libsqflite.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8928E32022356450001DB32A /* libsqflite.a */; }; - 8925094F222F0E0900455D87 /* libdevice_info.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8925094A222F0E0900455D87 /* libdevice_info.a */; }; - 898053B722047AD000E47AD9 /* libflutter_secure_storage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8980539E22047AAF00E47AD9 /* libflutter_secure_storage.a */; }; - 898053B822047AD000E47AD9 /* libimage_cropper.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053A022047AAF00E47AD9 /* libimage_cropper.a */; }; - 898053B922047AD000E47AD9 /* libimage_picker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053A222047AAF00E47AD9 /* libimage_picker.a */; }; - 898053BA22047AD000E47AD9 /* libonesignal.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053A422047AAF00E47AD9 /* libonesignal.a */; }; - 898053BB22047AD000E47AD9 /* libpath_provider.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053A622047AAF00E47AD9 /* libpath_provider.a */; }; - 898053BC22047AD000E47AD9 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053AA22047AAF00E47AD9 /* libPods-Runner.a */; }; - 898053BE22047AD000E47AD9 /* libTOCropViewController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053AE22047AAF00E47AD9 /* libTOCropViewController.a */; }; - 898053BF22047AD000E47AD9 /* libuni_links.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053B222047AAF00E47AD9 /* libuni_links.a */; }; - 898053C022047AD000E47AD9 /* liburl_launcher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053B422047AAF00E47AD9 /* liburl_launcher.a */; }; - 898053C122047AD000E47AD9 /* libvideo_player.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053B622047AAF00E47AD9 /* libvideo_player.a */; }; - 898053C322047AF100E47AD9 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053C222047AF100E47AD9 /* UserNotifications.framework */; }; - 898053C522047AF800E47AD9 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053C422047AF700E47AD9 /* SystemConfiguration.framework */; }; - 898053C622047B3B00E47AD9 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 898053C722047B4200E47AD9 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 89A543B022A7FFB500A4C0BB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 898053AA22047AAF00E47AD9 /* Pods_Runner.framework */; }; 89ABAE522203425900049DFB /* NotificationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 89ABAE512203425900049DFB /* NotificationService.m */; }; 89ABAE562203425900049DFB /* OneSignalNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 89ABAE4E2203425900049DFB /* OneSignalNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; @@ -40,6 +22,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + D9A32CBE0439E00B856D454C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BEBB57E8B03FDA9D0A698867 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -92,13 +75,6 @@ remoteGlobalIDString = D6D2B1288A9474AE589E5F0AED80524B; remoteInfo = image_picker; }; - 898053A322047AAF00E47AD9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = BEB67C58BB1B431F81FB28DF8658A597; - remoteInfo = onesignal; - }; 898053A522047AAF00E47AD9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; @@ -155,6 +131,13 @@ remoteGlobalIDString = F223BD1D37BD4412AC0C0C61C7615399; remoteInfo = video_player; }; + 8986A1FD22A7F7E7009824AF /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B71D6799C47D234A25EDBDA75F0C660D; + remoteInfo = onesignalflutter; + }; 89ABAE542203425900049DFB /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 97C146E61CF9000F007C117D /* Project object */; @@ -162,6 +145,41 @@ remoteGlobalIDString = 89ABAE4D2203425900049DFB; remoteInfo = OneSignalNotificationServiceExtension; }; + 89B81DB322A7F41F00B3E9E0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = E17ED85F3FB0A1819C818427E1B96D7D; + remoteInfo = BSGridCollectionViewLayout; + }; + 89B81DB522A7F41F00B3E9E0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = F0A7A725902A8FF9A7D6AB717B2F7501; + remoteInfo = BSImagePicker; + }; + 89B81DB722A7F41F00B3E9E0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 95ED3BFAFDF702EF9348BDAB4EE5507C; + remoteInfo = "BSImagePicker-BSImagePicker"; + }; + 89B81DB922A7F41F00B3E9E0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 03FE8D1D31B74F15F3AEC7ACEEE84D4F; + remoteInfo = BSImageView; + }; + 89B81DBB22A7F41F00B3E9E0 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = BFBFB5926D8AB35D39D19DC735F22517; + remoteInfo = multi_image_picker; + }; 89CF474322419BFD001BC50D /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 8980538D22047AAE00E47AD9 /* Pods.xcodeproj */; @@ -220,7 +238,6 @@ 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 4130CBCE97AB39E4070E45EF /* Pods-OneSignalNotificationServiceExtension.debug-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.debug-development.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.debug-development.xcconfig"; sourceTree = ""; }; 4E993998490EDCE06AC2B3F0 /* Pods-OneSignalNotificationServiceExtension.debug-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.debug-production.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.debug-production.xcconfig"; sourceTree = ""; }; - 7A4BA49A905C89E90E88FD37 /* libPods-OneSignalNotificationServiceExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OneSignalNotificationServiceExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -236,8 +253,9 @@ 89ABAE502203425900049DFB /* NotificationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationService.h; sourceTree = ""; }; 89ABAE512203425900049DFB /* NotificationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationService.m; sourceTree = ""; }; 89ABAE532203425900049DFB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 89B81DC622A7F4EA00B3E9E0 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 89B81DC722A7F4EA00B3E9E0 /* OneSignalNotificationServiceExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OneSignalNotificationServiceExtension-Bridging-Header.h"; sourceTree = ""; }; 89DBC2DD2203430700F80685 /* OneSignalNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = OneSignalNotificationServiceExtension.entitlements; sourceTree = ""; }; - 970D6175355421F353EA64C9 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; @@ -250,7 +268,9 @@ A6C34D3821BE816E00882F1E /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; AAF4B8238CF43C30122544EF /* Pods-OneSignalNotificationServiceExtension.release-production.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.release-production.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.release-production.xcconfig"; sourceTree = ""; }; B23196F4E53E7786037AC8B5 /* Pods-OneSignalNotificationServiceExtension.release-development.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.release-development.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.release-development.xcconfig"; sourceTree = ""; }; + BEBB57E8B03FDA9D0A698867 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CBD573B1F3D19BD6C5F5624A /* Pods-OneSignalNotificationServiceExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.release.xcconfig"; sourceTree = ""; }; + CE2F8A8BC5DC89A5ADEF49D1 /* Pods_OneSignalNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_OneSignalNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E6C0DC9AEB28225FDDAABF70 /* Pods-OneSignalNotificationServiceExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.profile.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.profile.xcconfig"; sourceTree = ""; }; E73A042BEC81027B2EBB8967 /* Pods-OneSignalNotificationServiceExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OneSignalNotificationServiceExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OneSignalNotificationServiceExtension/Pods-OneSignalNotificationServiceExtension.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -260,25 +280,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8902A1072236D5BF005F914D /* libFMDB.a in Frameworks */, - 8902A1082236D5BF005F914D /* libintercom_flutter.a in Frameworks */, - 8902A1092236D5BF005F914D /* libsqflite.a in Frameworks */, - 8925094F222F0E0900455D87 /* libdevice_info.a in Frameworks */, - 898053C722047B4200E47AD9 /* App.framework in Frameworks */, - 898053C622047B3B00E47AD9 /* Flutter.framework in Frameworks */, - 898053C522047AF800E47AD9 /* SystemConfiguration.framework in Frameworks */, - 898053C322047AF100E47AD9 /* UserNotifications.framework in Frameworks */, - 898053B722047AD000E47AD9 /* libflutter_secure_storage.a in Frameworks */, - 898053B822047AD000E47AD9 /* libimage_cropper.a in Frameworks */, - 898053B922047AD000E47AD9 /* libimage_picker.a in Frameworks */, - 898053BA22047AD000E47AD9 /* libonesignal.a in Frameworks */, - 898053BB22047AD000E47AD9 /* libpath_provider.a in Frameworks */, - 898053BC22047AD000E47AD9 /* libPods-Runner.a in Frameworks */, - 898053BE22047AD000E47AD9 /* libTOCropViewController.a in Frameworks */, - 898053BF22047AD000E47AD9 /* libuni_links.a in Frameworks */, - 898053C022047AD000E47AD9 /* liburl_launcher.a in Frameworks */, - 898053C122047AD000E47AD9 /* libvideo_player.a in Frameworks */, - 542BD8834B6D6BE2F3E671A0 /* libPods-OneSignalNotificationServiceExtension.a in Frameworks */, + 89A543B022A7FFB500A4C0BB /* Pods_Runner.framework in Frameworks */, + 30C0AB83114DC50252134799 /* Pods_OneSignalNotificationServiceExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -288,7 +291,7 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, - 26B90459017411DF809A7507 /* libPods-Runner.a in Frameworks */, + D9A32CBE0439E00B856D454C /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -320,8 +323,8 @@ 8980538622047A2300E47AD9 /* Runner */, 89805384220479C200E47AD9 /* video_player */, 898053822204791900E47AD9 /* VideoPlayerPlugin.h */, - 7A4BA49A905C89E90E88FD37 /* libPods-OneSignalNotificationServiceExtension.a */, - 970D6175355421F353EA64C9 /* libPods-Runner.a */, + CE2F8A8BC5DC89A5ADEF49D1 /* Pods_OneSignalNotificationServiceExtension.framework */, + BEBB57E8B03FDA9D0A698867 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -336,25 +339,30 @@ 8980538E22047AAE00E47AD9 /* Products */ = { isa = PBXGroup; children = ( - 8925094A222F0E0900455D87 /* libdevice_info.a */, - 89CF474422419BFD001BC50D /* libflutter_exif_rotation.a */, - 8980539E22047AAF00E47AD9 /* libflutter_secure_storage.a */, - 8928E31E22356450001DB32A /* libFMDB.a */, - 898053A022047AAF00E47AD9 /* libimage_cropper.a */, - 898053A222047AAF00E47AD9 /* libimage_picker.a */, - 8902A1062236D5BF005F914D /* libintercom_flutter.a */, - 898053A422047AAF00E47AD9 /* libonesignal.a */, - 898053A622047AAF00E47AD9 /* libpath_provider.a */, - 898053A822047AAF00E47AD9 /* libPods-OneSignalNotificationServiceExtension.a */, - 898053AA22047AAF00E47AD9 /* libPods-Runner.a */, - 89EE3231227B56510094ACB0 /* libshare.a */, - 89EE3233227B56510094ACB0 /* libshared_preferences.a */, - 8928E32022356450001DB32A /* libsqflite.a */, - 898053AE22047AAF00E47AD9 /* libTOCropViewController.a */, + 89B81DB422A7F41F00B3E9E0 /* BSGridCollectionViewLayout.framework */, + 89B81DB622A7F41F00B3E9E0 /* BSImagePicker.framework */, + 89B81DB822A7F41F00B3E9E0 /* BSImagePicker.bundle */, + 89B81DBA22A7F41F00B3E9E0 /* BSImageView.framework */, + 8925094A222F0E0900455D87 /* device_info.framework */, + 89CF474422419BFD001BC50D /* flutter_exif_rotation.framework */, + 8980539E22047AAF00E47AD9 /* flutter_secure_storage.framework */, + 8928E31E22356450001DB32A /* FMDB.framework */, + 898053A022047AAF00E47AD9 /* image_cropper.framework */, + 898053A222047AAF00E47AD9 /* image_picker.framework */, + 8902A1062236D5BF005F914D /* intercom_flutter.framework */, + 89B81DBC22A7F41F00B3E9E0 /* multi_image_picker.framework */, + 8986A1FE22A7F7E7009824AF /* onesignalflutter.framework */, + 898053A622047AAF00E47AD9 /* path_provider.framework */, + 898053A822047AAF00E47AD9 /* Pods_OneSignalNotificationServiceExtension.framework */, + 898053AA22047AAF00E47AD9 /* Pods_Runner.framework */, + 89EE3231227B56510094ACB0 /* share.framework */, + 89EE3233227B56510094ACB0 /* shared_preferences.framework */, + 8928E32022356450001DB32A /* sqflite.framework */, + 898053AE22047AAF00E47AD9 /* TOCropViewController.framework */, 898053B022047AAF00E47AD9 /* TOCropViewControllerBundle.bundle */, - 898053B222047AAF00E47AD9 /* libuni_links.a */, - 898053B422047AAF00E47AD9 /* liburl_launcher.a */, - 898053B622047AAF00E47AD9 /* libvideo_player.a */, + 898053B222047AAF00E47AD9 /* uni_links.framework */, + 898053B422047AAF00E47AD9 /* url_launcher.framework */, + 898053B622047AAF00E47AD9 /* video_player.framework */, ); name = Products; sourceTree = ""; @@ -392,6 +400,8 @@ 97C146EF1CF9000F007C117D /* Products */, 0532A0D3A2BF06AB149735E4 /* Pods */, 5ABC6138995F2182C962F35D /* Frameworks */, + 89B81DC622A7F4EA00B3E9E0 /* Runner-Bridging-Header.h */, + 89B81DC722A7F4EA00B3E9E0 /* OneSignalNotificationServiceExtension-Bridging-Header.h */, ); sourceTree = ""; }; @@ -490,6 +500,7 @@ 89ABAE4D2203425900049DFB = { CreatedOnToolsVersion = 10.1; DevelopmentTeam = GAR7B57RXU; + LastSwiftMigration = 1020; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -500,6 +511,7 @@ 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = GAR7B57RXU; + LastSwiftMigration = 1020; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { enabled = 1; @@ -548,87 +560,80 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - 8902A1062236D5BF005F914D /* libintercom_flutter.a */ = { + 8902A1062236D5BF005F914D /* intercom_flutter.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libintercom_flutter.a; + fileType = wrapper.framework; + path = intercom_flutter.framework; remoteRef = 8902A1052236D5BF005F914D /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 8925094A222F0E0900455D87 /* libdevice_info.a */ = { + 8925094A222F0E0900455D87 /* device_info.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libdevice_info.a; + fileType = wrapper.framework; + path = device_info.framework; remoteRef = 89250949222F0E0900455D87 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 8928E31E22356450001DB32A /* libFMDB.a */ = { + 8928E31E22356450001DB32A /* FMDB.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libFMDB.a; + fileType = wrapper.framework; + path = FMDB.framework; remoteRef = 8928E31D22356450001DB32A /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 8928E32022356450001DB32A /* libsqflite.a */ = { + 8928E32022356450001DB32A /* sqflite.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libsqflite.a; + fileType = wrapper.framework; + path = sqflite.framework; remoteRef = 8928E31F22356450001DB32A /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 8980539E22047AAF00E47AD9 /* libflutter_secure_storage.a */ = { + 8980539E22047AAF00E47AD9 /* flutter_secure_storage.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libflutter_secure_storage.a; + fileType = wrapper.framework; + path = flutter_secure_storage.framework; remoteRef = 8980539D22047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053A022047AAF00E47AD9 /* libimage_cropper.a */ = { + 898053A022047AAF00E47AD9 /* image_cropper.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libimage_cropper.a; + fileType = wrapper.framework; + path = image_cropper.framework; remoteRef = 8980539F22047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053A222047AAF00E47AD9 /* libimage_picker.a */ = { + 898053A222047AAF00E47AD9 /* image_picker.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libimage_picker.a; + fileType = wrapper.framework; + path = image_picker.framework; remoteRef = 898053A122047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053A422047AAF00E47AD9 /* libonesignal.a */ = { + 898053A622047AAF00E47AD9 /* path_provider.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libonesignal.a; - remoteRef = 898053A322047AAF00E47AD9 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 898053A622047AAF00E47AD9 /* libpath_provider.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libpath_provider.a; + fileType = wrapper.framework; + path = path_provider.framework; remoteRef = 898053A522047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053A822047AAF00E47AD9 /* libPods-OneSignalNotificationServiceExtension.a */ = { + 898053A822047AAF00E47AD9 /* Pods_OneSignalNotificationServiceExtension.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libPods-OneSignalNotificationServiceExtension.a"; + fileType = wrapper.framework; + path = Pods_OneSignalNotificationServiceExtension.framework; remoteRef = 898053A722047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053AA22047AAF00E47AD9 /* libPods-Runner.a */ = { + 898053AA22047AAF00E47AD9 /* Pods_Runner.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = "libPods-Runner.a"; + fileType = wrapper.framework; + path = Pods_Runner.framework; remoteRef = 898053A922047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053AE22047AAF00E47AD9 /* libTOCropViewController.a */ = { + 898053AE22047AAF00E47AD9 /* TOCropViewController.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libTOCropViewController.a; + fileType = wrapper.framework; + path = TOCropViewController.framework; remoteRef = 898053AD22047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -639,45 +644,87 @@ remoteRef = 898053AF22047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053B222047AAF00E47AD9 /* libuni_links.a */ = { + 898053B222047AAF00E47AD9 /* uni_links.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libuni_links.a; + fileType = wrapper.framework; + path = uni_links.framework; remoteRef = 898053B122047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053B422047AAF00E47AD9 /* liburl_launcher.a */ = { + 898053B422047AAF00E47AD9 /* url_launcher.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = liburl_launcher.a; + fileType = wrapper.framework; + path = url_launcher.framework; remoteRef = 898053B322047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 898053B622047AAF00E47AD9 /* libvideo_player.a */ = { + 898053B622047AAF00E47AD9 /* video_player.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libvideo_player.a; + fileType = wrapper.framework; + path = video_player.framework; remoteRef = 898053B522047AAF00E47AD9 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 89CF474422419BFD001BC50D /* libflutter_exif_rotation.a */ = { + 8986A1FE22A7F7E7009824AF /* onesignalflutter.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = onesignalflutter.framework; + remoteRef = 8986A1FD22A7F7E7009824AF /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 89B81DB422A7F41F00B3E9E0 /* BSGridCollectionViewLayout.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = BSGridCollectionViewLayout.framework; + remoteRef = 89B81DB322A7F41F00B3E9E0 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 89B81DB622A7F41F00B3E9E0 /* BSImagePicker.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = BSImagePicker.framework; + remoteRef = 89B81DB522A7F41F00B3E9E0 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 89B81DB822A7F41F00B3E9E0 /* BSImagePicker.bundle */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = BSImagePicker.bundle; + remoteRef = 89B81DB722A7F41F00B3E9E0 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 89B81DBA22A7F41F00B3E9E0 /* BSImageView.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = BSImageView.framework; + remoteRef = 89B81DB922A7F41F00B3E9E0 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 89B81DBC22A7F41F00B3E9E0 /* multi_image_picker.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = multi_image_picker.framework; + remoteRef = 89B81DBB22A7F41F00B3E9E0 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 89CF474422419BFD001BC50D /* flutter_exif_rotation.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libflutter_exif_rotation.a; + fileType = wrapper.framework; + path = flutter_exif_rotation.framework; remoteRef = 89CF474322419BFD001BC50D /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 89EE3231227B56510094ACB0 /* libshare.a */ = { + 89EE3231227B56510094ACB0 /* share.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libshare.a; + fileType = wrapper.framework; + path = share.framework; remoteRef = 89EE3230227B56510094ACB0 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 89EE3233227B56510094ACB0 /* libshared_preferences.a */ = { + 89EE3233227B56510094ACB0 /* shared_preferences.framework */ = { isa = PBXReferenceProxy; - fileType = archive.ar; - path = libshared_preferences.a; + fileType = wrapper.framework; + path = shared_preferences.framework; remoteRef = 89EE3232227B56510094ACB0 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -714,13 +761,11 @@ "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_ROOT}/Intercom/Intercom/Intercom.framework/Versions/A/Resources/Intercom.bundle", "${PODS_ROOT}/Intercom/Intercom/Intercom.framework/Versions/A/Resources/IntercomTranslations.bundle", - "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Intercom.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/IntercomTranslations.bundle", - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TOCropViewControllerBundle.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -734,11 +779,45 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/BSGridCollectionViewLayout/BSGridCollectionViewLayout.framework", + "${BUILT_PRODUCTS_DIR}/BSImagePicker/BSImagePicker.framework", + "${BUILT_PRODUCTS_DIR}/BSImageView/BSImageView.framework", + "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", + "${BUILT_PRODUCTS_DIR}/TOCropViewController/TOCropViewController.framework", + "${BUILT_PRODUCTS_DIR}/device_info/device_info.framework", + "${BUILT_PRODUCTS_DIR}/flutter_exif_rotation/flutter_exif_rotation.framework", + "${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework", + "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", + "${BUILT_PRODUCTS_DIR}/multi_image_picker/multi_image_picker.framework", + "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", + "${BUILT_PRODUCTS_DIR}/share/share.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences/shared_preferences.framework", + "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", + "${BUILT_PRODUCTS_DIR}/uni_links/uni_links.framework", + "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", + "${BUILT_PRODUCTS_DIR}/video_player/video_player.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSGridCollectionViewLayout.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSImagePicker.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSImageView.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/TOCropViewController.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_exif_rotation.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/multi_image_picker.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/uni_links.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -915,6 +994,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -933,6 +1013,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; @@ -999,6 +1081,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -1017,6 +1100,9 @@ ); PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1077,6 +1163,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -1095,6 +1182,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1161,6 +1250,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -1179,6 +1269,9 @@ ); PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1239,6 +1332,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -1257,6 +1351,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1268,6 +1364,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -1289,6 +1386,9 @@ PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "OneSignalNotificationServiceExtension-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -1299,6 +1399,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -1320,6 +1421,9 @@ PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "OneSignalNotificationServiceExtension-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Debug-production"; @@ -1330,6 +1434,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -1351,6 +1456,9 @@ PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "OneSignalNotificationServiceExtension-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Debug-development"; @@ -1361,6 +1469,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -1381,6 +1490,8 @@ PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "OneSignalNotificationServiceExtension-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -1391,6 +1502,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -1411,6 +1523,8 @@ PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "OneSignalNotificationServiceExtension-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Profile; @@ -1421,6 +1535,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -1441,6 +1556,8 @@ PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "OneSignalNotificationServiceExtension-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Release-production"; @@ -1451,6 +1568,7 @@ buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; @@ -1471,6 +1589,8 @@ PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app.OneSignalNotificationServiceExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "OneSignalNotificationServiceExtension-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = "1,2"; }; name = "Release-development"; @@ -1586,6 +1706,7 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -1604,6 +1725,9 @@ ); PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; @@ -1614,6 +1738,7 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; @@ -1632,6 +1757,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = social.openbook.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner-Bridging-Header.h"; + SWIFT_VERSION = 5; TARGETED_DEVICE_FAMILY = 1; VERSIONING_SYSTEM = "apple-generic"; }; diff --git a/lib/pages/home/bottom_sheets/photo_picker.dart b/lib/pages/home/bottom_sheets/photo_picker.dart deleted file mode 100644 index 85fc24ca5..000000000 --- a/lib/pages/home/bottom_sheets/photo_picker.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'dart:io'; - -import 'package:Openbook/provider.dart'; -import 'package:Openbook/services/image_picker.dart'; -import 'package:Openbook/services/toast.dart'; -import 'package:Openbook/widgets/icon.dart'; -import 'package:Openbook/widgets/theming/primary_color_container.dart'; -import 'package:Openbook/widgets/theming/text.dart'; -import 'package:flutter/material.dart'; - -class OBPhotoPickerBottomSheet extends StatelessWidget { - final OBImageType imageType; - - const OBPhotoPickerBottomSheet({Key key, this.imageType = OBImageType.post}) - : super(key: key); - - @override - Widget build(BuildContext context) { - ImagePickerService imagePickerService = - OpenbookProvider.of(context).imagePickerService; - ToastService toastService = OpenbookProvider.of(context).toastService; - - List photoPickerActions = [ - ListTile( - leading: const OBIcon(OBIcons.gallery), - title: const OBText( - 'From gallery', - ), - onTap: () async { - try { - File image = await imagePickerService.pickImage( - imageType: imageType, source: ImageSource.gallery); - Navigator.pop(context, image); - } on ImageTooLargeException catch (e) { - int limit = e.getLimitInMB(); - toastService.error(message: 'Image too large (limit: $limit MB)', context: context); - } - }, - ), - ListTile( - leading: const OBIcon(OBIcons.camera), - title: const OBText( - 'From camera', - ), - onTap: () async { - try { - File image = await imagePickerService.pickImage( - imageType: imageType, source: ImageSource.camera); - Navigator.pop(context, image); - } on ImageTooLargeException catch (e) { - int limit = e.getLimitInMB(); - toastService.error(message: 'Image too large (limit: $limit MB)', context: context); - } - }, - ) - ]; - - return OBPrimaryColorContainer( - mainAxisSize: MainAxisSize.min, - child: Padding( - padding: EdgeInsets.only(bottom: 16), - child: Column( - children: photoPickerActions, - mainAxisSize: MainAxisSize.min, - ), - ) - ); - } -} diff --git a/lib/pages/home/modals/create_post/create_post.dart b/lib/pages/home/modals/create_post/create_post.dart index da21b8548..11e8d7796 100644 --- a/lib/pages/home/modals/create_post/create_post.dart +++ b/lib/pages/home/modals/create_post/create_post.dart @@ -10,6 +10,7 @@ import 'package:Openbook/pages/home/modals/create_post/widgets/remaining_post_ch import 'package:Openbook/provider.dart'; import 'package:Openbook/services/bottom_sheet.dart'; import 'package:Openbook/services/httpie.dart'; +import 'package:Openbook/services/image_picker.dart'; import 'package:Openbook/services/navigation_service.dart'; import 'package:Openbook/services/toast.dart'; import 'package:Openbook/services/user.dart'; @@ -43,7 +44,7 @@ class CreatePostModal extends StatefulWidget { class CreatePostModalState extends State { ValidationService _validationService; NavigationService _navigationService; - BottomSheetService _bottomSheetService; + ImagePickerService _imagePickerService; ToastService _toastService; UserService _userService; @@ -80,7 +81,9 @@ class CreatePostModalState extends State { _isPostTextAllowedLength = false; _hasImage = false; _hasVideo = false; - _postItemsWidgets = [OBCreatePostText(controller: _textController, focusNode: _focusNode)]; + _postItemsWidgets = [ + OBCreatePostText(controller: _textController, focusNode: _focusNode) + ]; if (widget.community != null) _postItemsWidgets.add(OBPostCommunityPreviewer( @@ -104,7 +107,7 @@ class CreatePostModalState extends State { var openbookProvider = OpenbookProvider.of(context); _validationService = openbookProvider.validationService; _navigationService = openbookProvider.navigationService; - _bottomSheetService = openbookProvider.bottomSheetService; + _imagePickerService = openbookProvider.imagePickerService; _userService = openbookProvider.userService; _toastService = openbookProvider.toastService; @@ -262,7 +265,7 @@ class CreatePostModalState extends State { onPressed: () async { _unfocusTextField(); File pickedPhoto = - await _bottomSheetService.showPhotoPicker(context: context); + await _imagePickerService.pickImage(imageType: OBImageType.post); if (pickedPhoto != null) _setPostImage(pickedPhoto); }, ), diff --git a/lib/pages/home/modals/edit_user_profile/edit_user_profile.dart b/lib/pages/home/modals/edit_user_profile/edit_user_profile.dart index 2dda697d7..606f7c6df 100644 --- a/lib/pages/home/modals/edit_user_profile/edit_user_profile.dart +++ b/lib/pages/home/modals/edit_user_profile/edit_user_profile.dart @@ -314,33 +314,19 @@ class OBEditUserProfileModalState extends State { context: context, builder: (BuildContext context) { List listTiles = [ - new ListTile( - leading: new Icon(Icons.camera_alt), - title: new Text('Camera'), - onTap: () async { - try { - var image = await _imagePickerService.pickImage( - source: ImageSource.camera, imageType: imageType); - _onUserImageSelected(image: image, imageType: imageType); - //if (image != null) createAccountBloc.avatar.add(image); - } on ImageTooLargeException catch(e) { - int limit = e.getLimitInMB(); - toastService.error(message: 'Image too large (limit: $limit MB)', context: context); - } - Navigator.pop(context); - }, - ), new ListTile( leading: new Icon(Icons.photo_library), - title: new Text('Gallery'), + title: new Text('Pick image'), onTap: () async { try { - var image = await _imagePickerService.pickImage( - source: ImageSource.gallery, imageType: imageType); + var image = await _imagePickerService.pickImage(imageType: imageType); + _onUserImageSelected(image: image, imageType: imageType); - } on ImageTooLargeException catch(e) { + } on ImageTooLargeException catch (e) { int limit = e.getLimitInMB(); - toastService.error(message: 'Image too large (limit: $limit MB)', context: context); + toastService.error( + message: 'Image too large (limit: $limit MB)', + context: context); } Navigator.pop(context); }, diff --git a/lib/pages/home/modals/save_community.dart b/lib/pages/home/modals/save_community.dart index 96811222b..1fa5fcd0c 100644 --- a/lib/pages/home/modals/save_community.dart +++ b/lib/pages/home/modals/save_community.dart @@ -43,7 +43,7 @@ class OBSaveCommunityModalState extends State { UserService _userService; ToastService _toastService; ValidationService _validationService; - BottomSheetService _bottomSheetService; + ImagePickerService _imagePickerService; ThemeValueParserService _themeValueParserService; bool _requestInProgress; @@ -121,7 +121,7 @@ class OBSaveCommunityModalState extends State { _userService = openbookProvider.userService; _toastService = openbookProvider.toastService; _validationService = openbookProvider.validationService; - _bottomSheetService = openbookProvider.bottomSheetService; + _imagePickerService = openbookProvider.imagePickerService; _themeValueParserService = openbookProvider.themeValueParserService; var themeService = openbookProvider.themeService; @@ -399,8 +399,8 @@ class OBSaveCommunityModalState extends State { } void _pickNewAvatar() async { - File newAvatar = await _bottomSheetService.showPhotoPicker( - context: context, imageType: OBImageType.avatar); + File newAvatar = + await _imagePickerService.pickImage(imageType: OBImageType.avatar); if (newAvatar != null) _setAvatarFile(newAvatar); } @@ -443,8 +443,8 @@ class OBSaveCommunityModalState extends State { } void _pickNewCover() async { - File newCover = await _bottomSheetService.showPhotoPicker( - context: context, imageType: OBImageType.cover); + File newCover = + await _imagePickerService.pickImage(imageType: OBImageType.cover); if (newCover != null) _setCoverFile(newCover); } diff --git a/lib/services/bottom_sheet.dart b/lib/services/bottom_sheet.dart index 6016ef77e..b8b1d9b85 100644 --- a/lib/services/bottom_sheet.dart +++ b/lib/services/bottom_sheet.dart @@ -11,11 +11,9 @@ import 'package:Openbook/pages/home/bottom_sheets/community_type_picker.dart'; import 'package:Openbook/pages/home/bottom_sheets/connection_circles_picker.dart'; import 'package:Openbook/pages/home/bottom_sheets/comment_more_actions.dart'; import 'package:Openbook/pages/home/bottom_sheets/follows_lists_picker.dart'; -import 'package:Openbook/pages/home/bottom_sheets/photo_picker.dart'; import 'package:Openbook/pages/home/bottom_sheets/post_actions.dart'; import 'package:Openbook/pages/home/bottom_sheets/video_picker.dart'; import 'package:Openbook/pages/home/modals/react_to_post/react_to_post.dart'; -import 'package:Openbook/services/image_picker.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'package:meta/meta.dart'; @@ -121,18 +119,6 @@ class BottomSheetService { }); } - Future showPhotoPicker( - {@required BuildContext context, - OBImageType imageType = OBImageType.post}) { - return showModalBottomSheetApp( - context: context, - builder: (BuildContext context) { - return OBPhotoPickerBottomSheet( - imageType: imageType, - ); - }); - } - Future showVideoPicker({@required BuildContext context}) { return showModalBottomSheetApp( context: context, diff --git a/lib/services/image_picker.dart b/lib/services/image_picker.dart index cf247d704..dfc0f247b 100644 --- a/lib/services/image_picker.dart +++ b/lib/services/image_picker.dart @@ -1,12 +1,19 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:flutter_exif_rotation/flutter_exif_rotation.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:meta/meta.dart'; import 'package:Openbook/services/validation.dart'; +import 'package:multi_image_picker/multi_image_picker.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:uuid/uuid.dart'; export 'package:image_picker/image_picker.dart'; class ImagePickerService { + static Uuid uuid = new Uuid(); + static const Map IMAGE_RATIOS = { OBImageType.avatar: {'x': 1.0, 'y': 1.0}, OBImageType.cover: {'x': 16.0, 'y': 9.0} @@ -19,18 +26,30 @@ class ImagePickerService { } Future pickImage( - {@required OBImageType imageType, - ImageSource source = ImageSource.gallery}) async { - var image = await ImagePicker.pickImage(source: source); + {@required OBImageType imageType}) async { + List pickedAssets = + await MultiImagePicker.pickImages(maxImages: 1, enableCamera: true); - if (image == null) { + if (pickedAssets.isEmpty) { return null; } - if (!await _validationService.isImageAllowedSize(image, imageType)) { - throw ImageTooLargeException(_validationService.getAllowedImageSize(imageType)); + Asset pickedAsset = pickedAssets.first; + + String tmpImageName = uuid.v4() + '.jpg'; + final path = await _getTempPath(); + final file = File('$path/$tmpImageName'); + ByteData byteData = await pickedAsset.requestOriginal(); + List imageData = byteData.buffer.asUint8List(); + file.writeAsBytesSync(imageData); + + if (!await _validationService.isImageAllowedSize(file, imageType)) { + throw ImageTooLargeException( + _validationService.getAllowedImageSize(imageType)); } + File processedImage = await _processImage(file); + double ratioX = imageType != OBImageType.post ? IMAGE_RATIOS[imageType]['x'] : null; double ratioY = @@ -41,7 +60,7 @@ class ImagePickerService { toolbarColor: Colors.black, statusBarColor: Colors.black, toolbarWidgetColor: Colors.white, - sourcePath: image.path, + sourcePath: processedImage.path, ratioX: ratioX, ratioY: ratioY, ); @@ -54,6 +73,19 @@ class ImagePickerService { return video; } + + Future _processImage(File image) async { + /// Fix rotation issue on android + if (Platform.isAndroid) + return FlutterExifRotation.rotateImage(path: image.path); + return image; + } + + Future _getTempPath() async { + final directory = await getTemporaryDirectory(); + + return directory.path; + } } class ImageTooLargeException implements Exception { diff --git a/lib/services/push_notifications/push_notifications.dart b/lib/services/push_notifications/push_notifications.dart index c4786f36e..619ecadcf 100644 --- a/lib/services/push_notifications/push_notifications.dart +++ b/lib/services/push_notifications/push_notifications.dart @@ -5,7 +5,7 @@ import 'package:Openbook/models/push_notification.dart'; import 'package:Openbook/models/user.dart'; import 'package:Openbook/services/user.dart'; import 'package:crypto/crypto.dart'; -import 'package:onesignal/onesignal.dart'; +import 'package:onesignalflutter/onesignalflutter.dart'; import 'package:rxdart/rxdart.dart'; class PushNotificationsService { diff --git a/pubspec.lock b/pubspec.lock index 3a55684b1..8fc7490c7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -192,7 +192,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.12.4+1" + version: "0.12.4+2" flutter_test: dependency: "direct dev" description: flutter @@ -232,7 +232,7 @@ packages: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.0" http_parser: dependency: transitive description: @@ -331,6 +331,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.9.6+3" + multi_image_picker: + dependency: "direct main" + description: + name: multi_image_picker + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.3" multi_server_socket: dependency: transitive description: @@ -345,12 +352,14 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.4" - onesignal: + onesignalflutter: dependency: "direct main" description: - name: onesignal - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: HEAD + resolved-ref: fb4bd2da344db58f2073d314ff1e363556804673 + url: "git://github.com/jmrobles/OneSignal-Flutter-SDK.git" + source: git version: "1.1.0" package_config: dependency: transitive @@ -470,7 +479,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "0.5.3" + version: "0.5.3+1" shelf: dependency: transitive description: @@ -645,7 +654,7 @@ packages: source: hosted version: "3.4.1" uuid: - dependency: transitive + dependency: "direct main" description: name: uuid url: "https://pub.dartlang.org" @@ -671,7 +680,7 @@ packages: name: video_player url: "https://pub.dartlang.org" source: hosted - version: "0.10.1+2" + version: "0.10.1+3" vm_service_client: dependency: transitive description: @@ -709,4 +718,4 @@ packages: version: "2.1.15" sdks: dart: ">=2.2.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.5.0 <1.5.9" diff --git a/pubspec.yaml b/pubspec.yaml index 8bd421023..c8a8f952e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,17 +13,20 @@ environment: sdk: ">=2.1.0 <3.0.0" dependencies: + uuid: ^2.0.1 + multi_image_picker: ^4.3.3 + flutter_exif_rotation: ^0.2.2 shared_preferences: ^0.5.2 flutter_markdown: ^0.2.0 sentry: ^2.2.0 - flutter_exif_rotation: ^0.2.0 back_button_interceptor: ^4.0.1 flutter_colorpicker: any intercom_flutter: ^1.0.11 device_info: ^0.4.0+1 flutter_pagewise: ^1.2.2 tinycolor: ^1.0.2 - onesignal: ^1.0.5 + onesignalflutter: + git: git://github.com/jmrobles/OneSignal-Flutter-SDK.git flutter_advanced_networkimage: ^0.4.13 dcache: ^0.1.0 validators: ^1.0.0+1