From 42d37e8d6eb48fe22e4bb38dcd989481eee9e2ca Mon Sep 17 00:00:00 2001 From: Dessalines Date: Sun, 4 Feb 2024 22:25:38 -0500 Subject: [PATCH] Adding admin / mod view votes. (#1331) * Starting to work on admin_view_votes, needs API added yet. * Finished up adding admin view votes. * Fix package name. * Upping lemmy-api version. * Adding a moderation subfield to post and comment action dropdowns. * Adding feature flag check. * Addressing PR comments. - Moving padding up to Box. - Using alternate feature flag method. --- .../main/java/com/jerboa/JerboaAppState.kt | 8 ++ app/src/main/java/com/jerboa/MainActivity.kt | 36 +++++ .../java/com/jerboa/datatypes/SampleData.kt | 1 + .../com/jerboa/model/CommentLikesViewModel.kt | 86 ++++++++++++ .../jerboa/model/CommunityListViewModel.kt | 1 + .../com/jerboa/model/PostLikesViewModel.kt | 86 ++++++++++++ .../ui/components/comment/CommentNode.kt | 28 ++++ .../ui/components/comment/CommentNodes.kt | 5 + .../comment/CommentOptionsDropdown.kt | 84 +++++++---- .../com/jerboa/ui/components/common/Route.kt | 31 ++++ .../components/community/CommunityActivity.kt | 1 + .../jerboa/ui/components/home/HomeActivity.kt | 1 + .../person/PersonProfileActivity.kt | 2 + .../jerboa/ui/components/post/PostActivity.kt | 2 + .../jerboa/ui/components/post/PostListing.kt | 13 ++ .../jerboa/ui/components/post/PostListings.kt | 4 + .../post/composables/PostOptionsDropdown.kt | 132 +++++++++++------- .../ui/components/viewvotes/ViewVotes.kt | 91 ++++++++++++ .../viewvotes/comment/CommentLikesActivity.kt | 120 ++++++++++++++++ .../viewvotes/post/PostLikesActivity.kt | 121 ++++++++++++++++ app/src/main/res/values/strings.xml | 4 + 21 files changed, 776 insertions(+), 81 deletions(-) create mode 100644 app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt create mode 100644 app/src/main/java/com/jerboa/model/PostLikesViewModel.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesActivity.kt create mode 100644 app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesActivity.kt diff --git a/app/src/main/java/com/jerboa/JerboaAppState.kt b/app/src/main/java/com/jerboa/JerboaAppState.kt index 043e6ee24..3a07f4b02 100644 --- a/app/src/main/java/com/jerboa/JerboaAppState.kt +++ b/app/src/main/java/com/jerboa/JerboaAppState.kt @@ -167,6 +167,14 @@ class JerboaAppState( navController.navigate(Route.ProfileFromIdArgs.makeRoute(id = "$id", saved = "$saved")) } + fun toPostLikes(postId: PostId) { + navController.navigate(Route.PostLikesArgs.makeRoute(id = "$postId")) + } + + fun toCommentLikes(commentId: CommentId) { + navController.navigate(Route.CommentLikesArgs.makeRoute(id = "$commentId")) + } + fun toCommunityList(select: Boolean = Route.CommunityListArgs.SELECT_DEFAULT) { navController.navigate(Route.CommunityListArgs.makeRoute(select = "$select")) } diff --git a/app/src/main/java/com/jerboa/MainActivity.kt b/app/src/main/java/com/jerboa/MainActivity.kt index d536c49d7..c2c0697ee 100644 --- a/app/src/main/java/com/jerboa/MainActivity.kt +++ b/app/src/main/java/com/jerboa/MainActivity.kt @@ -73,6 +73,8 @@ import com.jerboa.ui.components.settings.about.AboutActivity import com.jerboa.ui.components.settings.account.AccountSettingsActivity import com.jerboa.ui.components.settings.crashlogs.CrashLogsActivity import com.jerboa.ui.components.settings.lookandfeel.LookAndFeelActivity +import com.jerboa.ui.components.viewvotes.comment.CommentLikesActivity +import com.jerboa.ui.components.viewvotes.post.PostLikesActivity import com.jerboa.ui.theme.JerboaTheme import com.jerboa.util.markwon.BetterLinkMovementMethod @@ -617,6 +619,40 @@ class MainActivity : AppCompatActivity() { ) } + composable( + route = Route.POST_LIKES, + arguments = + listOf( + navArgument(Route.PostLikesArgs.ID) { + type = Route.PostLikesArgs.ID_TYPE + }, + ), + ) { + val args = Route.PostLikesArgs(it) + PostLikesActivity( + appState = appState, + postId = args.id, + onBack = appState::navigateUp, + ) + } + + composable( + route = Route.COMMENT_LIKES, + arguments = + listOf( + navArgument(Route.CommentLikesArgs.ID) { + type = Route.CommentLikesArgs.ID_TYPE + }, + ), + ) { + val args = Route.CommentLikesArgs(it) + CommentLikesActivity( + appState = appState, + commentId = args.id, + onBack = appState::navigateUp, + ) + } + composable( route = Route.COMMENT_REPORT, arguments = diff --git a/app/src/main/java/com/jerboa/datatypes/SampleData.kt b/app/src/main/java/com/jerboa/datatypes/SampleData.kt index 3bd0add3a..fd5d4e90b 100644 --- a/app/src/main/java/com/jerboa/datatypes/SampleData.kt +++ b/app/src/main/java/com/jerboa/datatypes/SampleData.kt @@ -472,6 +472,7 @@ val sampleCommunityAggregates = published = "2022-01-02T04:02:44.592929", community_id = 834, subscribers = 52, + subscribers_local = 21, posts = 82, comments = 987, users_active_day = 28, diff --git a/app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt b/app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt new file mode 100644 index 000000000..0ee65122c --- /dev/null +++ b/app/src/main/java/com/jerboa/model/CommentLikesViewModel.kt @@ -0,0 +1,86 @@ +package com.jerboa.model + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.jerboa.api.API +import com.jerboa.api.ApiState +import com.jerboa.api.toApiState +import com.jerboa.appendData +import it.vercruysse.lemmyapi.v0x19.datatypes.CommentId +import it.vercruysse.lemmyapi.v0x19.datatypes.ListCommentLikes +import it.vercruysse.lemmyapi.v0x19.datatypes.ListCommentLikesResponse +import kotlinx.coroutines.launch + +class CommentLikesViewModel(val id: CommentId) : ViewModel() { + var likesRes: ApiState by mutableStateOf(ApiState.Empty) + private set + private var page by mutableLongStateOf(1) + + init { + getLikes() + } + + fun resetPage() { + page = 1 + } + + fun getLikes(state: ApiState = ApiState.Loading) { + viewModelScope.launch { + likesRes = state + likesRes = API.getInstance().listCommentLikes(getForm()).toApiState() + } + } + + private fun getForm(): ListCommentLikes { + return ListCommentLikes( + comment_id = id, + page = page, + ) + } + + fun appendLikes() { + viewModelScope.launch { + val oldRes = likesRes + when (oldRes) { + is ApiState.Success -> likesRes = ApiState.Appending(oldRes.data) + else -> return@launch + } + + page += 1 + val newRes = API.getInstance().listCommentLikes(getForm()).toApiState() + + likesRes = + when (newRes) { + is ApiState.Success -> { + val appended = appendData(oldRes.data.comment_likes, newRes.data.comment_likes) + + ApiState.Success(oldRes.data.copy(comment_likes = appended)) + } + + else -> { + oldRes + } + } + } + } + + companion object { + class Factory( + private val id: CommentId, + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return CommentLikesViewModel(id) as T + } + } + } +} diff --git a/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt b/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt index 93967d0ac..6bde97200 100644 --- a/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt +++ b/app/src/main/java/com/jerboa/model/CommunityListViewModel.kt @@ -46,6 +46,7 @@ class CommunityListViewModel(communities: List) : ViewMod CommunityAggregates( community_id = cfv.community.id, subscribers = 0, + subscribers_local = 0, posts = 0, comments = 0, published = "", diff --git a/app/src/main/java/com/jerboa/model/PostLikesViewModel.kt b/app/src/main/java/com/jerboa/model/PostLikesViewModel.kt new file mode 100644 index 000000000..4b096890b --- /dev/null +++ b/app/src/main/java/com/jerboa/model/PostLikesViewModel.kt @@ -0,0 +1,86 @@ +package com.jerboa.model + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras +import com.jerboa.api.API +import com.jerboa.api.ApiState +import com.jerboa.api.toApiState +import com.jerboa.appendData +import it.vercruysse.lemmyapi.v0x19.datatypes.ListPostLikes +import it.vercruysse.lemmyapi.v0x19.datatypes.ListPostLikesResponse +import it.vercruysse.lemmyapi.v0x19.datatypes.PostId +import kotlinx.coroutines.launch + +class PostLikesViewModel(val id: PostId) : ViewModel() { + var likesRes: ApiState by mutableStateOf(ApiState.Empty) + private set + private var page by mutableLongStateOf(1) + + init { + getLikes() + } + + fun resetPage() { + page = 1 + } + + fun getLikes(state: ApiState = ApiState.Loading) { + viewModelScope.launch { + likesRes = state + likesRes = API.getInstance().listPostLikes(getForm()).toApiState() + } + } + + private fun getForm(): ListPostLikes { + return ListPostLikes( + post_id = id, + page = page, + ) + } + + fun appendLikes() { + viewModelScope.launch { + val oldRes = likesRes + when (oldRes) { + is ApiState.Success -> likesRes = ApiState.Appending(oldRes.data) + else -> return@launch + } + + page += 1 + val newRes = API.getInstance().listPostLikes(getForm()).toApiState() + + likesRes = + when (newRes) { + is ApiState.Success -> { + val appended = appendData(oldRes.data.post_likes, newRes.data.post_likes) + + ApiState.Success(oldRes.data.copy(post_likes = appended)) + } + + else -> { + oldRes + } + } + } + } + + companion object { + class Factory( + private val id: PostId, + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class, + extras: CreationExtras, + ): T { + return PostLikesViewModel(id) as T + } + } + } +} 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 921d39d3f..2fd9af04a 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 @@ -58,6 +58,8 @@ import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.InstantScores import com.jerboa.feat.VoteType +import com.jerboa.feat.amAdmin +import com.jerboa.feat.amMod import com.jerboa.feat.canMod import com.jerboa.isPostCreator import com.jerboa.ui.components.common.ActionBarButton @@ -188,6 +190,7 @@ fun LazyListScope.commentNodeItem( onEditCommentClick: (commentView: CommentView) -> Unit, onDeleteCommentClick: (commentView: CommentView) -> Unit, onPersonClick: (personId: PersonId) -> Unit, + onViewVotesClick: (CommentId) -> Unit, onHeaderClick: (commentView: CommentView) -> Unit, onHeaderLongClick: (commentView: CommentView) -> Unit, onCommunityClick: (community: Community) -> Unit, @@ -350,6 +353,7 @@ fun LazyListScope.commentNodeItem( onBanFromCommunityClick = onBanFromCommunityClick, onCommentLinkClick = onCommentLinkClick, onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, onBlockCreatorClick = onBlockCreatorClick, onClick = { toggleExpanded(commentId) @@ -405,6 +409,7 @@ fun LazyListScope.commentNodeItem( onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, onHeaderClick = onHeaderClick, onHeaderLongClick = onHeaderLongClick, onCommunityClick = onCommunityClick, @@ -443,6 +448,7 @@ fun LazyListScope.missingCommentNodeItem( onEditCommentClick: (commentView: CommentView) -> Unit, onDeleteCommentClick: (commentView: CommentView) -> Unit, onPersonClick: (personId: PersonId) -> Unit, + onViewVotesClick: (CommentId) -> Unit, onHeaderClick: (commentView: CommentView) -> Unit, onHeaderLongClick: (commentView: CommentView) -> Unit, onCommunityClick: (community: Community) -> Unit, @@ -553,6 +559,7 @@ fun LazyListScope.missingCommentNodeItem( onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, onHeaderClick = onHeaderClick, onHeaderLongClick = onHeaderLongClick, onCommunityClick = onCommunityClick, @@ -684,6 +691,7 @@ fun CommentFooterLine( onCommentLinkClick: (commentView: CommentView) -> Unit, onBlockCreatorClick: (creator: Person) -> Unit, onPersonClick: (personId: PersonId) -> Unit, + onViewVotesClick: (CommentId) -> Unit, onClick: () -> Unit, onLongClick: () -> Unit, account: Account, @@ -692,6 +700,22 @@ fun CommentFooterLine( ) { var showMoreOptions by remember { mutableStateOf(false) } + val amAdmin = + remember(admins) { + amAdmin( + admins = admins, + myId = account.id, + ) + } + + val amMod = + remember { + amMod( + moderators = moderators, + myId = account.id, + ) + } + val canMod = remember(admins) { canMod( @@ -716,8 +740,11 @@ fun CommentFooterLine( onBlockCreatorClick = onBlockCreatorClick, onCommentLinkClick = onCommentLinkClick, onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, isCreator = account.id == commentView.creator.id, canMod = canMod, + amMod = amMod, + amAdmin = amAdmin, viewSource = viewSource, ) } @@ -831,6 +858,7 @@ fun CommentNodesPreview() { onBanFromCommunityClick = {}, onCommentLinkClick = {}, onPersonClick = {}, + onViewVotesClick = {}, onHeaderClick = {}, onHeaderLongClick = {}, onCommunityClick = {}, 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 d43fc33d8..aee9b1260 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 @@ -49,6 +49,7 @@ fun CommentNodes( onCommentLinkClick: (commentView: CommentView) -> Unit, onFetchChildrenClick: (commentView: CommentView) -> Unit, onPersonClick: (personId: PersonId) -> Unit, + onViewVotesClick: (CommentId) -> Unit, onHeaderClick: (commentView: CommentView) -> Unit, onHeaderLongClick: (commentView: CommentView) -> Unit, onCommunityClick: (community: Community) -> Unit, @@ -90,6 +91,7 @@ fun CommentNodes( onCommentLinkClick = onCommentLinkClick, onFetchChildrenClick = onFetchChildrenClick, onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, onHeaderClick = onHeaderClick, onHeaderLongClick = onHeaderLongClick, onCommunityClick = onCommunityClick, @@ -136,6 +138,7 @@ fun LazyListScope.commentNodeItems( onCommentLinkClick: (commentView: CommentView) -> Unit, onFetchChildrenClick: (commentView: CommentView) -> Unit, onPersonClick: (personId: PersonId) -> Unit, + onViewVotesClick: (CommentId) -> Unit, onHeaderClick: (commentView: CommentView) -> Unit, onHeaderLongClick: (commentView: CommentView) -> Unit, onCommunityClick: (community: Community) -> Unit, @@ -172,6 +175,7 @@ fun LazyListScope.commentNodeItems( onMarkAsReadClick = onMarkAsReadClick, onCommentClick = onCommentClick, onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, onHeaderClick = onHeaderClick, onHeaderLongClick = onHeaderLongClick, onCommunityClick = onCommunityClick, @@ -214,6 +218,7 @@ fun LazyListScope.commentNodeItems( onMarkAsReadClick = onMarkAsReadClick, onCommentClick = onCommentClick, onPersonClick = onPersonClick, + onViewVotesClick = onViewVotesClick, onHeaderClick = onHeaderClick, onHeaderLongClick = onHeaderLongClick, onCommunityClick = onCommunityClick, 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 312fa986e..91229a748 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 @@ -14,19 +14,26 @@ 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 +import androidx.compose.material.icons.outlined.Shield import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString import com.jerboa.R +import com.jerboa.api.API 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 io.github.z4kn4fein.semver.toVersion +import it.vercruysse.lemmyapi.FeatureFlags +import it.vercruysse.lemmyapi.v0x19.datatypes.CommentId import it.vercruysse.lemmyapi.v0x19.datatypes.CommentView import it.vercruysse.lemmyapi.v0x19.datatypes.Person import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId @@ -43,10 +50,13 @@ fun CommentOptionsDropdown( onBlockCreatorClick: (Person) -> Unit, onReportClick: (CommentView) -> Unit, onRemoveClick: (CommentView) -> Unit, + onViewVotesClick: (CommentId) -> Unit, onBanPersonClick: (person: Person) -> Unit, onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, isCreator: Boolean, canMod: Boolean, + amMod: Boolean, + amAdmin: Boolean, viewSource: Boolean, ) { val localClipboardManager = LocalClipboardManager.current @@ -176,36 +186,56 @@ fun CommentOptionsDropdown( onReportClick(commentView) }, ) + } - if (canMod) { - HorizontalDivider() - val (removeText, removeIcon) = - if (commentView.comment.removed) { - Pair(stringResource(R.string.restore_comment), Icons.Outlined.Restore) - } else { - Pair(stringResource(R.string.remove_comment), Icons.Outlined.GppBad) - } + // The moderation subfield + if (amMod || amAdmin) { + PopupMenuItem( + text = stringResource(R.string.moderation), + icon = Icons.Outlined.Shield, + ) { + if (canMod) { + HorizontalDivider() + val (removeText, removeIcon) = + if (commentView.comment.removed) { + Pair(stringResource(R.string.restore_comment), Icons.Outlined.Restore) + } else { + Pair(stringResource(R.string.remove_comment), Icons.Outlined.GppBad) + } - PopupMenuItem( - text = removeText, - icon = removeIcon, - onClick = { - onDismissRequest() - onRemoveClick(commentView) - }, - ) - BanPersonPopupMenuItem(commentView.creator, onDismissRequest, onBanPersonClick) + PopupMenuItem( + text = removeText, + icon = removeIcon, + onClick = { + onDismissRequest() + 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, + ) + } + } - // 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, + // You can do these actions on mods above you + if (FeatureFlags(version = API.version.toVersion()).listAdminVotes()) { + PopupMenuItem( + text = stringResource(R.string.view_votes), + icon = ImageVector.vectorResource(R.drawable.up_filled), + onClick = { + onDismissRequest() + onViewVotesClick(commentView.comment.id) + }, ) } } 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 00b86a398..ba965c3f3 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 @@ -45,6 +45,9 @@ object Route { val COMMENT_REPORT = CommentReportArgs.route val POST_REPORT = PostReportArgs.route + val COMMENT_LIKES = CommentLikesArgs.route + val POST_LIKES = PostLikesArgs.route + const val SETTINGS = "settings" const val LOOK_AND_FEEL = "lookAndFeel" const val ACCOUNT_SETTINGS = "accountSettings" @@ -207,6 +210,34 @@ object Route { } } + class CommentLikesArgs(val id: CommentId) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(id = navBackStackEntry.arguments?.getLong(ID)!!) + + companion object { + const val ID = "id" + val ID_TYPE = NavType.LongType + + fun makeRoute(id: String) = "commentLikes/$id" + + internal val route by lazy { makeRoute(id = "{$ID}") } + } + } + + class PostLikesArgs(val id: PostId) { + constructor(navBackStackEntry: NavBackStackEntry) : + this(id = navBackStackEntry.arguments?.getLong(ID)!!) + + companion object { + const val ID = "id" + val ID_TYPE = NavType.LongType + + fun makeRoute(id: String) = "postLikes/$id" + + internal val route by lazy { makeRoute(id = "{$ID}") } + } + } + class ViewArgs(val url: String) { constructor(navBackStackEntry: NavBackStackEntry) : this(url = navBackStackEntry.arguments?.getString(URL)!!) 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 0cc6c712b..0529dfca6 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 @@ -379,6 +379,7 @@ fun CommunityActivity( ) } }, + onViewPostVotesClick = appState::toPostLikes, 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 e3e8a5444..bc4ff5409 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 @@ -380,6 +380,7 @@ fun MainPostListingsContent( ) } }, + onViewPostVotesClick = appState::toPostLikes, 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 b1c4b1288..49f31102d 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 @@ -580,6 +580,7 @@ fun UserTabs( ) } }, + onViewPostVotesClick = appState::toPostLikes, onCommunityClick = { community -> appState.toCommunity(id = community.id) }, @@ -764,6 +765,7 @@ fun UserTabs( } }, onPersonClick = appState::toProfile, + onViewVotesClick = appState::toCommentLikes, onHeaderClick = {}, onHeaderLongClick = { commentView -> toggleActionBar(commentView.comment.id) }, onCommunityClick = { community -> 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 03c01b21b..1a6908a75 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 @@ -443,6 +443,7 @@ fun PostActivity( ) } }, + onViewVotesClick = appState::toPostLikes, onPersonClick = appState::toProfile, // Do nothing showReply = true, @@ -609,6 +610,7 @@ fun PostActivity( } }, onPersonClick = appState::toProfile, + onViewVotesClick = appState::toCommentLikes, onHeaderClick = { commentView -> toggleExpanded(commentView.comment.id) }, onHeaderLongClick = { commentView -> toggleActionBar(commentView.comment.id) }, onEditCommentClick = { cv -> 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 738d349f4..217f017ab 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 @@ -560,6 +560,7 @@ fun PostFooterLine( onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onLockPostClick: (postView: PostView) -> Unit, onFeaturePostClick: (data: PostFeatureData) -> Unit, + onViewVotesClick: (PostId) -> Unit, onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: PersonId) -> Unit, onViewSourceClick: () -> Unit, @@ -615,6 +616,7 @@ fun PostFooterLine( onBanFromCommunityClick = onBanFromCommunityClick, onLockPostClick = onLockPostClick, onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewVotesClick, onViewSourceClick = onViewSourceClick, isCreator = account.id == postView.creator.id, canMod = canMod, @@ -814,6 +816,7 @@ fun PostFooterLinePreview() { onBanFromCommunityClick = {}, onLockPostClick = {}, onFeaturePostClick = {}, + onViewVotesClick = {}, onCommunityClick = {}, onPersonClick = {}, onViewSourceClick = {}, @@ -850,6 +853,7 @@ fun PreviewPostListingCard() { onBanFromCommunityClick = {}, onLockPostClick = {}, onFeaturePostClick = {}, + onViewVotesClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -889,6 +893,7 @@ fun PreviewLinkPostListing() { onBanFromCommunityClick = {}, onLockPostClick = {}, onFeaturePostClick = {}, + onViewVotesClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -928,6 +933,7 @@ fun PreviewImagePostListingCard() { onBanFromCommunityClick = {}, onLockPostClick = {}, onFeaturePostClick = {}, + onViewVotesClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -967,6 +973,7 @@ fun PreviewImagePostListingSmallCard() { onBanFromCommunityClick = {}, onLockPostClick = {}, onFeaturePostClick = {}, + onViewVotesClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -1006,6 +1013,7 @@ fun PreviewLinkNoThumbnailPostListing() { onBanFromCommunityClick = {}, onLockPostClick = {}, onFeaturePostClick = {}, + onViewVotesClick = {}, onPersonClick = {}, fullBody = false, account = AnonAccount, @@ -1044,6 +1052,7 @@ fun PostListing( onLockPostClick: (postView: PostView) -> Unit, onFeaturePostClick: (data: PostFeatureData) -> Unit, onPersonClick: (personId: PersonId) -> Unit, + onViewVotesClick: (PostId) -> Unit, showReply: Boolean = false, showCommunityName: Boolean = true, fullBody: Boolean, @@ -1101,6 +1110,7 @@ fun PostListing( onBanFromCommunityClick = onBanFromCommunityClick, onLockPostClick = onLockPostClick, onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewVotesClick, onPersonClick = onPersonClick, onViewSourceClick = { viewSource = !viewSource @@ -1149,6 +1159,7 @@ fun PostListing( onBanFromCommunityClick = onBanFromCommunityClick, onLockPostClick = onLockPostClick, onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewVotesClick, onPersonClick = onPersonClick, onViewSourceClick = { viewSource = !viewSource @@ -1541,6 +1552,7 @@ fun PostListingCard( onLockPostClick: (postView: PostView) -> Unit, onFeaturePostClick: (data: PostFeatureData) -> Unit, onPersonClick: (personId: PersonId) -> Unit, + onViewVotesClick: (PostId) -> Unit, onViewSourceClick: () -> Unit, viewSource: Boolean, showReply: Boolean = false, @@ -1620,6 +1632,7 @@ fun PostListingCard( onBanFromCommunityClick = onBanFromCommunityClick, onLockPostClick = onLockPostClick, onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewVotesClick, 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 20e951aec..26b14549d 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 @@ -36,6 +36,7 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityModeratorView import it.vercruysse.lemmyapi.v0x19.datatypes.Person import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId import it.vercruysse.lemmyapi.v0x19.datatypes.PersonView +import it.vercruysse.lemmyapi.v0x19.datatypes.PostId import it.vercruysse.lemmyapi.v0x19.datatypes.PostView @Composable @@ -56,6 +57,7 @@ fun PostListings( onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onLockPostClick: (postView: PostView) -> Unit, onFeaturePostClick: (data: PostFeatureData) -> Unit, + onViewPostVotesClick: (PostId) -> Unit, onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: PersonId) -> Unit, loadMorePosts: () -> Unit, @@ -115,6 +117,7 @@ fun PostListings( onBanFromCommunityClick = onBanFromCommunityClick, onLockPostClick = onLockPostClick, onFeaturePostClick = onFeaturePostClick, + onViewVotesClick = onViewPostVotesClick, onPersonClick = onPersonClick, showCommunityName = showCommunityName, fullBody = false, @@ -184,6 +187,7 @@ fun PreviewPostListings() { onBanFromCommunityClick = {}, onLockPostClick = {}, onFeaturePostClick = {}, + onViewPostVotesClick = {}, 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 9d5310b6f..202dfafb5 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 @@ -19,15 +19,18 @@ import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.PushPin import androidx.compose.material.icons.outlined.Restore import androidx.compose.material.icons.outlined.Share +import androidx.compose.material.icons.outlined.Shield import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString import com.jerboa.PostType import com.jerboa.R -import com.jerboa.api.API +import com.jerboa.api.API.getInstanceOrNull import com.jerboa.communityNameShown import com.jerboa.copyToClipboard import com.jerboa.datatypes.BanFromCommunityData @@ -50,6 +53,7 @@ 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.PostId import it.vercruysse.lemmyapi.v0x19.datatypes.PostView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -70,6 +74,7 @@ fun PostOptionsDropdown( onBanFromCommunityClick: (banData: BanFromCommunityData) -> Unit, onLockPostClick: (PostView) -> Unit, onFeaturePostClick: (PostFeatureData) -> Unit, + onViewVotesClick: (PostId) -> Unit, onViewSourceClick: () -> Unit, isCreator: Boolean, canMod: Boolean, @@ -394,7 +399,7 @@ fun PostOptionsDropdown( }, ) - val api = API.getInstanceOrNull() + val api = getInstanceOrNull() if (api != null && api.FF.instanceBlock()) { val instance = getInstanceFromCommunityUrl(postView.community.actor_id) @@ -428,43 +433,61 @@ fun PostOptionsDropdown( onReportClick(postView) }, ) + } - if (canMod) { - HorizontalDivider() + // The moderation subfield + if (amMod || amAdmin) { + PopupMenuItem( + text = stringResource(R.string.moderation), + icon = Icons.Outlined.Shield, + ) { + // Moddable items limited to mods below you + if (canMod) { + 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.GppBad) + } - 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.GppBad) + PopupMenuItem( + text = removeText, + icon = removeIcon, + onClick = { + onDismissRequest() + 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, + ) } + } - PopupMenuItem( - text = removeText, - icon = removeIcon, - onClick = { - onDismissRequest() - 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, + // You can do these actions on mods above you + + // These are all amMod || amAdmin + if (getInstanceOrNull()?.FF?.listAdminVotes() == true) { + PopupMenuItem( + text = stringResource(R.string.view_votes), + icon = ImageVector.vectorResource(R.drawable.up_filled), + onClick = { + onDismissRequest() + onViewVotesClick(postView.post.id) + }, ) } - } - // You can do these actions on mods above you - if (amMod || amAdmin) { val (lockText, lockIcon) = if (postView.post.locked) { Pair(stringResource(R.string.unlock_post), Icons.Outlined.LockOpen) @@ -480,6 +503,7 @@ fun PostOptionsDropdown( onLockPostClick(postView) }, ) + val (featureInCommunityText, featureIconUsed) = if (postView.post.featured_community) { Pair(stringResource(R.string.unfeature_in_community), unFeatureIcon) @@ -501,30 +525,30 @@ fun PostOptionsDropdown( ) }, ) - } - if (amAdmin) { - val (featureInLocalText, featureIconUsed) = - if (postView.post.featured_local) { - Pair(stringResource(R.string.unfeature_in_local), unFeatureIcon) - } else { - Pair(stringResource(R.string.feature_in_local), featureIcon) - } + if (amAdmin) { + val (featureInLocalText, featureLocalIconUsed) = + if (postView.post.featured_local) { + Pair(stringResource(R.string.unfeature_in_local), unFeatureIcon) + } else { + Pair(stringResource(R.string.feature_in_local), featureIcon) + } - PopupMenuItem( - text = featureInLocalText, - icon = featureIconUsed, - onClick = { - onDismissRequest() - onFeaturePostClick( - PostFeatureData( - post = postView.post, - featured = postView.post.featured_local, - type = PostFeatureType.Local, - ), - ) - }, - ) + PopupMenuItem( + text = featureInLocalText, + icon = featureLocalIconUsed, + onClick = { + onDismissRequest() + onFeaturePostClick( + PostFeatureData( + post = postView.post, + featured = postView.post.featured_local, + type = PostFeatureType.Local, + ), + ) + }, + ) + } } } } diff --git a/app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt b/app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt new file mode 100644 index 000000000..13c4c8876 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/viewvotes/ViewVotes.kt @@ -0,0 +1,91 @@ +package com.jerboa.ui.components.viewvotes + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import com.jerboa.R +import com.jerboa.ui.components.common.simpleVerticalScrollbar +import com.jerboa.ui.components.person.PersonProfileLink +import com.jerboa.ui.theme.MEDIUM_PADDING +import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId +import it.vercruysse.lemmyapi.v0x19.datatypes.VoteView + +@Composable +fun ViewVotesBody( + likes: List, + listState: LazyListState, + onPersonClick: (personId: PersonId) -> Unit, +) { + LazyColumn( + state = listState, + modifier = + Modifier + .fillMaxSize() + .simpleVerticalScrollbar(listState), + verticalArrangement = Arrangement.spacedBy(MEDIUM_PADDING), + ) { + items( + likes, + key = { like -> like.creator.id }, + contentType = { "like" }, + ) { like -> + val voteInfo = buildVoteInfo(vote = like.score) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + ) { + PersonProfileLink( + person = like.creator, + onClick = onPersonClick, + showAvatar = true, + ) + Icon( + imageVector = voteInfo.icon, + tint = voteInfo.color, + contentDescription = voteInfo.description, + ) + } + HorizontalDivider(modifier = Modifier.padding(vertical = MEDIUM_PADDING)) + } + } +} + +private data class VoteInfo( + val icon: ImageVector, + val color: Color, + val description: String, +) + +@Composable +private fun buildVoteInfo(vote: Long): VoteInfo { + return when (vote) { + 1L -> + VoteInfo( + ImageVector.vectorResource(id = R.drawable.up_filled), + MaterialTheme.colorScheme.secondary, + stringResource(R.string.upvote), + ) + + else -> + VoteInfo( + ImageVector.vectorResource(id = R.drawable.down_filled), + MaterialTheme.colorScheme.error, + stringResource(R.string.downvote), + ) + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesActivity.kt b/app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesActivity.kt new file mode 100644 index 000000000..4b7fee6f6 --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/viewvotes/comment/CommentLikesActivity.kt @@ -0,0 +1,120 @@ +package com.jerboa.ui.components.viewvotes.comment + +import android.util.Log +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.zIndex +import androidx.lifecycle.viewmodel.compose.viewModel +import com.jerboa.JerboaAppState +import com.jerboa.R +import com.jerboa.api.ApiState +import com.jerboa.isScrolledToEnd +import com.jerboa.model.CommentLikesViewModel +import com.jerboa.ui.components.common.ApiEmptyText +import com.jerboa.ui.components.common.ApiErrorText +import com.jerboa.ui.components.common.JerboaPullRefreshIndicator +import com.jerboa.ui.components.common.LoadingBar +import com.jerboa.ui.components.common.SimpleTopAppBar +import com.jerboa.ui.components.common.isLoading +import com.jerboa.ui.components.common.isRefreshing +import com.jerboa.ui.components.viewvotes.ViewVotesBody +import com.jerboa.ui.theme.MEDIUM_PADDING +import it.vercruysse.lemmyapi.v0x19.datatypes.CommentId + +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@Composable +fun CommentLikesActivity( + appState: JerboaAppState, + commentId: CommentId, + onBack: () -> Unit, +) { + Log.d("jerboa", "got to comment likes activity") + + val commentLikesViewModel: CommentLikesViewModel = viewModel(factory = CommentLikesViewModel.Companion.Factory(commentId)) + + Scaffold( + topBar = { + SimpleTopAppBar( + text = stringResource(R.string.comment_votes), + onClickBack = onBack, + ) + }, + content = { padding -> + + val listState = rememberLazyListState() + + // observer when reached end of list + val endOfListReached by remember { + derivedStateOf { + listState.isScrolledToEnd() + } + } + + // act when end of list reached + if (endOfListReached) { + LaunchedEffect(Unit) { + commentLikesViewModel.appendLikes() + } + } + + val refreshing = commentLikesViewModel.likesRes.isRefreshing() + + val refreshState = + rememberPullRefreshState( + refreshing = refreshing, + onRefresh = { + commentLikesViewModel.resetPage() + commentLikesViewModel.getLikes(ApiState.Refreshing) + }, + ) + + Box( + modifier = Modifier.pullRefresh(refreshState) + .padding( + vertical = padding.calculateTopPadding(), + horizontal = MEDIUM_PADDING, + ), + ) { + JerboaPullRefreshIndicator( + refreshing, + refreshState, + Modifier + .align(Alignment.TopCenter) + .zIndex(100F), + ) + + if (commentLikesViewModel.likesRes.isLoading()) { + LoadingBar() + } + + when (val likesRes = commentLikesViewModel.likesRes) { + ApiState.Empty -> ApiEmptyText() + is ApiState.Failure -> ApiErrorText(likesRes.msg) + is ApiState.Holder -> { + val likes = likesRes.data.comment_likes + ViewVotesBody( + likes = likes, + listState = listState, + onPersonClick = appState::toProfile, + ) + } + else -> {} + } + } + }, + ) +} diff --git a/app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesActivity.kt b/app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesActivity.kt new file mode 100644 index 000000000..fedf3977d --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/viewvotes/post/PostLikesActivity.kt @@ -0,0 +1,121 @@ +package com.jerboa.ui.components.viewvotes.post + +import android.util.Log +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.zIndex +import androidx.lifecycle.viewmodel.compose.viewModel +import com.jerboa.JerboaAppState +import com.jerboa.R +import com.jerboa.api.ApiState +import com.jerboa.isScrolledToEnd +import com.jerboa.model.PostLikesViewModel +import com.jerboa.ui.components.common.ApiEmptyText +import com.jerboa.ui.components.common.ApiErrorText +import com.jerboa.ui.components.common.JerboaPullRefreshIndicator +import com.jerboa.ui.components.common.LoadingBar +import com.jerboa.ui.components.common.SimpleTopAppBar +import com.jerboa.ui.components.common.isLoading +import com.jerboa.ui.components.common.isRefreshing +import com.jerboa.ui.components.viewvotes.ViewVotesBody +import com.jerboa.ui.theme.MEDIUM_PADDING +import it.vercruysse.lemmyapi.v0x19.datatypes.PostId + +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@Composable +fun PostLikesActivity( + appState: JerboaAppState, + postId: PostId, + onBack: () -> Unit, +) { + Log.d("jerboa", "got to post likes activity") + + val postLikesViewModel: PostLikesViewModel = viewModel(factory = PostLikesViewModel.Companion.Factory(postId)) + + Scaffold( + topBar = { + SimpleTopAppBar( + text = stringResource(R.string.post_votes), + onClickBack = onBack, + ) + }, + content = { padding -> + + val listState = rememberLazyListState() + + // observer when reached end of list + val endOfListReached by remember { + derivedStateOf { + listState.isScrolledToEnd() + } + } + + // act when end of list reached + if (endOfListReached) { + LaunchedEffect(Unit) { + postLikesViewModel.appendLikes() + } + } + + val refreshing = postLikesViewModel.likesRes.isRefreshing() + + val refreshState = + rememberPullRefreshState( + refreshing = refreshing, + onRefresh = { + postLikesViewModel.resetPage() + postLikesViewModel.getLikes(ApiState.Refreshing) + }, + ) + + Box( + modifier = Modifier + .pullRefresh(refreshState) + .padding( + vertical = padding.calculateTopPadding(), + horizontal = MEDIUM_PADDING, + ), + ) { + JerboaPullRefreshIndicator( + refreshing, + refreshState, + Modifier + .align(Alignment.TopCenter) + .zIndex(100F), + ) + + if (postLikesViewModel.likesRes.isLoading()) { + LoadingBar() + } + + when (val likesRes = postLikesViewModel.likesRes) { + ApiState.Empty -> ApiEmptyText() + is ApiState.Failure -> ApiErrorText(likesRes.msg) + is ApiState.Holder -> { + val likes = likesRes.data.post_likes + ViewVotesBody( + likes = likes, + listState = listState, + onPersonClick = appState::toProfile, + ) + } + else -> {} + } + } + }, + ) +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a77b39fbb..c16d27f9d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -432,4 +432,8 @@ Unfeature in Community Feature in Local Unfeature in Local + View votes + Post votes + Comment votes + Moderation