diff --git a/app/src/main/java/com/jerboa/JerboaAppState.kt b/app/src/main/java/com/jerboa/JerboaAppState.kt index 4bf92e054..5aea1aabf 100644 --- a/app/src/main/java/com/jerboa/JerboaAppState.kt +++ b/app/src/main/java/com/jerboa/JerboaAppState.kt @@ -20,10 +20,12 @@ import com.jerboa.ui.components.post.create.CreatePostReturn import com.jerboa.ui.components.post.edit.PostEditReturn import com.jerboa.ui.components.privatemessage.PrivateMessage import com.jerboa.ui.components.remove.comment.CommentRemoveReturn +import com.jerboa.ui.components.remove.post.PostRemoveReturn import it.vercruysse.lemmyapi.v0x19.datatypes.Comment import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView import it.vercruysse.lemmyapi.v0x19.datatypes.Community import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityView +import it.vercruysse.lemmyapi.v0x19.datatypes.Post import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import it.vercruysse.lemmyapi.v0x19.datatypes.PrivateMessageView import kotlinx.coroutines.CoroutineScope @@ -70,6 +72,11 @@ class JerboaAppState( navController.navigate(Route.PostReportArgs.makeRoute(id = "$id")) } + fun toPostRemove(post: Post) { + sendReturnForwards(PostRemoveReturn.POST_SEND, post) + navController.navigate(Route.POST_REMOVE) + } + fun toCommentRemove(comment: Comment) { sendReturnForwards(CommentRemoveReturn.COMMENT_SEND, comment) navController.navigate(Route.COMMENT_REMOVE) diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index c51a49271..b8acf24af 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -62,6 +62,7 @@ import com.jerboa.ui.components.post.edit.PostEditActivity import com.jerboa.ui.components.privatemessage.CreatePrivateMessageActivity import com.jerboa.ui.components.privatemessage.PrivateMessageReplyActivity import com.jerboa.ui.components.remove.comment.CommentRemoveActivity +import com.jerboa.ui.components.remove.post.PostRemoveActivity import com.jerboa.ui.components.report.comment.CreateCommentReportActivity import com.jerboa.ui.components.report.post.CreatePostReportActivity import com.jerboa.ui.components.settings.SettingsActivity @@ -557,6 +558,15 @@ class MainActivity : AppCompatActivity() { ) } + composable( + route = Route.POST_REMOVE, + ) { + PostRemoveActivity( + appState = appState, + accountViewModel = accountViewModel, + ) + } + composable( route = Route.COMMENT_REMOVE, ) { diff --git a/app/src/main/java/com/jerboa/Utils.kt b/app/src/main/java/com/jerboa/Utils.kt index 4478d4339..6eda4f16a 100644 --- a/app/src/main/java/com/jerboa/Utils.kt +++ b/app/src/main/java/com/jerboa/Utils.kt @@ -1455,7 +1455,7 @@ fun Context.getInputStream(url: String): InputStream { Request.Builder() .url(url) .build(), - ).execute().body?.byteStream() ?: throw IOException("Failed to get input stream") + ).execute().body.byteStream() } } diff --git a/app/src/main/java/com/jerboa/model/RemoveViewModel.kt b/app/src/main/java/com/jerboa/model/CommentRemoveViewModel.kt similarity index 88% rename from app/src/main/java/com/jerboa/model/RemoveViewModel.kt rename to app/src/main/java/com/jerboa/model/CommentRemoveViewModel.kt index ab4794389..cb3a4525a 100644 --- a/app/src/main/java/com/jerboa/model/RemoveViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CommentRemoveViewModel.kt @@ -14,17 +14,18 @@ import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.ui.components.common.apiErrorToast +import it.vercruysse.lemmyapi.v0x19.datatypes.CommentId import it.vercruysse.lemmyapi.v0x19.datatypes.CommentResponse import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView import it.vercruysse.lemmyapi.v0x19.datatypes.RemoveComment import kotlinx.coroutines.launch -class RemoveViewModel : ViewModel() { +class CommentRemoveViewModel : ViewModel() { var commentRemoveRes: ApiState by mutableStateOf(ApiState.Empty) private set fun removeOrRestoreComment( - commentId: Int, + commentId: CommentId, removed: Boolean, reason: String, ctx: Context, @@ -51,9 +52,9 @@ class RemoveViewModel : ViewModel() { is ApiState.Success -> { val message = if (removed) { - ctx.getString(R.string.remove_view_model_comment_removed) + ctx.getString(R.string.comment_removed) } else { - ctx.getString(R.string.remove_view_model_comment_restored) + ctx.getString(R.string.comment_restored) } val commentView = res.data.comment_view Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/com/jerboa/model/PostRemoveViewModel.kt b/app/src/main/java/com/jerboa/model/PostRemoveViewModel.kt new file mode 100644 index 000000000..d14dc3113 --- /dev/null +++ b/app/src/main/java/com/jerboa/model/PostRemoveViewModel.kt @@ -0,0 +1,69 @@ +package com.jerboa.model + +import android.content.Context +import android.util.Log +import android.widget.Toast +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.focus.FocusManager +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jerboa.R +import com.jerboa.api.API +import com.jerboa.api.ApiState +import com.jerboa.api.toApiState +import com.jerboa.ui.components.common.apiErrorToast +import it.vercruysse.lemmyapi.v0x19.datatypes.PostId +import it.vercruysse.lemmyapi.v0x19.datatypes.PostResponse +import it.vercruysse.lemmyapi.v0x19.datatypes.PostView +import it.vercruysse.lemmyapi.v0x19.datatypes.RemovePost +import kotlinx.coroutines.launch + +class PostRemoveViewModel : ViewModel() { + var postRemoveRes: ApiState by mutableStateOf(ApiState.Empty) + private set + + fun removeOrRestorePost( + postId: PostId, + removed: Boolean, + reason: String, + ctx: Context, + focusManager: FocusManager, + onSuccess: (PostView) -> Unit, + ) { + viewModelScope.launch { + val form = + RemovePost( + post_id = postId, + removed = removed, + reason = reason, + ) + + postRemoveRes = ApiState.Loading + postRemoveRes = API.getInstance().removePost(form).toApiState() + + when (val res = postRemoveRes) { + is ApiState.Failure -> { + Log.d("removePost", "failed", res.msg) + apiErrorToast(msg = res.msg, ctx = ctx) + } + + is ApiState.Success -> { + val message = + if (removed) { + ctx.getString(R.string.post_removed) + } else { + ctx.getString(R.string.post_restored) + } + val postView = res.data.post_view + Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show() + + focusManager.clearFocus() + onSuccess(postView) + } + else -> {} + } + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt index a0eee5ff0..510113a6e 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentOptionsDropdown.kt @@ -3,7 +3,6 @@ package com.jerboa.ui.components.comment import android.widget.Toast import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Block -import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Comment import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.CopyAll @@ -11,6 +10,7 @@ import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Description import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Flag +import androidx.compose.material.icons.outlined.Gavel import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Restore @@ -178,7 +178,7 @@ fun CommentOptionsDropdown( if (commentView.comment.removed) { Pair(stringResource(R.string.restore_comment), Icons.Outlined.Restore) } else { - Pair(stringResource(R.string.remove_comment), Icons.Outlined.Close) + Pair(stringResource(R.string.remove_comment), Icons.Outlined.Gavel) } PopupMenuItem( diff --git a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionNode.kt b/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionNode.kt index c707a4a2b..4885b9362 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionNode.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionNode.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.jerboa.R import com.jerboa.VoteType +import com.jerboa.canMod import com.jerboa.datatypes.samplePersonMentionView import com.jerboa.db.entity.Account import com.jerboa.ui.components.comment.CommentBody @@ -40,8 +41,11 @@ import com.jerboa.ui.theme.SMALL_PADDING import com.jerboa.ui.theme.XXL_PADDING import com.jerboa.ui.theme.muted import it.vercruysse.lemmyapi.v0x19.datatypes.Community +import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityModeratorView import it.vercruysse.lemmyapi.v0x19.datatypes.Person import it.vercruysse.lemmyapi.v0x19.datatypes.PersonMentionView +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView +import kotlinx.collections.immutable.ImmutableList @Composable fun CommentMentionNodeHeader( @@ -91,6 +95,8 @@ fun CommentMentionNodeHeaderPreview() { @Composable fun CommentMentionNodeFooterLine( personMentionView: PersonMentionView, + admins: ImmutableList, + moderators: ImmutableList?, onUpvoteClick: () -> Unit, onDownvoteClick: () -> Unit, onReplyClick: (personMentionView: PersonMentionView) -> Unit, @@ -112,6 +118,16 @@ fun CommentMentionNodeFooterLine( ) { var showMoreOptions by remember { mutableStateOf(false) } + val canMod = + remember { + canMod( + creatorId = personMentionView.comment.creator_id, + admins = admins, + moderators = moderators, + myId = account.id, + ) + } + if (showMoreOptions) { CommentMentionsOptionsDropdown( personMentionView = personMentionView, @@ -123,6 +139,7 @@ fun CommentMentionNodeFooterLine( onBlockCreatorClick = onBlockCreatorClick, isCreator = account.id == personMentionView.creator.id, onCommentLinkClick = onLinkClick, + canMod = canMod, viewSource = viewSource, ) } @@ -228,6 +245,8 @@ fun CommentMentionNodeFooterLine( @Composable fun CommentMentionNode( personMentionView: PersonMentionView, + admins: ImmutableList, + moderators: ImmutableList?, onUpvoteClick: (personMentionView: PersonMentionView) -> Unit, onDownvoteClick: (personMentionView: PersonMentionView) -> Unit, onReplyClick: (personMentionView: PersonMentionView) -> Unit, @@ -303,6 +322,8 @@ fun CommentMentionNode( ) { CommentMentionNodeFooterLine( personMentionView = personMentionView, + admins = admins, + moderators = moderators, onUpvoteClick = { onUpvoteClick(personMentionView) }, diff --git a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionOptionsDropdown.kt b/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionOptionsDropdown.kt index 78df6a155..cc712b92b 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionOptionsDropdown.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/mentionnode/CommentMentionOptionsDropdown.kt @@ -8,8 +8,10 @@ import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.CopyAll import androidx.compose.material.icons.outlined.Description import androidx.compose.material.icons.outlined.Flag +import androidx.compose.material.icons.outlined.Gavel import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Person +import androidx.compose.material.icons.outlined.Restore import androidx.compose.material3.Divider import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalClipboardManager @@ -35,6 +37,7 @@ fun CommentMentionsOptionsDropdown( onReportClick: (PersonMentionView) -> Unit, onRemoveClick: (PersonMentionView) -> Unit, isCreator: Boolean, + canMod: Boolean, viewSource: Boolean, ) { val localClipboardManager = LocalClipboardManager.current @@ -128,5 +131,24 @@ fun CommentMentionsOptionsDropdown( }, ) } + + if (canMod) { + Divider() + val (removeText, removeIcon) = + if (personMentionView.comment.removed) { + Pair(stringResource(R.string.restore_comment), Icons.Outlined.Restore) + } else { + Pair(stringResource(R.string.remove_comment), Icons.Outlined.Gavel) + } + + PopupMenuItem( + text = removeText, + icon = removeIcon, + onClick = { + onDismissRequest() + onRemoveClick(personMentionView) + }, + ) + } } } diff --git a/app/src/main/java/com/jerboa/ui/components/common/Route.kt b/app/src/main/java/com/jerboa/ui/components/common/Route.kt index 514b39c21..959e8c76d 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/Route.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/Route.kt @@ -31,6 +31,7 @@ object Route { const val SITE_SIDEBAR = "siteSidebar" const val COMMENT_EDIT = "commentEdit" const val POST_EDIT = "postEdit" + const val POST_REMOVE = "postRemove" const val PRIVATE_MESSAGE_REPLY = "privateMessageReply" const val COMMENT_REMOVE = "commentRemove" val CREATE_PRIVATE_MESSAGE = CreatePrivateMessageArgs.route diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt index c603608b7..32b0423b7 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityActivity.kt @@ -56,6 +56,7 @@ import com.jerboa.ui.components.common.isRefreshing import com.jerboa.ui.components.post.PostListings import com.jerboa.ui.components.post.PostViewReturn import com.jerboa.ui.components.post.edit.PostEditReturn +import com.jerboa.ui.components.remove.post.PostRemoveReturn import it.vercruysse.lemmyapi.dto.SubscribedType import it.vercruysse.lemmyapi.v0x19.datatypes.BlockCommunity import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityId @@ -96,6 +97,7 @@ fun CommunityActivity( viewModel(factory = CommunityViewModel.Companion.Factory(communityArg)) appState.ConsumeReturn(PostEditReturn.POST_VIEW, communityViewModel::updatePost) + appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, communityViewModel::updatePost) appState.ConsumeReturn(PostViewReturn.POST_VIEW, communityViewModel::updatePost) val pullRefreshState = @@ -190,10 +192,23 @@ fun CommunityActivity( ApiState.Empty -> ApiEmptyText() is ApiState.Failure -> ApiErrorText(postsRes.msg) is ApiState.Holder -> { + val communityRes = communityViewModel.communityRes + val moderators = + remember(communityRes) { + when (communityRes) { + is ApiState.Success -> communityRes.data.moderators.toImmutableList() + else -> { + null + } + } + } + PostListings( posts = postsRes.data.posts.toImmutableList(), + admins = siteViewModel.admins(), + moderators = moderators, contentAboveListings = { - when (val communityRes = communityViewModel.communityRes) { + when (communityRes) { is ApiState.Success -> { CommunityTopSection( communityView = communityRes.data.community_view, @@ -315,6 +330,9 @@ fun CommunityActivity( onReportClick = { postView -> appState.toPostReport(id = postView.post.id) }, + onRemoveClick = { pv -> + appState.toPostRemove(post = pv.post) + }, onCommunityClick = { community -> appState.toCommunity(id = community.id) }, diff --git a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt index c5210d21c..e2308be62 100644 --- a/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/home/HomeActivity.kt @@ -62,6 +62,7 @@ import com.jerboa.ui.components.common.isRefreshing import com.jerboa.ui.components.post.PostListings import com.jerboa.ui.components.post.PostViewReturn import com.jerboa.ui.components.post.edit.PostEditReturn +import com.jerboa.ui.components.remove.post.PostRemoveReturn import it.vercruysse.lemmyapi.v0x19.datatypes.CreatePostLike import it.vercruysse.lemmyapi.v0x19.datatypes.DeletePost import it.vercruysse.lemmyapi.v0x19.datatypes.MarkPostAsRead @@ -104,6 +105,7 @@ fun HomeActivity( val snackbarHostState = remember(account) { SnackbarHostState() } appState.ConsumeReturn(PostEditReturn.POST_VIEW, homeViewModel::updatePost) + appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, homeViewModel::updatePost) appState.ConsumeReturn(PostViewReturn.POST_VIEW, homeViewModel::updatePost) LaunchedEffect(account) { @@ -251,6 +253,9 @@ fun MainPostListingsContent( PostListings( posts = posts, + admins = siteViewModel.admins(), + // No community moderators available here + moderators = null, contentAboveListings = { if (taglines !== null) Taglines(taglines = taglines.toImmutableList()) }, onUpvoteClick = { postView -> account.doIfReadyElseDisplayInfo( @@ -335,6 +340,9 @@ fun MainPostListingsContent( onReportClick = { postView -> appState.toPostReport(id = postView.post.id) }, + onRemoveClick = { pv -> + appState.toPostRemove(post = pv.post) + }, onCommunityClick = { community -> appState.toCommunity(id = community.id) }, diff --git a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt index 4de961b41..0c523e108 100644 --- a/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/inbox/InboxActivity.kt @@ -533,6 +533,9 @@ fun InboxTabs( ) { pmv -> CommentMentionNode( personMentionView = pmv, + admins = siteViewModel.admins(), + // No community moderators available here + moderators = null, onUpvoteClick = { pm -> account.doIfReadyElseDisplayInfo( appState, diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt index c7cda55b5..c8391941e 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileActivity.kt @@ -81,6 +81,7 @@ import com.jerboa.ui.components.post.PostListings import com.jerboa.ui.components.post.PostViewReturn import com.jerboa.ui.components.post.edit.PostEditReturn import com.jerboa.ui.components.remove.comment.CommentRemoveReturn +import com.jerboa.ui.components.remove.post.PostRemoveReturn import com.jerboa.ui.theme.MEDIUM_PADDING import it.vercruysse.lemmyapi.v0x19.datatypes.BlockPerson import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView @@ -130,6 +131,7 @@ fun PersonProfileActivity( viewModel(factory = PersonProfileViewModel.Companion.Factory(personArg, savedMode)) appState.ConsumeReturn(PostEditReturn.POST_VIEW, personProfileViewModel::updatePost) + appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, personProfileViewModel::updatePost) appState.ConsumeReturn(CommentEditReturn.COMMENT_VIEW, personProfileViewModel::updateComment) appState.ConsumeReturn(CommentRemoveReturn.COMMENT_VIEW, personProfileViewModel::updateComment) @@ -453,6 +455,9 @@ fun UserTabs( is ApiState.Holder -> { PostListings( posts = profileRes.data.posts.toImmutableList(), + admins = siteViewModel.admins(), + // No community moderators available here + moderators = null, onUpvoteClick = { pv -> account.doIfReadyElseDisplayInfo( appState, @@ -536,6 +541,9 @@ fun UserTabs( onReportClick = { pv -> appState.toPostReport(id = pv.post.id) }, + onRemoveClick = { pv -> + appState.toPostRemove(post = pv.post) + }, onCommunityClick = { community -> appState.toCommunity(id = community.id) }, diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt b/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt index fa1c745a5..5b941cd83 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostActivity.kt @@ -86,6 +86,7 @@ import com.jerboa.ui.components.common.isRefreshing import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.components.post.edit.PostEditReturn import com.jerboa.ui.components.remove.comment.CommentRemoveReturn +import com.jerboa.ui.components.remove.post.PostRemoveReturn import it.vercruysse.lemmyapi.dto.CommentSortType import it.vercruysse.lemmyapi.v0x19.datatypes.* import kotlinx.collections.immutable.toImmutableList @@ -140,6 +141,7 @@ fun PostActivity( val postViewModel: PostViewModel = viewModel(factory = PostViewModel.Companion.Factory(id)) appState.ConsumeReturn(PostEditReturn.POST_VIEW, postViewModel::updatePost) + appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, postViewModel::updatePost) appState.ConsumeReturn(CommentReplyReturn.COMMENT_VIEW, postViewModel::appendComment) appState.ConsumeReturn(CommentEditReturn.COMMENT_VIEW, postViewModel::updateComment) appState.ConsumeReturn(CommentRemoveReturn.COMMENT_VIEW, postViewModel::updateComment) @@ -295,6 +297,8 @@ fun PostActivity( item(key = "${postView.post.id}_listing", "post_listing") { PostListing( postView = postView, + admins = siteViewModel.admins(), + moderators = moderators, onUpvoteClick = { pv -> account.doIfReadyElseDisplayInfo( appState, @@ -388,6 +392,9 @@ fun PostActivity( onReportClick = { pv -> appState.toPostReport(id = pv.post.id) }, + onRemoveClick = { pv -> + appState.toPostRemove(post = pv.post) + }, onPersonClick = appState::toProfile, showReply = true, // Do nothing fullBody = true, diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt index 4e01aa821..1abb5aa18 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListing.kt @@ -26,6 +26,7 @@ import androidx.compose.material.icons.outlined.Comment import androidx.compose.material.icons.outlined.CommentsDisabled import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Forum +import androidx.compose.material.icons.outlined.Gavel import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.PushPin @@ -62,6 +63,7 @@ import com.jerboa.PostViewMode import com.jerboa.R import com.jerboa.VoteType import com.jerboa.calculateNewInstantScores +import com.jerboa.canMod import com.jerboa.datatypes.sampleImagePostView import com.jerboa.datatypes.sampleLinkNoThumbnailPostView import com.jerboa.datatypes.sampleLinkPostView @@ -113,6 +115,8 @@ import com.jerboa.ui.theme.XXL_PADDING import com.jerboa.ui.theme.jerboaColorScheme import com.jerboa.ui.theme.muted import it.vercruysse.lemmyapi.v0x19.datatypes.* +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope @Composable @@ -208,6 +212,13 @@ fun PostHeaderLine( tint = MaterialTheme.colorScheme.error, ) } + if (postView.post.removed) { + Icon( + imageVector = Icons.Outlined.Gavel, + contentDescription = stringResource(R.string.removed), + tint = MaterialTheme.colorScheme.error, + ) + } } ScoreAndTime( score = score, @@ -532,6 +543,8 @@ fun PreviewSourcePost() { @Composable fun PostFooterLine( postView: PostView, + admins: ImmutableList, + moderators: ImmutableList?, instantScores: InstantScores, onUpvoteClick: () -> Unit, onDownvoteClick: () -> Unit, @@ -540,6 +553,7 @@ fun PostFooterLine( onEditPostClick: (postView: PostView) -> Unit, onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, + onRemoveClick: (postView: PostView) -> Unit, onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: Int) -> Unit, onViewSourceClick: () -> Unit, @@ -555,6 +569,16 @@ fun PostFooterLine( ) { var showMoreOptions by remember { mutableStateOf(false) } + val canMod = + remember { + canMod( + creatorId = postView.creator.id, + admins = admins, + moderators = moderators, + myId = account.id, + ) + } + if (showMoreOptions) { PostOptionsDropdown( postView = postView, @@ -564,8 +588,10 @@ fun PostFooterLine( onEditPostClick = onEditPostClick, onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, + onRemoveClick = onRemoveClick, onViewSourceClick = onViewSourceClick, isCreator = account.id == postView.creator.id, + canMod = canMod, viewSource = viewSource, showViewSource = fromPostActivity, scope = scope, @@ -745,6 +771,8 @@ fun PostFooterLinePreview() { ) PostFooterLine( postView = postView, + admins = persistentListOf(), + moderators = persistentListOf(), instantScores = instantScores, onUpvoteClick = {}, onDownvoteClick = {}, @@ -753,6 +781,7 @@ fun PostFooterLinePreview() { onEditPostClick = {}, onDeletePostClick = {}, onReportClick = {}, + onRemoveClick = {}, onCommunityClick = {}, onPersonClick = {}, onViewSourceClick = {}, @@ -771,6 +800,8 @@ fun PostFooterLinePreview() { fun PreviewPostListingCard() { PostListing( postView = samplePostView, + admins = persistentListOf(), + moderators = persistentListOf(), useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -782,6 +813,7 @@ fun PreviewPostListingCard() { onEditPostClick = {}, onDeletePostClick = {}, onReportClick = {}, + onRemoveClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -803,6 +835,8 @@ fun PreviewPostListingCard() { fun PreviewLinkPostListing() { PostListing( postView = sampleLinkPostView, + admins = persistentListOf(), + moderators = persistentListOf(), useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -814,6 +848,7 @@ fun PreviewLinkPostListing() { onEditPostClick = {}, onDeletePostClick = {}, onReportClick = {}, + onRemoveClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -835,6 +870,8 @@ fun PreviewLinkPostListing() { fun PreviewImagePostListingCard() { PostListing( postView = sampleImagePostView, + admins = persistentListOf(), + moderators = persistentListOf(), useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -846,6 +883,7 @@ fun PreviewImagePostListingCard() { onEditPostClick = {}, onDeletePostClick = {}, onReportClick = {}, + onRemoveClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -867,6 +905,8 @@ fun PreviewImagePostListingCard() { fun PreviewImagePostListingSmallCard() { PostListing( postView = sampleImagePostView, + admins = persistentListOf(), + moderators = persistentListOf(), useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -878,6 +918,7 @@ fun PreviewImagePostListingSmallCard() { onEditPostClick = {}, onDeletePostClick = {}, onReportClick = {}, + onRemoveClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -899,6 +940,8 @@ fun PreviewImagePostListingSmallCard() { fun PreviewLinkNoThumbnailPostListing() { PostListing( postView = sampleLinkNoThumbnailPostView, + admins = persistentListOf(), + moderators = persistentListOf(), useCustomTabs = false, usePrivateTabs = false, onUpvoteClick = {}, @@ -910,6 +953,7 @@ fun PreviewLinkNoThumbnailPostListing() { onEditPostClick = {}, onDeletePostClick = {}, onReportClick = {}, + onRemoveClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -929,6 +973,8 @@ fun PreviewLinkNoThumbnailPostListing() { @Composable fun PostListing( postView: PostView, + admins: ImmutableList, + moderators: ImmutableList?, useCustomTabs: Boolean, usePrivateTabs: Boolean, onUpvoteClick: (postView: PostView) -> Unit, @@ -940,6 +986,7 @@ fun PostListing( onEditPostClick: (postView: PostView) -> Unit, onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, + onRemoveClick: (postView: PostView) -> Unit, onPersonClick: (personId: Int) -> Unit, showReply: Boolean = false, showCommunityName: Boolean = true, @@ -975,6 +1022,8 @@ fun PostListing( PostViewMode.Card -> PostListingCard( postView = postView, + admins = admins, + moderators = moderators, instantScores = instantScores.value, onUpvoteClick = { instantScores.value = @@ -999,6 +1048,7 @@ fun PostListing( onEditPostClick = onEditPostClick, onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, + onRemoveClick = onRemoveClick, onPersonClick = onPersonClick, onViewSourceClick = { viewSource = !viewSource @@ -1024,6 +1074,8 @@ fun PostListing( PostViewMode.SmallCard -> PostListingCard( postView = postView, + admins = admins, + moderators = moderators, instantScores = instantScores.value, onUpvoteClick = { instantScores.value = @@ -1048,6 +1100,7 @@ fun PostListing( onEditPostClick = onEditPostClick, onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, + onRemoveClick = onRemoveClick, onPersonClick = onPersonClick, onViewSourceClick = { viewSource = !viewSource @@ -1430,6 +1483,8 @@ fun PostListingListWithThumbPreview() { @Composable fun PostListingCard( postView: PostView, + admins: ImmutableList, + moderators: ImmutableList?, instantScores: InstantScores, onUpvoteClick: () -> Unit, onDownvoteClick: () -> Unit, @@ -1440,6 +1495,7 @@ fun PostListingCard( onEditPostClick: (postView: PostView) -> Unit, onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, + onRemoveClick: (postView: PostView) -> Unit, onPersonClick: (personId: Int) -> Unit, onViewSourceClick: () -> Unit, viewSource: Boolean, @@ -1505,6 +1561,8 @@ fun PostListingCard( // Footer bar PostFooterLine( postView = postView, + admins = admins, + moderators = moderators, instantScores = instantScores, onUpvoteClick = onUpvoteClick, onDownvoteClick = onDownvoteClick, @@ -1513,6 +1571,7 @@ fun PostListingCard( onEditPostClick = onEditPostClick, onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, + onRemoveClick = onRemoveClick, onCommunityClick = onCommunityClick, onPersonClick = onPersonClick, onViewSourceClick = onViewSourceClick, diff --git a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt index 01c9a133f..6d259d9ac 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/PostListings.kt @@ -30,6 +30,8 @@ import com.jerboa.ui.components.common.RetryLoadingPosts import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.theme.SMALL_PADDING import it.vercruysse.lemmyapi.v0x19.datatypes.Community +import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityModeratorView +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -37,6 +39,8 @@ import kotlinx.collections.immutable.persistentListOf @Composable fun PostListings( posts: ImmutableList, + admins: ImmutableList, + moderators: ImmutableList?, contentAboveListings: @Composable () -> Unit = {}, onUpvoteClick: (postView: PostView) -> Unit, onDownvoteClick: (postView: PostView) -> Unit, @@ -45,6 +49,7 @@ fun PostListings( onEditPostClick: (postView: PostView) -> Unit, onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, + onRemoveClick: (postView: PostView) -> Unit, onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: Int) -> Unit, loadMorePosts: () -> Unit, @@ -87,6 +92,8 @@ fun PostListings( ) { index, postView -> PostListing( postView = postView, + admins = admins, + moderators = moderators, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, onUpvoteClick = onUpvoteClick, @@ -97,6 +104,7 @@ fun PostListings( onEditPostClick = onEditPostClick, onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, + onRemoveClick = onRemoveClick, onPersonClick = onPersonClick, showCommunityName = showCommunityName, fullBody = false, @@ -152,6 +160,8 @@ fun PostListings( fun PreviewPostListings() { PostListings( posts = persistentListOf(samplePostView, sampleLinkPostView), + admins = persistentListOf(), + moderators = persistentListOf(), onUpvoteClick = {}, onDownvoteClick = {}, onPostClick = {}, @@ -159,6 +169,7 @@ fun PreviewPostListings() { onEditPostClick = {}, onDeletePostClick = {}, onReportClick = {}, + onRemoveClick = {}, onCommunityClick = {}, onPersonClick = {}, loadMorePosts = {}, diff --git a/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt b/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt index 4c4335a7f..8e07c81da 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/composables/PostOptionsDropdown.kt @@ -10,6 +10,7 @@ import androidx.compose.material.icons.outlined.Description import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.Flag import androidx.compose.material.icons.outlined.Forum +import androidx.compose.material.icons.outlined.Gavel import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Restore @@ -54,8 +55,10 @@ fun PostOptionsDropdown( onEditPostClick: (PostView) -> Unit, onDeletePostClick: (PostView) -> Unit, onReportClick: (PostView) -> Unit, + onRemoveClick: (PostView) -> Unit, onViewSourceClick: () -> Unit, isCreator: Boolean, + canMod: Boolean, viewSource: Boolean, showViewSource: Boolean, scope: CoroutineScope, @@ -398,6 +401,25 @@ fun PostOptionsDropdown( onReportClick(postView) }, ) + + if (canMod) { + Divider() + val (removeText, removeIcon) = + if (postView.post.removed) { + Pair(stringResource(R.string.restore_post), Icons.Outlined.Restore) + } else { + Pair(stringResource(R.string.remove_post), Icons.Outlined.Gavel) + } + + PopupMenuItem( + text = removeText, + icon = removeIcon, + onClick = { + onDismissRequest() + onRemoveClick(postView) + }, + ) + } } } } diff --git a/app/src/main/java/com/jerboa/ui/components/remove/comment/CommentRemoveActivity.kt b/app/src/main/java/com/jerboa/ui/components/remove/comment/CommentRemoveActivity.kt index 3d4d53a3d..92c94161d 100644 --- a/app/src/main/java/com/jerboa/ui/components/remove/comment/CommentRemoveActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/remove/comment/CommentRemoveActivity.kt @@ -19,7 +19,7 @@ import com.jerboa.R import com.jerboa.api.ApiState import com.jerboa.db.entity.isAnon import com.jerboa.model.AccountViewModel -import com.jerboa.model.RemoveViewModel +import com.jerboa.model.CommentRemoveViewModel import com.jerboa.ui.components.common.ActionTopBar import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.remove.RemoveItemBody @@ -40,7 +40,7 @@ fun CommentRemoveActivity( val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel = accountViewModel) - val removeViewModel: RemoveViewModel = viewModel() + val commentRemoveViewModel: CommentRemoveViewModel = viewModel() val comment = appState.getPrevReturn(key = CommentRemoveReturn.COMMENT_SEND) var reason by rememberSaveable(stateSaver = TextFieldValue.Saver) { @@ -49,7 +49,7 @@ fun CommentRemoveActivity( ) } val loading = - when (removeViewModel.commentRemoveRes) { + when (commentRemoveViewModel.commentRemoveRes) { ApiState.Loading -> true else -> false } @@ -64,7 +64,7 @@ fun CommentRemoveActivity( loading = loading, onActionClick = { if (!account.isAnon()) { - removeViewModel.removeOrRestoreComment( + commentRemoveViewModel.removeOrRestoreComment( commentId = comment.id, reason = reason.text, removed = !comment.removed, diff --git a/app/src/main/java/com/jerboa/ui/components/remove/post/PostRemoveActivity.kt b/app/src/main/java/com/jerboa/ui/components/remove/post/PostRemoveActivity.kt new file mode 100644 index 000000000..36defc08a --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/remove/post/PostRemoveActivity.kt @@ -0,0 +1,95 @@ +package com.jerboa.ui.components.remove.post + +import android.util.Log +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Send +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import androidx.lifecycle.viewmodel.compose.viewModel +import com.jerboa.JerboaAppState +import com.jerboa.R +import com.jerboa.api.ApiState +import com.jerboa.db.entity.isAnon +import com.jerboa.model.AccountViewModel +import com.jerboa.model.PostRemoveViewModel +import com.jerboa.ui.components.common.ActionTopBar +import com.jerboa.ui.components.common.getCurrentAccount +import com.jerboa.ui.components.remove.RemoveItemBody +import it.vercruysse.lemmyapi.v0x19.datatypes.Post + +object PostRemoveReturn { + const val POST_VIEW = "post-edit::return(post-view)" + const val POST_SEND = "post-edit::send(post-view)" +} + +@Composable +fun PostRemoveActivity( + appState: JerboaAppState, + accountViewModel: AccountViewModel, +) { + Log.d("jerboa", "got to create post remove activity") + + val ctx = LocalContext.current + val account = getCurrentAccount(accountViewModel = accountViewModel) + + val postRemoveViewModel: PostRemoveViewModel = viewModel() + val post = appState.getPrevReturn(key = PostRemoveReturn.POST_SEND) + + var reason by rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf( + TextFieldValue(""), + ) + } + val loading = + when (postRemoveViewModel.postRemoveRes) { + ApiState.Loading -> true + else -> false + } + + val focusManager = LocalFocusManager.current + val title = stringResource(if (post.removed) R.string.restore_post else R.string.remove_post) + + Scaffold( + topBar = { + ActionTopBar( + title = title, + loading = loading, + onActionClick = { + if (!account.isAnon()) { + postRemoveViewModel.removeOrRestorePost( + postId = post.id, + reason = reason.text, + removed = !post.removed, + ctx = ctx, + focusManager = focusManager, + ) { postView -> + appState.apply { + addReturn(PostRemoveReturn.POST_VIEW, postView) + navigateUp() + } + } + } + }, + actionText = R.string.form_submit, + actionIcon = Icons.Outlined.Send, + onBackClick = appState::popBackStack, + ) + }, + content = { padding -> + RemoveItemBody( + reason = reason, + onReasonChange = { reason = it }, + account = account, + padding = padding, + ) + }, + ) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 46e9f8457..72b27637f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -218,6 +218,7 @@ Report Person Upload Image Deleted + Removed Featured in community Featured locally Go to community @@ -403,11 +404,13 @@ Failed to parse datetime There is no record of this comment The version (%s) is not supported - Comment removed - Comment restored - Post removed - Post restored + Comment removed + Comment restored + Post removed + Post restored Remove Comment Restore Comment + Remove Post + Restore Post Type your reason