From 13a18d77629ec57b0cabb16b97b416fe3252d2a7 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Sat, 20 Jan 2024 11:22:38 -0500 Subject: [PATCH] Adding ban and ban from community. - Adding appropriate viewModels and activities. - Adds functions to search for and update lists of posts and comments - Context: #1182 --- .../main/java/com/jerboa/JerboaAppState.kt | 14 ++ app/src/main/java/com/jerboa/MainActivity.kt | 50 +++++-- app/src/main/java/com/jerboa/Utils.kt | 69 +++++++++ .../main/java/com/jerboa/datatypes/Others.kt | 13 ++ .../jerboa/model/BanFromCommunityViewModel.kt | 95 +++++++++++++ .../com/jerboa/model/BanPersonViewModel.kt | 80 +++++++++++ .../jerboa/model/PersonProfileViewModel.kt | 54 ++++++- .../java/com/jerboa/model/PostViewModel.kt | 63 ++++++++- .../java/com/jerboa/model/PostsViewModel.kt | 28 ++++ .../ban/BanFromCommunityActivity.kt | 132 ++++++++++++++++++ .../com/jerboa/ui/components/ban/BanPerson.kt | 79 +++++++++++ .../ui/components/ban/BanPersonActivity.kt | 122 ++++++++++++++++ .../ui/components/comment/CommentNode.kt | 31 ++-- .../ui/components/comment/CommentNodes.kt | 11 ++ .../comment/CommentOptionsDropdown.kt | 23 ++- .../comment/mentionnode/CommentMentionNode.kt | 14 +- .../ui/components/common/InputFields.kt | 47 +++++++ .../jerboa/ui/components/common/PopupItems.kt | 61 ++++++++ .../com/jerboa/ui/components/common/Route.kt | 2 + .../components/community/CommunityActivity.kt | 12 ++ .../jerboa/ui/components/home/HomeActivity.kt | 12 ++ .../person/PersonProfileActivity.kt | 18 +++ .../jerboa/ui/components/post/PostActivity.kt | 17 +++ .../jerboa/ui/components/post/PostListing.kt | 41 ++++-- .../jerboa/ui/components/post/PostListings.kt | 8 ++ .../post/composables/PostComposables.kt | 28 +--- .../post/composables/PostOptionsDropdown.kt | 25 +++- .../main/java/com/jerboa/ui/theme/Color.kt | 2 - app/src/main/res/values/strings.xml | 13 ++ build.gradle.kts | 2 +- 30 files changed, 1090 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt create mode 100644 app/src/main/java/com/jerboa/model/BanPersonViewModel.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityActivity.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/ban/BanPersonActivity.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt diff --git a/app/src/main/java/com/jerboa/JerboaAppState.kt b/app/src/main/java/com/jerboa/JerboaAppState.kt index 5aea1aabf..4a5a8edf3 100644 --- a/app/src/main/java/com/jerboa/JerboaAppState.kt +++ b/app/src/main/java/com/jerboa/JerboaAppState.kt @@ -11,7 +11,10 @@ import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.model.ReplyItem +import com.jerboa.ui.components.ban.BanFromCommunityReturn +import com.jerboa.ui.components.ban.BanPersonReturn import com.jerboa.ui.components.comment.edit.CommentEditReturn import com.jerboa.ui.components.comment.reply.CommentReplyReturn import com.jerboa.ui.components.common.Route @@ -25,6 +28,7 @@ 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.Person import it.vercruysse.lemmyapi.v0x19.datatypes.Post import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import it.vercruysse.lemmyapi.v0x19.datatypes.PrivateMessageView @@ -82,6 +86,16 @@ class JerboaAppState( navController.navigate(Route.COMMENT_REMOVE) } + fun toBanPerson(person: Person) { + sendReturnForwards(BanPersonReturn.PERSON_SEND, person) + navController.navigate(Route.BAN_PERSON) + } + + fun toBanFromCommunity(banData: BanFromCommunityData) { + sendReturnForwards(BanFromCommunityReturn.BAN_DATA_SEND, banData) + navController.navigate(Route.BAN_FROM_COMMUNITY) + } + fun toSettings() = navController.navigate(Route.SETTINGS) fun toAccountSettings() = navController.navigate(Route.ACCOUNT_SETTINGS) diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index b8acf24af..9720bd44f 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -40,6 +40,8 @@ import com.jerboa.model.AccountViewModelFactory import com.jerboa.model.AppSettingsViewModel import com.jerboa.model.AppSettingsViewModelFactory import com.jerboa.model.SiteViewModel +import com.jerboa.ui.components.ban.BanFromCommunityActivity +import com.jerboa.ui.components.ban.BanPersonActivity import com.jerboa.ui.components.comment.edit.CommentEditActivity import com.jerboa.ui.components.comment.reply.CommentReplyActivity import com.jerboa.ui.components.common.LinkDropDownMenu @@ -542,35 +544,36 @@ class MainActivity : AppCompatActivity() { } composable( - route = Route.COMMENT_REPORT, - arguments = - listOf( - navArgument(Route.CommentReportArgs.ID) { - type = Route.CommentReportArgs.ID_TYPE - }, - ), + route = Route.POST_REMOVE, ) { - val args = Route.CommentReportArgs(it) - CreateCommentReportActivity( - commentId = args.id, + PostRemoveActivity( + appState = appState, accountViewModel = accountViewModel, - onBack = appState::navigateUp, ) } composable( - route = Route.POST_REMOVE, + route = Route.COMMENT_REMOVE, ) { - PostRemoveActivity( + CommentRemoveActivity( appState = appState, accountViewModel = accountViewModel, ) } composable( - route = Route.COMMENT_REMOVE, + route = Route.BAN_PERSON, ) { - CommentRemoveActivity( + BanPersonActivity( + appState = appState, + accountViewModel = accountViewModel, + ) + } + + composable( + route = Route.BAN_FROM_COMMUNITY, + ) { + BanFromCommunityActivity( appState = appState, accountViewModel = accountViewModel, ) @@ -593,6 +596,23 @@ class MainActivity : AppCompatActivity() { ) } + composable( + route = Route.COMMENT_REPORT, + arguments = + listOf( + navArgument(Route.CommentReportArgs.ID) { + type = Route.CommentReportArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommentReportArgs(it) + CreateCommentReportActivity( + commentId = args.id, + accountViewModel = accountViewModel, + onBack = appState::navigateUp, + ) + } + composable(route = Route.SETTINGS) { SettingsActivity( accountViewModel = accountViewModel, diff --git a/app/src/main/java/com/jerboa/Utils.kt b/app/src/main/java/com/jerboa/Utils.kt index 6eda4f16a..7067a9545 100644 --- a/app/src/main/java/com/jerboa/Utils.kt +++ b/app/src/main/java/com/jerboa/Utils.kt @@ -51,6 +51,7 @@ import coil.annotation.ExperimentalCoilApi import coil.imageLoader import com.jerboa.api.API import com.jerboa.api.ApiState +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.APP_SETTINGS_DEFAULT import com.jerboa.db.entity.AppSettings import com.jerboa.ui.components.common.Route @@ -70,6 +71,8 @@ import java.io.InputStream import java.net.MalformedURLException import java.net.URL import java.text.DecimalFormat +import java.time.Duration +import java.time.Instant import java.util.* import kotlin.math.abs import kotlin.math.pow @@ -1204,6 +1207,66 @@ fun findAndUpdateComment( } } +fun findAndUpdatePostCreator( + posts: List, + person: Person, +): List { + val newPosts = posts.toMutableList() + newPosts.replaceAll { + if (it.creator.id == person.id) { + it.copy(creator = person) + } else { + it + } + } + return newPosts +} + +fun findAndUpdatePostCreatorBannedFromCommunity( + posts: List, + banData: BanFromCommunityData, +): List { + val newPosts = posts.toMutableList() + newPosts.replaceAll { + if (it.creator.id == banData.person.id && it.community.id == banData.community.id) { + it.copy(creator_banned_from_community = banData.banned) + } else { + it + } + } + return newPosts +} + +fun findAndUpdateCommentCreator( + comments: List, + person: Person, +): List { + val newComments = comments.toMutableList() + newComments.replaceAll { + if (it.creator.id == person.id) { + it.copy(creator = person) + } else { + it + } + } + return newComments +} + +fun findAndUpdateCommentCreatorBannedFromCommunity( + comments: List, + banData: BanFromCommunityData, +): List { + val newComments = comments.toMutableList() + newComments.replaceAll { + if (it.creator.id == banData.person.id && it.community.id == banData.community.id) { + it.copy(creator_banned_from_community = banData.banned) + } else { + it + } + } + return newComments +} + fun findAndUpdateCommentReply( replies: List, updatedCommentView: CommentView, @@ -1568,3 +1631,9 @@ fun canMod( false } } + +fun futureDaysToUnixTime(days: Int?): Int? { + return days?.let { + (Date.from(Instant.now().plus(Duration.ofDays(it.toLong()))).time / 1000L).toInt() + } +} diff --git a/app/src/main/java/com/jerboa/datatypes/Others.kt b/app/src/main/java/com/jerboa/datatypes/Others.kt index 670711d58..d072bcf00 100644 --- a/app/src/main/java/com/jerboa/datatypes/Others.kt +++ b/app/src/main/java/com/jerboa/datatypes/Others.kt @@ -20,6 +20,9 @@ import com.jerboa.ui.components.person.UserTab import it.vercruysse.lemmyapi.dto.CommentSortType import it.vercruysse.lemmyapi.dto.ListingType import it.vercruysse.lemmyapi.dto.SortType +import it.vercruysse.lemmyapi.v0x19.datatypes.Community +import it.vercruysse.lemmyapi.v0x19.datatypes.Person +import kotlinx.serialization.Serializable data class CommentSortData( @@ -187,3 +190,13 @@ fun getLocalizedStringForInboxTab( } return returnString } + +/** + * A wrapper to store extra community ban info + */ +@Serializable +data class BanFromCommunityData( + val person: Person, + val community: Community, + val banned: Boolean, +) diff --git a/app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt b/app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt new file mode 100644 index 000000000..644464eca --- /dev/null +++ b/app/src/main/java/com/jerboa/model/BanFromCommunityViewModel.kt @@ -0,0 +1,95 @@ +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.communityNameShown +import com.jerboa.datatypes.BanFromCommunityData +import com.jerboa.futureDaysToUnixTime +import com.jerboa.personNameShown +import com.jerboa.ui.components.common.apiErrorToast +import it.vercruysse.lemmyapi.v0x19.datatypes.BanFromCommunity +import it.vercruysse.lemmyapi.v0x19.datatypes.BanFromCommunityResponse +import it.vercruysse.lemmyapi.v0x19.datatypes.Community +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId +import kotlinx.coroutines.launch + +class BanFromCommunityViewModel : ViewModel() { + var banFromCommunityRes: ApiState by mutableStateOf(ApiState.Empty) + private set + + fun banOrUnbanFromCommunity( + personId: PersonId, + community: Community, + ban: Boolean, + removeData: Boolean? = null, + reason: String, + expireDays: Int? = null, + ctx: Context, + focusManager: FocusManager, + onSuccess: (BanFromCommunityData) -> Unit, + ) { + viewModelScope.launch { + val form = + BanFromCommunity( + person_id = personId, + community_id = community.id, + ban = ban, + remove_data = removeData, + reason = reason, + expires = futureDaysToUnixTime(expireDays), + ) + + banFromCommunityRes = ApiState.Loading + banFromCommunityRes = API.getInstance().banFromCommunity(form).toApiState() + + when (val res = banFromCommunityRes) { + is ApiState.Failure -> { + Log.d("banFromCommunity", "failed", res.msg) + apiErrorToast(msg = res.msg, ctx = ctx) + } + + is ApiState.Success -> { + val personNameShown = personNameShown(res.data.person_view.person, true) + val communityNameShown = communityNameShown(community) + val message = + if (ban) { + if (expireDays !== null) { + ctx.getString( + R.string.person_banned_from_community_for_x_days, + personNameShown, + communityNameShown, + expireDays, + ) + } else { + ctx.getString(R.string.person_banned_from_community, personNameShown, communityNameShown) + } + } else { + ctx.getString(R.string.person_unbanned_from_community, personNameShown, communityNameShown) + } + Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show() + + focusManager.clearFocus() + onSuccess( + BanFromCommunityData( + person = res.data.person_view.person, + community = community, + banned = res.data.banned, + ), + ) + } + else -> {} + } + } + } +} diff --git a/app/src/main/java/com/jerboa/model/BanPersonViewModel.kt b/app/src/main/java/com/jerboa/model/BanPersonViewModel.kt new file mode 100644 index 000000000..80998e4d8 --- /dev/null +++ b/app/src/main/java/com/jerboa/model/BanPersonViewModel.kt @@ -0,0 +1,80 @@ +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.futureDaysToUnixTime +import com.jerboa.personNameShown +import com.jerboa.ui.components.common.apiErrorToast +import it.vercruysse.lemmyapi.v0x19.datatypes.BanPerson +import it.vercruysse.lemmyapi.v0x19.datatypes.BanPersonResponse +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView +import kotlinx.coroutines.launch + +class BanPersonViewModel : ViewModel() { + var banPersonRes: ApiState by mutableStateOf(ApiState.Empty) + private set + + fun banOrUnbanPerson( + personId: PersonId, + ban: Boolean, + removeData: Boolean? = null, + reason: String, + expireDays: Int? = null, + ctx: Context, + focusManager: FocusManager, + onSuccess: (PersonView) -> Unit, + ) { + viewModelScope.launch { + val form = + BanPerson( + person_id = personId, + ban = ban, + remove_data = removeData, + reason = reason, + expires = futureDaysToUnixTime(expireDays), + ) + + banPersonRes = ApiState.Loading + banPersonRes = API.getInstance().banPerson(form).toApiState() + + when (val res = banPersonRes) { + is ApiState.Failure -> { + Log.d("banPerson", "failed", res.msg) + apiErrorToast(msg = res.msg, ctx = ctx) + } + + is ApiState.Success -> { + val personView = res.data.person_view + val personNameShown = personNameShown(personView.person, true) + val message = + if (ban) { + if (expireDays !== null) { + ctx.getString(R.string.person_banned_for_x_days, personNameShown, expireDays) + } else { + ctx.getString(R.string.person_banned, personNameShown) + } + } else { + ctx.getString(R.string.person_unbanned, personNameShown) + } + Toast.makeText(ctx, message, Toast.LENGTH_SHORT).show() + + focusManager.clearFocus() + onSuccess(personView) + } + else -> {} + } + } + } +} diff --git a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt index db6474214..1a5a46a52 100644 --- a/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PersonProfileViewModel.kt @@ -14,8 +14,13 @@ import com.jerboa.JerboaAppState import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.findAndUpdateComment +import com.jerboa.findAndUpdateCommentCreator +import com.jerboa.findAndUpdateCommentCreatorBannedFromCommunity import com.jerboa.findAndUpdatePost +import com.jerboa.findAndUpdatePostCreator +import com.jerboa.findAndUpdatePostCreatorBannedFromCommunity import com.jerboa.getDeduplicateMerge import com.jerboa.showBlockCommunityToast import com.jerboa.showBlockPersonToast @@ -118,7 +123,8 @@ class PersonProfileViewModel(personArg: Either, savedMode: Boo val appendedPosts = getDeduplicateMerge(oldRes.data.posts, newRes.data.posts) { it.post.id } val appendedComments = getDeduplicateMerge( - oldRes.data.comments, newRes.data.comments, + oldRes.data.comments, + newRes.data.comments, ) { it.comment.id } ApiState.Success( @@ -260,6 +266,52 @@ class PersonProfileViewModel(personArg: Either, savedMode: Boo } } + fun updateBanned(personView: PersonView) { + when (val existing = personDetailsRes) { + is ApiState.Success -> { + val data = existing.data + + // Replace all the post creators + val posts = findAndUpdatePostCreator(posts = data.posts, person = personView.person) + + // Replace all the comment creators + val comments = findAndUpdateCommentCreator(comments = data.comments, person = personView.person) + + val newRes = ApiState.Success(data.copy(person_view = personView, posts = posts, comments = comments)) + personDetailsRes = newRes + } + + else -> {} + } + } + + fun updateBannedFromCommunity(banData: BanFromCommunityData) { + when (val existing = personDetailsRes) { + is ApiState.Success -> { + val data = existing.data + + // Replace all the post creators + val posts = + findAndUpdatePostCreatorBannedFromCommunity( + posts = data.posts, + banData = banData, + ) + + // Replace all the comment creators + val comments = + findAndUpdateCommentCreatorBannedFromCommunity( + comments = data.comments, + banData = banData, + ) + + val newRes = ApiState.Success(data.copy(posts = posts, comments = comments)) + personDetailsRes = newRes + } + + else -> {} + } + } + fun updateComment(commentView: CommentView) { when (val existing = personDetailsRes) { is ApiState.Success -> { diff --git a/app/src/main/java/com/jerboa/model/PostViewModel.kt b/app/src/main/java/com/jerboa/model/PostViewModel.kt index a721a9781..7f7da2046 100644 --- a/app/src/main/java/com/jerboa/model/PostViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostViewModel.kt @@ -14,7 +14,10 @@ import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState import com.jerboa.appendData +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.findAndUpdateComment +import com.jerboa.findAndUpdateCommentCreator +import com.jerboa.findAndUpdateCommentCreatorBannedFromCommunity import com.jerboa.showBlockPersonToast import it.vercruysse.lemmyapi.dto.CommentSortType import it.vercruysse.lemmyapi.dto.ListingType @@ -106,9 +109,7 @@ class PostViewModel(val id: Either) : ViewModel() { type_ = ListingType.All, ) - val moreComments = API.getInstance().getComments(commentsForm).toApiState() - - when (moreComments) { + when (val moreComments = API.getInstance().getComments(commentsForm).toApiState()) { is ApiState.Success -> { // Remove the first comment, since it is a parent // Actually since a bug in 18.3 that is no longer a guarantee @@ -267,6 +268,62 @@ class PostViewModel(val id: Either) : ViewModel() { } } + fun updateBanned(personView: PersonView) { + when (val existing = postRes) { + is ApiState.Success -> { + val data = existing.data + // Only update the creator if it matches + val newPostView = + if (data.post_view.creator.id == personView.person.id) { + data.post_view.copy(creator = personView.person) + } else { + data.post_view + } + val newRes = ApiState.Success(data.copy(post_view = newPostView)) + postRes = newRes + } + else -> {} + } + + // Also do all the comments + when (val existing = commentsRes) { + is ApiState.Success -> { + val comments = findAndUpdateCommentCreator(existing.data.comments, personView.person) + val newRes = ApiState.Success(existing.data.copy(comments = comments)) + commentsRes = newRes + } + else -> {} + } + } + + fun updateBannedFromCommunity(banData: BanFromCommunityData) { + when (val existing = postRes) { + is ApiState.Success -> { + val data = existing.data + // Only update the creator if it matches + val newPostView = + if (data.post_view.creator.id == banData.person.id && data.post_view.community.id == banData.community.id) { + data.post_view.copy(creator_banned_from_community = banData.banned) + } else { + data.post_view + } + val newRes = ApiState.Success(data.copy(post_view = newPostView)) + postRes = newRes + } + else -> {} + } + + // Also do all the comments + when (val existing = commentsRes) { + is ApiState.Success -> { + val comments = findAndUpdateCommentCreatorBannedFromCommunity(existing.data.comments, banData) + val newRes = ApiState.Success(existing.data.copy(comments = comments)) + commentsRes = newRes + } + else -> {} + } + } + companion object { class Factory( private val id: Either, diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index 5cc2ce12b..3251a9ca7 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -12,9 +12,12 @@ import com.jerboa.JerboaAppState import com.jerboa.api.API import com.jerboa.api.ApiState import com.jerboa.api.toApiState +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.AnonAccount import com.jerboa.db.repository.AccountRepository import com.jerboa.findAndUpdatePost +import com.jerboa.findAndUpdatePostCreator +import com.jerboa.findAndUpdatePostCreatorBannedFromCommunity import com.jerboa.mergePosts import com.jerboa.toEnumSafe import it.vercruysse.lemmyapi.dto.ListingType @@ -25,6 +28,7 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.GetPosts import it.vercruysse.lemmyapi.v0x19.datatypes.GetPostsResponse import it.vercruysse.lemmyapi.v0x19.datatypes.MarkPostAsRead import it.vercruysse.lemmyapi.v0x19.datatypes.PaginationCursor +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost import kotlinx.coroutines.flow.map @@ -130,6 +134,30 @@ open class PostsViewModel(protected val accountRepository: AccountRepository) : } } + fun updateBanned(personView: PersonView) { + when (val existing = postsRes) { + is ApiState.Success -> { + val posts = findAndUpdatePostCreator(existing.data.posts, personView.person) + val newRes = ApiState.Success(existing.data.copy(posts = posts)) + postsRes = newRes + } + + else -> {} + } + } + + fun updateBannedFromCommunity(banData: BanFromCommunityData) { + when (val existing = postsRes) { + is ApiState.Success -> { + val posts = findAndUpdatePostCreatorBannedFromCommunity(existing.data.posts, banData) + val newRes = ApiState.Success(existing.data.copy(posts = posts)) + postsRes = newRes + } + + else -> {} + } + } + fun resetPosts() { resetPage() getPosts( diff --git a/app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityActivity.kt b/app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityActivity.kt new file mode 100644 index 000000000..72f0a53c6 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/ban/BanFromCommunityActivity.kt @@ -0,0 +1,132 @@ +package com.jerboa.ui.components.ban + +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.communityNameShown +import com.jerboa.datatypes.BanFromCommunityData +import com.jerboa.db.entity.isAnon +import com.jerboa.model.AccountViewModel +import com.jerboa.model.BanFromCommunityViewModel +import com.jerboa.personNameShown +import com.jerboa.ui.components.common.ActionTopBar +import com.jerboa.ui.components.common.getCurrentAccount + +object BanFromCommunityReturn { + const val BAN_DATA_VIEW = "ban-from-community::return(ban-data-view)" + const val BAN_DATA_SEND = "ban-from-community::send(ban-data-view)" +} + +@Composable +fun BanFromCommunityActivity( + appState: JerboaAppState, + accountViewModel: AccountViewModel, +) { + Log.d("jerboa", "got to ban from community activity") + + val ctx = LocalContext.current + val account = getCurrentAccount(accountViewModel = accountViewModel) + + val banFromCommunityViewModel: BanFromCommunityViewModel = viewModel() + val banData = appState.getPrevReturn(key = BanFromCommunityReturn.BAN_DATA_SEND) + val person = banData.person + val community = banData.community + val banned = banData.banned + + var removeData by rememberSaveable { mutableStateOf(false) } + var permaBan by rememberSaveable { mutableStateOf(false) } + var expireDays: Int? by rememberSaveable { mutableStateOf(null) } + + var reason by rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf( + TextFieldValue(""), + ) + } + + val loading = + when (banFromCommunityViewModel.banFromCommunityRes) { + ApiState.Loading -> true + else -> false + } + + val focusManager = LocalFocusManager.current + val title = + stringResource( + if (banned) R.string.unban_person_from_community else R.string.ban_person_from_community, + personNameShown(person, true), + communityNameShown(community), + ) + + val isBan = !banned + + // Make sure the form is valid only if permaban is checked or expireDays is not null + val isValid = !isBan or permaBan or (expireDays !== null) + + Scaffold( + topBar = { + ActionTopBar( + formValid = isValid && !loading, + title = title, + loading = loading, + onActionClick = { + if (!account.isAnon()) { + banFromCommunityViewModel.banOrUnbanFromCommunity( + personId = person.id, + community = community, + ban = isBan, + removeData = if (isBan) removeData else false, + expireDays = if (!isBan or permaBan) null else expireDays, + reason = reason.text, + ctx = ctx, + focusManager = focusManager, + ) { banData -> + appState.apply { + addReturn(BanFromCommunityReturn.BAN_DATA_VIEW, banData) + navigateUp() + } + } + } + }, + actionText = R.string.form_submit, + actionIcon = Icons.Outlined.Send, + onBackClick = appState::popBackStack, + ) + }, + content = { padding -> + BanPersonBody( + reason = reason, + onReasonChange = { reason = it }, + isBan = isBan, + expireDays = expireDays, + onExpiresChange = { + expireDays = it + permaBan = false + }, + permaBan = permaBan, + onPermaBanChange = { + permaBan = it + expireDays = null + }, + removeData = removeData, + onRemoveDataChange = { removeData = it }, + isValid = isValid, + account = account, + padding = padding, + ) + }, + ) +} diff --git a/app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt b/app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt new file mode 100644 index 000000000..eebfe7424 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/ban/BanPerson.kt @@ -0,0 +1,79 @@ +package com.jerboa.ui.components.ban + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.TextFieldValue +import com.jerboa.R +import com.jerboa.db.entity.Account +import com.jerboa.ui.components.common.CheckboxField +import com.jerboa.ui.components.common.ExpiresField +import com.jerboa.ui.components.common.MarkdownTextField +import com.jerboa.ui.theme.MEDIUM_PADDING + +@Composable +fun BanPersonBody( + isBan: Boolean, + reason: TextFieldValue, + onReasonChange: (TextFieldValue) -> Unit, + expireDays: Int?, + onExpiresChange: (Int?) -> Unit, + permaBan: Boolean, + onPermaBanChange: (Boolean) -> Unit, + removeData: Boolean, + onRemoveDataChange: (Boolean) -> Unit, + isValid: Boolean, + account: Account, + padding: PaddingValues, +) { + val scrollState = rememberScrollState() + + Column( + modifier = + Modifier + .verticalScroll(scrollState) + .padding( + vertical = padding.calculateTopPadding(), + horizontal = MEDIUM_PADDING, + ) + .imePadding(), + ) { + MarkdownTextField( + text = reason, + onTextChange = onReasonChange, + account = account, + modifier = Modifier.fillMaxWidth(), + placeholder = stringResource(R.string.type_your_reason), + ) + + // Only show these fields for a ban, not an unban + if (isBan) { + if (!permaBan) { + ExpiresField( + value = expireDays, + onIntChange = onExpiresChange, + isValid = isValid, + ) + } + + CheckboxField( + label = stringResource(R.string.remove_content), + checked = removeData, + onCheckedChange = onRemoveDataChange, + ) + + CheckboxField( + label = stringResource(R.string.permaban), + checked = permaBan, + onCheckedChange = onPermaBanChange, + ) + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/ban/BanPersonActivity.kt b/app/src/main/java/com/jerboa/ui/components/ban/BanPersonActivity.kt new file mode 100644 index 000000000..0cec7f54b --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/ban/BanPersonActivity.kt @@ -0,0 +1,122 @@ +package com.jerboa.ui.components.ban + +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.BanPersonViewModel +import com.jerboa.personNameShown +import com.jerboa.ui.components.common.ActionTopBar +import com.jerboa.ui.components.common.getCurrentAccount +import it.vercruysse.lemmyapi.v0x19.datatypes.Person + +object BanPersonReturn { + const val PERSON_VIEW = "ban-person::return(person-view)" + const val PERSON_SEND = "ban-person::send(person-view)" +} + +@Composable +fun BanPersonActivity( + appState: JerboaAppState, + accountViewModel: AccountViewModel, +) { + Log.d("jerboa", "got to ban person activity") + + val ctx = LocalContext.current + val account = getCurrentAccount(accountViewModel = accountViewModel) + + val banPersonViewModel: BanPersonViewModel = viewModel() + val person = appState.getPrevReturn(key = BanPersonReturn.PERSON_SEND) + + var removeData by rememberSaveable { mutableStateOf(false) } + var permaBan by rememberSaveable { mutableStateOf(false) } + var expireDays: Int? by rememberSaveable { mutableStateOf(null) } + + var reason by rememberSaveable(stateSaver = TextFieldValue.Saver) { + mutableStateOf( + TextFieldValue(""), + ) + } + + val loading = + when (banPersonViewModel.banPersonRes) { + ApiState.Loading -> true + else -> false + } + + val focusManager = LocalFocusManager.current + val title = stringResource(if (person.banned) R.string.unban_person else R.string.ban_person, personNameShown(person, true)) + + val isBan = !person.banned + + // Make sure the form is valid only if permaban is checked or expireDays is not null + val isValid = !isBan or permaBan or (expireDays !== null) + + Scaffold( + topBar = { + ActionTopBar( + formValid = isValid && !loading, + title = title, + loading = loading, + onActionClick = { + if (!account.isAnon()) { + banPersonViewModel.banOrUnbanPerson( + personId = person.id, + ban = isBan, + removeData = if (isBan) removeData else false, + expireDays = if (!isBan or permaBan) null else expireDays, + reason = reason.text, + ctx = ctx, + focusManager = focusManager, + ) { personView -> + appState.apply { + addReturn(BanPersonReturn.PERSON_VIEW, personView) + navigateUp() + } + } + } + }, + actionText = R.string.form_submit, + actionIcon = Icons.Outlined.Send, + onBackClick = appState::popBackStack, + ) + }, + content = { padding -> + BanPersonBody( + reason = reason, + onReasonChange = { reason = it }, + isBan = isBan, + expireDays = expireDays, + onExpiresChange = { + expireDays = it + permaBan = false + }, + permaBan = permaBan, + onPermaBanChange = { + permaBan = it + expireDays = null + }, + removeData = removeData, + onRemoveDataChange = { removeData = it }, + isValid = isValid, + account = account, + padding = padding, + ) + }, + ) +} diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt index 5c0aede12..01a7feaf4 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNode.kt @@ -52,6 +52,7 @@ import com.jerboa.buildCommentsTree import com.jerboa.calculateCommentOffset import com.jerboa.calculateNewInstantScores import com.jerboa.canMod +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.sampleCommentView import com.jerboa.datatypes.sampleCommunity import com.jerboa.datatypes.samplePost @@ -197,6 +198,8 @@ fun LazyListScope.commentNodeItem( onPostClick: (postId: Int) -> Unit, onReportClick: (commentView: CommentView) -> Unit, onRemoveClick: (commentView: CommentView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onCommentLinkClick: (commentView: CommentView) -> Unit, onBlockCreatorClick: (creator: Person) -> Unit, onFetchChildrenClick: (commentView: CommentView) -> Unit, @@ -355,6 +358,8 @@ fun LazyListScope.commentNodeItem( onSaveClick = onSaveClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onCommentLinkClick = onCommentLinkClick, onPersonClick = onPersonClick, onBlockCreatorClick = onBlockCreatorClick, @@ -407,6 +412,8 @@ fun LazyListScope.commentNodeItem( onDeleteCommentClick = onDeleteCommentClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onPersonClick = onPersonClick, @@ -454,6 +461,8 @@ fun LazyListScope.missingCommentNodeItem( onPostClick: (postId: Int) -> Unit, onReportClick: (commentView: CommentView) -> Unit, onRemoveClick: (commentView: CommentView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onCommentLinkClick: (commentView: CommentView) -> Unit, onBlockCreatorClick: (creator: Person) -> Unit, onFetchChildrenClick: (commentView: CommentView) -> Unit, @@ -551,6 +560,8 @@ fun LazyListScope.missingCommentNodeItem( onDeleteCommentClick = onDeleteCommentClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onPersonClick = onPersonClick, @@ -680,6 +691,8 @@ fun CommentFooterLine( onDeleteCommentClick: (commentView: CommentView) -> Unit, onReportClick: (commentView: CommentView) -> Unit, onRemoveClick: (commentView: CommentView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onCommentLinkClick: (commentView: CommentView) -> Unit, onBlockCreatorClick: (creator: Person) -> Unit, onPersonClick: (personId: Int) -> Unit, @@ -692,14 +705,12 @@ fun CommentFooterLine( var showMoreOptions by remember { mutableStateOf(false) } val canMod = - remember { - canMod( - creatorId = commentView.comment.creator_id, - admins = admins, - moderators = moderators, - myId = account.id, - ) - } + canMod( + creatorId = commentView.comment.creator_id, + admins = admins, + moderators = moderators, + myId = account.id, + ) if (showMoreOptions) { CommentOptionsDropdown( @@ -710,6 +721,8 @@ fun CommentFooterLine( onDeleteCommentClick = onDeleteCommentClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onBlockCreatorClick = onBlockCreatorClick, onCommentLinkClick = onCommentLinkClick, onPersonClick = onPersonClick, @@ -824,6 +837,8 @@ fun CommentNodesPreview() { onDeleteCommentClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onCommentLinkClick = {}, onPersonClick = {}, onHeaderClick = {}, diff --git a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt index e6fe337ba..ba90215f9 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/CommentNodes.kt @@ -11,6 +11,7 @@ import androidx.compose.ui.unit.dp import com.jerboa.CommentNode import com.jerboa.CommentNodeData import com.jerboa.MissingCommentNode +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.Account import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView import it.vercruysse.lemmyapi.v0x19.datatypes.Community @@ -41,6 +42,8 @@ fun CommentNodes( onDeleteCommentClick: (commentView: CommentView) -> Unit, onReportClick: (commentView: CommentView) -> Unit, onRemoveClick: (commentView: CommentView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onCommentLinkClick: (commentView: CommentView) -> Unit, onFetchChildrenClick: (commentView: CommentView) -> Unit, onPersonClick: (personId: Int) -> Unit, @@ -80,6 +83,8 @@ fun CommentNodes( onDeleteCommentClick = onDeleteCommentClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onPersonClick = onPersonClick, @@ -124,6 +129,8 @@ fun LazyListScope.commentNodeItems( onDeleteCommentClick: (commentView: CommentView) -> Unit, onReportClick: (commentView: CommentView) -> Unit, onRemoveClick: (commentView: CommentView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onCommentLinkClick: (commentView: CommentView) -> Unit, onFetchChildrenClick: (commentView: CommentView) -> Unit, onPersonClick: (personId: Int) -> Unit, @@ -171,6 +178,8 @@ fun LazyListScope.commentNodeItems( onDeleteCommentClick = onDeleteCommentClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onBlockCreatorClick = onBlockCreatorClick, @@ -211,6 +220,8 @@ fun LazyListScope.commentNodeItems( onDeleteCommentClick = onDeleteCommentClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onBlockCreatorClick = onBlockCreatorClick, 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 510113a6e..74ce31e36 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 @@ -10,7 +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.GppBad import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Restore @@ -22,6 +22,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import com.jerboa.R import com.jerboa.copyToClipboard +import com.jerboa.datatypes.BanFromCommunityData +import com.jerboa.ui.components.common.BanFromCommunityPopupMenuItem +import com.jerboa.ui.components.common.BanPersonPopupMenuItem import com.jerboa.ui.components.common.PopupMenuItem import com.jerboa.util.cascade.CascadeCenteredDropdownMenu import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView @@ -40,6 +43,8 @@ fun CommentOptionsDropdown( onBlockCreatorClick: (Person) -> Unit, onReportClick: (CommentView) -> Unit, onRemoveClick: (CommentView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, isCreator: Boolean, canMod: Boolean, viewSource: Boolean, @@ -178,7 +183,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.Gavel) + Pair(stringResource(R.string.remove_comment), Icons.Outlined.GppBad) } PopupMenuItem( @@ -189,6 +194,20 @@ fun CommentOptionsDropdown( onRemoveClick(commentView) }, ) + BanPersonPopupMenuItem(commentView.creator, onDismissRequest, onBanPersonClick) + + // Only show ban from community button if its a local community + if (commentView.community.local) { + BanFromCommunityPopupMenuItem( + BanFromCommunityData( + person = commentView.creator, + community = commentView.community, + banned = commentView.creator_banned_from_community, + ), + onDismissRequest, + onBanFromCommunityClick, + ) + } } } } 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 4885b9362..e460a89a8 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 @@ -119,14 +119,12 @@ fun CommentMentionNodeFooterLine( var showMoreOptions by remember { mutableStateOf(false) } val canMod = - remember { - canMod( - creatorId = personMentionView.comment.creator_id, - admins = admins, - moderators = moderators, - myId = account.id, - ) - } + canMod( + creatorId = personMentionView.comment.creator_id, + admins = admins, + moderators = moderators, + myId = account.id, + ) if (showMoreOptions) { CommentMentionsOptionsDropdown( diff --git a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt index bc63a0d0f..fa5af280f 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/InputFields.kt @@ -30,6 +30,7 @@ import androidx.compose.material.icons.outlined.Subscript import androidx.compose.material.icons.outlined.Superscript import androidx.compose.material.icons.outlined.Title import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -48,6 +49,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester @@ -61,6 +63,7 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.getSelectedText import androidx.compose.ui.tooling.preview.Preview +import androidx.core.text.isDigitsOnly import com.jerboa.R import com.jerboa.api.API import com.jerboa.appendMarkdownImage @@ -641,6 +644,50 @@ fun MyMarkdownText( ) } +@Composable +fun CheckboxField( + label: String, + checked: Boolean, + onCheckedChange: (checked: Boolean) -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth(), + ) { + Text( + text = label, + ) + Checkbox( + checked = checked, + onCheckedChange = onCheckedChange, + ) + } +} + +@Composable +fun ExpiresField( + value: Int?, + onIntChange: (Int?) -> Unit, + isValid: Boolean, +) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = value?.toString() ?: "", + onValueChange = { + if (it.isEmpty()) { + onIntChange(null) + } else if (it.isDigitsOnly() && it.toInt() > 0) { + onIntChange(it.toIntOrNull()) + } + }, + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + placeholder = { Text(text = stringResource(R.string.days_until_expiration)) }, + isError = !isValid, + ) +} + fun String.insert( index: Int, string: String, diff --git a/app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt b/app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt new file mode 100644 index 000000000..feaa5f590 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/common/PopupItems.kt @@ -0,0 +1,61 @@ +package com.jerboa.ui.components.common + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Gavel +import androidx.compose.material.icons.outlined.Restore +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.jerboa.R +import com.jerboa.communityNameShown +import com.jerboa.datatypes.BanFromCommunityData +import com.jerboa.personNameShown +import it.vercruysse.lemmyapi.v0x19.datatypes.Person + +@Composable +fun BanPersonPopupMenuItem( + person: Person, + onDismissRequest: () -> Unit, + onBanPersonClick: (person: Person) -> Unit, +) { + val personNameShown = personNameShown(person, true) + val (banText, banIcon) = + if (person.banned) { + Pair(stringResource(R.string.unban_person, personNameShown), Icons.Outlined.Restore) + } else { + Pair(stringResource(R.string.ban_person, personNameShown), Icons.Outlined.Gavel) + } + + PopupMenuItem( + text = banText, + icon = banIcon, + onClick = { + onDismissRequest() + onBanPersonClick(person) + }, + ) +} + +@Composable +fun BanFromCommunityPopupMenuItem( + banData: BanFromCommunityData, + onDismissRequest: () -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, +) { + val personNameShown = personNameShown(banData.person, true) + val communityNameShown = communityNameShown(banData.community) + val (banText, banIcon) = + if (banData.banned) { + Pair(stringResource(R.string.unban_person_from_community, personNameShown, communityNameShown), Icons.Outlined.Restore) + } else { + Pair(stringResource(R.string.ban_person_from_community, personNameShown, communityNameShown), Icons.Outlined.Gavel) + } + + PopupMenuItem( + text = banText, + icon = banIcon, + onClick = { + onDismissRequest() + onBanFromCommunityClick(banData) + }, + ) +} 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 959e8c76d..49e9c2b09 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 @@ -34,6 +34,8 @@ object Route { const val POST_REMOVE = "postRemove" const val PRIVATE_MESSAGE_REPLY = "privateMessageReply" const val COMMENT_REMOVE = "commentRemove" + const val BAN_PERSON = "banPerson" + const val BAN_FROM_COMMUNITY = "banFromCommunity" val CREATE_PRIVATE_MESSAGE = CreatePrivateMessageArgs.route val COMMENT_REPORT = CommentReportArgs.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 6b5ae49b0..28ae676db 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 @@ -33,6 +33,7 @@ import com.jerboa.JerboaAppState import com.jerboa.R import com.jerboa.VoteType import com.jerboa.api.ApiState +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.isAnon import com.jerboa.feat.BlurTypes import com.jerboa.feat.doIfReadyElseDisplayInfo @@ -44,6 +45,8 @@ import com.jerboa.model.CommunityViewModel import com.jerboa.model.SiteViewModel import com.jerboa.newVote import com.jerboa.scrollToTop +import com.jerboa.ui.components.ban.BanFromCommunityReturn +import com.jerboa.ui.components.ban.BanPersonReturn import com.jerboa.ui.components.common.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText import com.jerboa.ui.components.common.JerboaPullRefreshIndicator @@ -64,6 +67,7 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.CreatePostLike import it.vercruysse.lemmyapi.v0x19.datatypes.DeletePost import it.vercruysse.lemmyapi.v0x19.datatypes.FollowCommunity import it.vercruysse.lemmyapi.v0x19.datatypes.MarkPostAsRead +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost import kotlinx.collections.immutable.toImmutableList @@ -99,6 +103,8 @@ fun CommunityActivity( appState.ConsumeReturn(PostEditReturn.POST_VIEW, communityViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, communityViewModel::updatePost) appState.ConsumeReturn(PostViewReturn.POST_VIEW, communityViewModel::updatePost) + appState.ConsumeReturn(BanPersonReturn.PERSON_VIEW, communityViewModel::updateBanned) + appState.ConsumeReturn(BanFromCommunityReturn.BAN_DATA_VIEW, communityViewModel::updateBannedFromCommunity) val pullRefreshState = rememberPullRefreshState( @@ -330,6 +336,12 @@ fun CommunityActivity( onRemoveClick = { pv -> appState.toPostRemove(post = pv.post) }, + onBanPersonClick = { p -> + appState.toBanPerson(p) + }, + onBanFromCommunityClick = { d -> + appState.toBanFromCommunity(banData = d) + }, 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 e2308be62..74eacfa88 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 @@ -39,6 +39,7 @@ import com.jerboa.JerboaAppState import com.jerboa.R import com.jerboa.VoteType import com.jerboa.api.ApiState +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.db.entity.Account import com.jerboa.db.entity.isAnon import com.jerboa.db.entity.isReady @@ -49,6 +50,8 @@ import com.jerboa.model.HomeViewModel import com.jerboa.model.SiteViewModel import com.jerboa.newVote import com.jerboa.scrollToTop +import com.jerboa.ui.components.ban.BanFromCommunityReturn +import com.jerboa.ui.components.ban.BanPersonReturn import com.jerboa.ui.components.common.ApiEmptyText import com.jerboa.ui.components.common.ApiErrorText import com.jerboa.ui.components.common.JerboaPullRefreshIndicator @@ -66,6 +69,7 @@ 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 +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost import it.vercruysse.lemmyapi.v0x19.datatypes.Tagline @@ -107,6 +111,8 @@ fun HomeActivity( appState.ConsumeReturn(PostEditReturn.POST_VIEW, homeViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, homeViewModel::updatePost) appState.ConsumeReturn(PostViewReturn.POST_VIEW, homeViewModel::updatePost) + appState.ConsumeReturn(BanPersonReturn.PERSON_VIEW, homeViewModel::updateBanned) + appState.ConsumeReturn(BanFromCommunityReturn.BAN_DATA_VIEW, homeViewModel::updateBannedFromCommunity) LaunchedEffect(account) { if (!account.isAnon() && !account.isReady()) { @@ -343,6 +349,12 @@ fun MainPostListingsContent( onRemoveClick = { pv -> appState.toPostRemove(post = pv.post) }, + onBanPersonClick = { p -> + appState.toBanPerson(p) + }, + onBanFromCommunityClick = { d -> + appState.toBanFromCommunity(banData = d) + }, onCommunityClick = { community -> appState.toCommunity(id = community.id) }, 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 c8391941e..a854b1ec1 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 @@ -48,6 +48,7 @@ import com.jerboa.R import com.jerboa.VoteType import com.jerboa.api.ApiState import com.jerboa.commentsToFlatNodes +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.getDisplayName import com.jerboa.datatypes.getLocalizedStringForUserTab import com.jerboa.db.entity.Account @@ -61,6 +62,8 @@ import com.jerboa.model.ReplyItem import com.jerboa.model.SiteViewModel import com.jerboa.newVote import com.jerboa.scrollToTop +import com.jerboa.ui.components.ban.BanFromCommunityReturn +import com.jerboa.ui.components.ban.BanPersonReturn import com.jerboa.ui.components.comment.CommentNodes import com.jerboa.ui.components.comment.edit.CommentEditReturn import com.jerboa.ui.components.comment.reply.CommentReplyReturn @@ -92,6 +95,7 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.DeletePost import it.vercruysse.lemmyapi.v0x19.datatypes.GetPersonDetails import it.vercruysse.lemmyapi.v0x19.datatypes.MarkPostAsRead import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import it.vercruysse.lemmyapi.v0x19.datatypes.SaveComment import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost @@ -134,6 +138,8 @@ fun PersonProfileActivity( appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, personProfileViewModel::updatePost) appState.ConsumeReturn(CommentEditReturn.COMMENT_VIEW, personProfileViewModel::updateComment) appState.ConsumeReturn(CommentRemoveReturn.COMMENT_VIEW, personProfileViewModel::updateComment) + appState.ConsumeReturn(BanPersonReturn.PERSON_VIEW, personProfileViewModel::updateBanned) + appState.ConsumeReturn(BanFromCommunityReturn.BAN_DATA_VIEW, personProfileViewModel::updateBannedFromCommunity) appState.ConsumeReturn(CommentReplyReturn.COMMENT_VIEW) { cv -> when (val res = personProfileViewModel.personDetailsRes) { @@ -544,6 +550,12 @@ fun UserTabs( onRemoveClick = { pv -> appState.toPostRemove(post = pv.post) }, + onBanPersonClick = { p -> + appState.toBanPerson(p) + }, + onBanFromCommunityClick = { d -> + appState.toBanFromCommunity(banData = d) + }, onCommunityClick = { community -> appState.toCommunity(id = community.id) }, @@ -759,6 +771,12 @@ fun UserTabs( onRemoveClick = { cv -> appState.toCommentRemove(comment = cv.comment) }, + onBanPersonClick = { p -> + appState.toBanPerson(p) + }, + onBanFromCommunityClick = { d -> + appState.toBanFromCommunity(banData = d) + }, onCommentLinkClick = { cv -> appState.toComment(id = cv.comment.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 5b941cd83..4c6e78b3d 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 @@ -57,6 +57,7 @@ import com.jerboa.R import com.jerboa.VoteType import com.jerboa.api.ApiState import com.jerboa.buildCommentsTree +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.getLocalizedCommentSortTypeName import com.jerboa.db.entity.isAnon import com.jerboa.feat.doIfReadyElseDisplayInfo @@ -69,6 +70,8 @@ import com.jerboa.model.SiteViewModel import com.jerboa.newVote import com.jerboa.scrollToNextParentComment import com.jerboa.scrollToPreviousParentComment +import com.jerboa.ui.components.ban.BanFromCommunityReturn +import com.jerboa.ui.components.ban.BanPersonReturn import com.jerboa.ui.components.comment.ShowCommentContextButtons import com.jerboa.ui.components.comment.commentNodeItems import com.jerboa.ui.components.comment.edit.CommentEditReturn @@ -145,6 +148,8 @@ fun PostActivity( appState.ConsumeReturn(CommentReplyReturn.COMMENT_VIEW, postViewModel::appendComment) appState.ConsumeReturn(CommentEditReturn.COMMENT_VIEW, postViewModel::updateComment) appState.ConsumeReturn(CommentRemoveReturn.COMMENT_VIEW, postViewModel::updateComment) + appState.ConsumeReturn(BanPersonReturn.PERSON_VIEW, postViewModel::updateBanned) + appState.ConsumeReturn(BanFromCommunityReturn.BAN_DATA_VIEW, postViewModel::updateBannedFromCommunity) val onClickSortType = { commentSortType: CommentSortType -> postViewModel.updateSortType(commentSortType) @@ -395,6 +400,12 @@ fun PostActivity( onRemoveClick = { pv -> appState.toPostRemove(post = pv.post) }, + onBanPersonClick = { p -> + appState.toBanPerson(person = p) + }, + onBanFromCommunityClick = { d -> + appState.toBanFromCommunity(banData = d) + }, onPersonClick = appState::toProfile, showReply = true, // Do nothing fullBody = true, @@ -590,6 +601,12 @@ fun PostActivity( onRemoveClick = { cv -> appState.toCommentRemove(comment = cv.comment) }, + onBanPersonClick = { p -> + appState.toBanPerson(p) + }, + onBanFromCommunityClick = { d -> + appState.toBanFromCommunity(banData = d) + }, onCommentLinkClick = { cv -> appState.toComment(id = cv.comment.id) }, 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 1abb5aa18..aa26d07cd 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 @@ -64,6 +64,7 @@ import com.jerboa.R import com.jerboa.VoteType import com.jerboa.calculateNewInstantScores import com.jerboa.canMod +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.sampleImagePostView import com.jerboa.datatypes.sampleLinkNoThumbnailPostView import com.jerboa.datatypes.sampleLinkPostView @@ -554,6 +555,8 @@ fun PostFooterLine( onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, onRemoveClick: (postView: PostView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: Int) -> Unit, onViewSourceClick: () -> Unit, @@ -570,14 +573,12 @@ fun PostFooterLine( var showMoreOptions by remember { mutableStateOf(false) } val canMod = - remember { - canMod( - creatorId = postView.creator.id, - admins = admins, - moderators = moderators, - myId = account.id, - ) - } + canMod( + creatorId = postView.creator.id, + admins = admins, + moderators = moderators, + myId = account.id, + ) if (showMoreOptions) { PostOptionsDropdown( @@ -589,6 +590,8 @@ fun PostFooterLine( onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onViewSourceClick = onViewSourceClick, isCreator = account.id == postView.creator.id, canMod = canMod, @@ -782,6 +785,8 @@ fun PostFooterLinePreview() { onDeletePostClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onCommunityClick = {}, onPersonClick = {}, onViewSourceClick = {}, @@ -814,6 +819,8 @@ fun PreviewPostListingCard() { onDeletePostClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -849,6 +856,8 @@ fun PreviewLinkPostListing() { onDeletePostClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -884,6 +893,8 @@ fun PreviewImagePostListingCard() { onDeletePostClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -919,6 +930,8 @@ fun PreviewImagePostListingSmallCard() { onDeletePostClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -954,6 +967,8 @@ fun PreviewLinkNoThumbnailPostListing() { onDeletePostClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -987,6 +1002,8 @@ fun PostListing( onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, onRemoveClick: (postView: PostView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onPersonClick: (personId: Int) -> Unit, showReply: Boolean = false, showCommunityName: Boolean = true, @@ -1049,6 +1066,8 @@ fun PostListing( onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onPersonClick = onPersonClick, onViewSourceClick = { viewSource = !viewSource @@ -1101,6 +1120,8 @@ fun PostListing( onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onPersonClick = onPersonClick, onViewSourceClick = { viewSource = !viewSource @@ -1496,6 +1517,8 @@ fun PostListingCard( onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, onRemoveClick: (postView: PostView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onPersonClick: (personId: Int) -> Unit, onViewSourceClick: () -> Unit, viewSource: Boolean, @@ -1572,6 +1595,8 @@ fun PostListingCard( onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, 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 6d259d9ac..5e4e04f0b 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 @@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.jerboa.JerboaAppState import com.jerboa.PostViewMode +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.datatypes.sampleLinkPostView import com.jerboa.datatypes.samplePostView import com.jerboa.db.entity.Account @@ -31,6 +32,7 @@ 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.Person import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import kotlinx.collections.immutable.ImmutableList @@ -50,6 +52,8 @@ fun PostListings( onDeletePostClick: (postView: PostView) -> Unit, onReportClick: (postView: PostView) -> Unit, onRemoveClick: (postView: PostView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: Int) -> Unit, loadMorePosts: () -> Unit, @@ -105,6 +109,8 @@ fun PostListings( onDeletePostClick = onDeletePostClick, onReportClick = onReportClick, onRemoveClick = onRemoveClick, + onBanPersonClick = onBanPersonClick, + onBanFromCommunityClick = onBanFromCommunityClick, onPersonClick = onPersonClick, showCommunityName = showCommunityName, fullBody = false, @@ -170,6 +176,8 @@ fun PreviewPostListings() { onDeletePostClick = {}, onReportClick = {}, onRemoveClick = {}, + onBanPersonClick = {}, + onBanFromCommunityClick = {}, onCommunityClick = {}, onPersonClick = {}, loadMorePosts = {}, diff --git a/app/src/main/java/com/jerboa/ui/components/post/composables/PostComposables.kt b/app/src/main/java/com/jerboa/ui/components/post/composables/PostComposables.kt index 7341393a6..6f6c78d2c 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/composables/PostComposables.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/composables/PostComposables.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.imePadding @@ -16,7 +15,6 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ArrowDropDown -import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -36,6 +34,7 @@ import com.jerboa.datatypes.sampleCommunity import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.isImage +import com.jerboa.ui.components.common.CheckboxField import com.jerboa.ui.components.common.CircularIcon import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.common.PickImage @@ -162,10 +161,7 @@ fun CreateEditPostBody( /** * Checkbox to mark post NSFW */ - CheckboxIsNsfw( - checked = isNsfw, - onCheckedChange = onIsNsfwChange, - ) + CheckboxField(label = stringResource(R.string.create_post_tag_nsfw), checked = isNsfw, onCheckedChange = onIsNsfwChange) } } @@ -228,26 +224,6 @@ fun PostCommunitySelector( } } -@Composable -fun CheckboxIsNsfw( - checked: Boolean, - onCheckedChange: (checked: Boolean) -> Unit, -) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxWidth(), - ) { - Text( - text = stringResource(R.string.create_post_tag_nsfw), - ) - Checkbox( - checked = checked, - onCheckedChange = onCheckedChange, - ) - } -} - @Preview @Composable fun CreatePostBodyPreview() { 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 8e07c81da..202d4ee42 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,7 +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.GppBad import androidx.compose.material.icons.outlined.Link import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Restore @@ -26,9 +26,12 @@ import com.jerboa.R import com.jerboa.api.API import com.jerboa.communityNameShown import com.jerboa.copyToClipboard +import com.jerboa.datatypes.BanFromCommunityData import com.jerboa.feat.shareLink import com.jerboa.feat.shareMedia import com.jerboa.isMedia +import com.jerboa.ui.components.common.BanFromCommunityPopupMenuItem +import com.jerboa.ui.components.common.BanPersonPopupMenuItem import com.jerboa.ui.components.common.PopupMenuItem import com.jerboa.util.blockCommunity import com.jerboa.util.blockPerson @@ -39,6 +42,7 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.BlockCommunity import it.vercruysse.lemmyapi.v0x19.datatypes.BlockInstance import it.vercruysse.lemmyapi.v0x19.datatypes.BlockPerson import it.vercruysse.lemmyapi.v0x19.datatypes.Community +import it.vercruysse.lemmyapi.v0x19.datatypes.Person import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import kotlinx.coroutines.CoroutineScope @@ -56,6 +60,8 @@ fun PostOptionsDropdown( onDeletePostClick: (PostView) -> Unit, onReportClick: (PostView) -> Unit, onRemoveClick: (PostView) -> Unit, + onBanPersonClick: (person: Person) -> Unit, + onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onViewSourceClick: () -> Unit, isCreator: Boolean, canMod: Boolean, @@ -404,11 +410,12 @@ fun PostOptionsDropdown( 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) + Pair(stringResource(R.string.remove_post), Icons.Outlined.GppBad) } PopupMenuItem( @@ -419,6 +426,20 @@ fun PostOptionsDropdown( onRemoveClick(postView) }, ) + BanPersonPopupMenuItem(postView.creator, onDismissRequest, onBanPersonClick) + + // Only show ban from community button if its a local community + if (postView.community.local) { + BanFromCommunityPopupMenuItem( + BanFromCommunityData( + person = postView.creator, + community = postView.community, + banned = postView.creator_banned_from_community, + ), + onDismissRequest, + onBanFromCommunityClick, + ) + } } } } diff --git a/app/src/main/java/com/jerboa/ui/theme/Color.kt b/app/src/main/java/com/jerboa/ui/theme/Color.kt index c9cb47dc0..7f6e5c82f 100644 --- a/app/src/main/java/com/jerboa/ui/theme/Color.kt +++ b/app/src/main/java/com/jerboa/ui/theme/Color.kt @@ -1236,7 +1236,6 @@ fun dracula(): Pair { val md_theme_light_inverseOnSurface = Color(0xFFF3F0F4) val md_theme_light_inverseSurface = Color(0xFF303034) val md_theme_light_inversePrimary = Color(0xFFB9C3FF) - val md_theme_light_shadow = Color(0xFF000000) val md_theme_light_surfaceTint = Color(0xFF4758A9) val md_theme_light_outlineVariant = Color(0xFFC6C5D0) val md_theme_light_scrim = Color(0xFF000000) @@ -1267,7 +1266,6 @@ fun dracula(): Pair { val md_theme_dark_inverseOnSurface = Color(0xFF1B1B1F) val md_theme_dark_inverseSurface = Color(0xFFE4E1E6) val md_theme_dark_inversePrimary = Color(0xFF4758A9) - val md_theme_dark_shadow = Color(0xFF000000) val md_theme_dark_surfaceTint = Color(0xFFB9C3FF) val md_theme_dark_outlineVariant = Color(0xFF45464F) val md_theme_dark_scrim = Color(0xFF000000) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 72b27637f..fbb114082 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -413,4 +413,17 @@ Remove Post Restore Post Type your reason + Banned %1$s + Banned %1$s for %2$d days + Banned %1$s from %2$s + Banned %1$s from %2$s for %3$d days + Unbanned %1$s + Unbanned %1$s from %2$s + Ban %1$s + Ban %1$s from %2$s + Unban %1$s + Unban %1$s from %2$s + Remove content + Permanently ban + Days until expiration diff --git a/build.gradle.kts b/build.gradle.kts index b548952bc..5084344ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("com.android.library") version "8.2.1" apply false id("org.jetbrains.kotlin.android") version "1.9.21" apply false id("com.github.ben-manes.versions") version "0.42.0" - id("org.jmailen.kotlinter") version "4.1.0" apply false + id("org.jmailen.kotlinter") version "4.2.0" apply false id("com.google.devtools.ksp") version "1.9.21-1.0.15" apply false id("com.android.test") version "8.2.1" apply false id("androidx.baselineprofile") version "1.2.2" apply false