diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 78a834a10..2e0b4a910 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -29,7 +29,7 @@ android { useSupportLibrary = true } ksp { - arg("room.schemaLocation", "$projectDir/schemas") + arg("room.schemaLocation", "$projectDir/schemas") } } diff --git a/app/src/main/java/com/jerboa/Utils.kt b/app/src/main/java/com/jerboa/Utils.kt index ee8b8c61d..8d55a9084 100644 --- a/app/src/main/java/com/jerboa/Utils.kt +++ b/app/src/main/java/com/jerboa/Utils.kt @@ -867,7 +867,14 @@ fun getCommentIdDepthFromPath( } fun nsfwCheck(postView: PostView): Boolean { - return postView.post.nsfw || postView.community.nsfw + return nsfwCheck(postView.post, postView.community) +} + +fun nsfwCheck( + post: Post, + community: Community, +): Boolean { + return post.nsfw || community.nsfw } @RequiresApi(Build.VERSION_CODES.Q) diff --git a/app/src/main/java/com/jerboa/datatypes/SampleData.kt b/app/src/main/java/com/jerboa/datatypes/SampleData.kt index 42b4b4da6..9bb7643b2 100644 --- a/app/src/main/java/com/jerboa/datatypes/SampleData.kt +++ b/app/src/main/java/com/jerboa/datatypes/SampleData.kt @@ -49,7 +49,7 @@ val samplePost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077", + published = "2022-01-01T09:53:46.904077Z", updated = null, deleted = false, nsfw = false, @@ -74,7 +74,7 @@ val sampleLinkPost = community_id = 14681, removed = false, locked = true, - published = "2022-01-01T09:53:46.904077", + published = "2022-01-01T09:53:46.904077Z", updated = null, deleted = false, nsfw = false, @@ -99,7 +99,7 @@ val sampleLinkNoThumbnailPost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077", + published = "2022-01-01T09:53:46.904077Z", updated = null, deleted = false, nsfw = false, @@ -124,7 +124,7 @@ val sampleImagePost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077", + published = "2022-01-01T09:53:46.904077Z", updated = null, deleted = false, nsfw = false, @@ -149,7 +149,7 @@ val sampleMarkdownPost = community_id = 14681, removed = false, locked = false, - published = "2022-01-01T09:53:46.904077", + published = "2022-01-01T09:53:46.904077Z", updated = null, deleted = false, nsfw = false, @@ -171,8 +171,8 @@ val samplePerson = display_name = "No longer Homeless", avatar = "https://lemmy.ml/pictrs/image/LqURxPzFNW.jpg", banned = false, - published = "2021-08-08T01:47:44.437708", - updated = "2021-10-11T07:14:53.548707", + published = "2021-08-08T01:47:44.437708Z", + updated = "2021-10-11T07:14:53.548707Z", actor_id = "https://lemmy.ml/u/homeless", bio = "This is my bio.\n\nI like trucks, trains, and geese. This is *one* longer line " + @@ -194,8 +194,8 @@ val samplePerson2 = display_name = null, avatar = "https://lemmy.ml/pictrs/image/kykidJ1ssM.jpg", banned = false, - published = "2021-08-08T01:47:44.437708", - updated = "2021-10-11T07:14:53.548707", + published = "2021-08-08T01:47:44.437708Z", + updated = "2021-10-11T07:14:53.548707Z", actor_id = "https://lemmy.ml/u/homeless", bio = null, local = false, @@ -213,8 +213,8 @@ val samplePerson3 = name = "witch_power", display_name = null, banned = false, - published = "2021-08-08T01:47:44.437708", - updated = "2021-10-11T07:14:53.548707", + published = "2021-08-08T01:47:44.437708Z", + updated = "2021-10-11T07:14:53.548707Z", actor_id = "https://lemmy.ml/u/witch_power", bio = null, local = true, @@ -260,8 +260,8 @@ val sampleCommunity = title = "Socialism", description = "This is the r/socialism community", removed = false, - published = "2019-04-30T13:28:35.965035", - updated = "2021-01-25T16:27:15.804739", + published = "2019-04-30T13:28:35.965035Z", + updated = "2021-01-25T16:27:15.804739Z", deleted = false, nsfw = false, actor_id = "https://lemmy.ml/c/socialism", @@ -282,7 +282,7 @@ val samplePostAggregates = score = 8, upvotes = 8, downvotes = 0, - published = "2022-01-02T04:02:44.592929", + published = "2022-01-02T04:02:44.592929Z", ) val samplePostView = @@ -381,8 +381,8 @@ val sampleComment = "work" + ".\n\nIts kind of a long comment\n\nbut I don't want...", removed = false, - published = "2022-01-07T03:12:26.398434", - updated = "2022-01-07T03:15:37.360888", + published = "2022-01-07T03:12:26.398434Z", + updated = "2022-01-07T03:15:37.360888Z", deleted = false, ap_id = "https://midwest.social/comment/24621", local = false, @@ -399,8 +399,8 @@ val sampleReplyComment = path = "0.1.2", content = "This is a reply comment.\n\n# This is a header\n\n- list one\n\n- list two", removed = false, - published = "2022-01-07T04:12:26.398434", - updated = "2022-01-07T03:15:37.360888", + published = "2022-01-07T04:12:26.398434Z", + updated = "2022-01-07T03:15:37.360888Z", deleted = false, ap_id = "https://midwest.social/comment/24622", local = false, @@ -416,8 +416,8 @@ val sampleSecondReplyComment = path = "0.1.2.3", content = "This is a sub-reply comment, mmmmk", removed = false, - published = "2022-01-07T04:12:26.398434", - updated = "2022-01-07T03:15:37.360888", + published = "2022-01-07T04:12:26.398434Z", + updated = "2022-01-07T03:15:37.360888Z", deleted = false, ap_id = "https://midwest.social/comment/24622", local = false, @@ -431,7 +431,7 @@ val sampleCommentAggregates = score = 8, upvotes = 12, downvotes = 4, - published = "2022-01-02T04:02:44.592929", + published = "2022-01-02T04:02:44.592929Z", child_count = 0, ) @@ -486,7 +486,7 @@ val sampleCommentReply = recipient_id = 20, comment_id = 42, read = false, - published = "2022-01-01T09:53:46.904077", + published = "2022-01-01T09:53:46.904077Z", ) val samplePersonMention = @@ -495,7 +495,7 @@ val samplePersonMention = recipient_id = 20, comment_id = 42, read = false, - published = "2022-01-01T09:53:46.904077", + published = "2022-01-01T09:53:46.904077Z", ) val sampleCommentReplyView = @@ -534,7 +534,7 @@ val samplePersonMentionView = val sampleCommunityAggregates = CommunityAggregates( - published = "2022-01-02T04:02:44.592929", + published = "2022-01-02T04:02:44.592929Z", community_id = 834, subscribers = 52, subscribers_local = 21, @@ -576,8 +576,8 @@ val samplePrivateMessage = content = "A message from *me* to **you**", deleted = false, read = false, - published = "2022-01-07T04:12:26.398434", - updated = "2022-01-07T03:15:37.360888", + published = "2022-01-07T04:12:26.398434Z", + updated = "2022-01-07T03:15:37.360888Z", ap_id = "https://midwest.social/comment/24622", local = false, ) @@ -595,8 +595,8 @@ val sampleSite = name = "Lemmy.ml", sidebar = "# Hello!\n\n**This is** lemmy's sidebar", description = "A general purpose instance for lemmy", - published = "2022-01-07T04:12:26.398434", - updated = "2022-01-07T03:15:37.360888", + published = "2022-01-07T04:12:26.398434Z", + updated = "2022-01-07T03:15:37.360888Z", icon = "https://lemmy.ml/pictrs/image/LqURxPzFNW.jpg", banner = "https://lemmy.ml/pictrs/image/386rk5OYWS.jpg", actor_id = "https://lemmy.ml", diff --git a/app/src/main/java/com/jerboa/feed/FeedController.kt b/app/src/main/java/com/jerboa/feed/FeedController.kt new file mode 100644 index 000000000..2b37e78a1 --- /dev/null +++ b/app/src/main/java/com/jerboa/feed/FeedController.kt @@ -0,0 +1,74 @@ +package com.jerboa.feed + +import android.util.Log +import androidx.compose.runtime.mutableStateListOf + +open class FeedController { + protected val items = mutableStateListOf() + + val feed: List = items + + fun updateAll( + selector: (List) -> List, + transformer: (T) -> T, + ) { + selector(items).forEach { + safeUpdate(it, transformer) + } + } + + fun safeUpdate( + index: Int, + transformer: (T) -> T, + ) { + if (!isValidIndex(index)) { + Log.d("FeedController", "OoB item not updated $index") + return + } + + safeUpdate(index, transformer(items[index])) + } + + fun safeUpdate( + selector: (List) -> Int, + transformer: (T) -> T, + ) { + safeUpdate(selector(items), transformer) + } + + /** + * Update the item at the given index with the new item. + * + * If given -1 or an index that is out of bounds, the update will not be performed. + * It assumes that the item couldn't be found because the list has changed. + * Example: a network request to update an item succeeded after the list has changed. + * So, we ignore it + */ + fun safeUpdate( + index: Int, + new: T, + ) { + if (isValidIndex(index)) { + items[index] = new + } else { + Log.d("FeedController", "OoB item not updated $new") + } + } + + fun add(item: T) = items.add(item) + + fun remove(item: T) = items.remove(item) + + fun clear() = items.clear() + + fun addAll(newItems: List) = items.addAll(newItems) + + protected inline fun Iterable.indexesOf(predicate: (E) -> Boolean) = + mapIndexedNotNull { index, elem -> + index.takeIf { + predicate(elem) + } + } + + private fun isValidIndex(index: Int) = index >= 0 && index < items.size +} diff --git a/app/src/main/java/com/jerboa/feed/PaginationController.kt b/app/src/main/java/com/jerboa/feed/PaginationController.kt new file mode 100644 index 000000000..02d751119 --- /dev/null +++ b/app/src/main/java/com/jerboa/feed/PaginationController.kt @@ -0,0 +1,18 @@ +package com.jerboa.feed + +import it.vercruysse.lemmyapi.v0x19.datatypes.PaginationCursor + +class PaginationController( + var page: Long = 1, + var pageCursor: PaginationCursor? = null, +) { + fun reset() { + page = 1 + pageCursor = null + } + + fun nextPage(pageCursor: PaginationCursor?) { + page++ + this.pageCursor = pageCursor + } +} diff --git a/app/src/main/java/com/jerboa/feed/PostController.kt b/app/src/main/java/com/jerboa/feed/PostController.kt new file mode 100644 index 000000000..90a0cf305 --- /dev/null +++ b/app/src/main/java/com/jerboa/feed/PostController.kt @@ -0,0 +1,42 @@ +package com.jerboa.feed + +import com.jerboa.datatypes.BanFromCommunityData +import it.vercruysse.lemmyapi.v0x19.datatypes.HidePost +import it.vercruysse.lemmyapi.v0x19.datatypes.Person +import it.vercruysse.lemmyapi.v0x19.datatypes.PostView + +open class PostController : FeedController() { + fun findAndUpdatePost(updatedPostView: PostView) { + safeUpdate({ posts -> + posts.indexOfFirst { + it.post.id == updatedPostView.post.id + } + }) { updatedPostView } + } + + fun findAndUpdateCreator(person: Person) { + updateAll( + { it.indexesOf { postView -> postView.creator.id == person.id } }, + ) { it.copy(creator = person) } + } + + fun findAndUpdatePostCreatorBannedFromCommunity(banData: BanFromCommunityData) { + updateAll( + { + it.indexesOf { postView -> + postView.creator.id == banData.person.id && postView.community.id == banData.community.id + } + }, + ) { it.copy(banned_from_community = banData.banned, creator_banned_from_community = banData.banned, creator = banData.person) } + } + + fun findAndUpdatePostHidden(hidePost: HidePost) { + updateAll( + { + it.indexesOf { postView -> + hidePost.post_ids.contains(postView.post.id) + } + }, + ) { it.copy(hidden = hidePost.hide) } + } +} diff --git a/app/src/main/java/com/jerboa/model/HomeViewModel.kt b/app/src/main/java/com/jerboa/model/HomeViewModel.kt index dfd12501a..1208df1c9 100644 --- a/app/src/main/java/com/jerboa/model/HomeViewModel.kt +++ b/app/src/main/java/com/jerboa/model/HomeViewModel.kt @@ -1,14 +1,11 @@ package com.jerboa.model -import androidx.compose.foundation.lazy.LazyListState import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.jerboa.db.repository.AccountRepository import com.jerboa.jerboaApplication class HomeViewModel(accountRepository: AccountRepository) : PostsViewModel(accountRepository) { - val lazyListState = LazyListState() - init { init() } diff --git a/app/src/main/java/com/jerboa/model/PostViewModel.kt b/app/src/main/java/com/jerboa/model/PostViewModel.kt index 2971d1848..e45cd4822 100644 --- a/app/src/main/java/com/jerboa/model/PostViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostViewModel.kt @@ -48,8 +48,6 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.SaveComment import it.vercruysse.lemmyapi.v0x19.datatypes.SavePost import kotlinx.coroutines.launch -const val COMMENTS_DEPTH_MAX = 6L - class PostViewModel(val id: Either) : ViewModel() { var postRes: ApiState by mutableStateOf(ApiState.Empty) private set @@ -442,5 +440,7 @@ class PostViewModel(val id: Either) : ViewModel() { return PostViewModel(id) as T } } + + const val COMMENTS_DEPTH_MAX = 6L } } diff --git a/app/src/main/java/com/jerboa/model/PostsViewModel.kt b/app/src/main/java/com/jerboa/model/PostsViewModel.kt index 444be334c..c95ff7f2e 100644 --- a/app/src/main/java/com/jerboa/model/PostsViewModel.kt +++ b/app/src/main/java/com/jerboa/model/PostsViewModel.kt @@ -3,8 +3,8 @@ package com.jerboa.model import android.content.Context import android.util.Log import android.widget.Toast +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel @@ -18,11 +18,9 @@ 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.feed.PaginationController +import com.jerboa.feed.PostController import com.jerboa.findAndUpdatePostHidden -import com.jerboa.mergePosts import com.jerboa.toEnumSafe import it.vercruysse.lemmyapi.dto.ListingType import it.vercruysse.lemmyapi.dto.SortType @@ -30,11 +28,9 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.CreatePostLike import it.vercruysse.lemmyapi.v0x19.datatypes.DeletePost import it.vercruysse.lemmyapi.v0x19.datatypes.FeaturePost import it.vercruysse.lemmyapi.v0x19.datatypes.GetPosts -import it.vercruysse.lemmyapi.v0x19.datatypes.GetPostsResponse import it.vercruysse.lemmyapi.v0x19.datatypes.HidePost import it.vercruysse.lemmyapi.v0x19.datatypes.LockPost 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 @@ -42,16 +38,17 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch open class PostsViewModel(protected val accountRepository: AccountRepository) : ViewModel() { - var postsRes: ApiState by mutableStateOf(ApiState.Empty) - private set - private var page by mutableLongStateOf(1) - protected var pageCursor: PaginationCursor? by mutableStateOf(null) + val lazyListState = LazyListState() + var postsRes: ApiState> by mutableStateOf(ApiState.Empty) private set var sortType by mutableStateOf(SortType.Active) private set var listingType by mutableStateOf(ListingType.Local) private set + private val pageController = PaginationController() + private val postController = PostController() + protected fun init() { viewModelScope.launch { accountRepository.currentAccount @@ -66,136 +63,70 @@ open class PostsViewModel(protected val accountRepository: AccountRepository) : } } - protected fun nextPage() { - page += 1 - } - - protected fun prevPage() { - page -= 1 - } - - protected fun resetPage() { - page = 1 - pageCursor = null - } - - protected fun getPosts( + private fun initPosts( form: GetPosts, - state: ApiState = ApiState.Loading, + state: ApiState> = ApiState.Loading, ) { viewModelScope.launch { postsRes = state - postsRes = API.getInstance().getPosts(form).toApiState() + postsRes = API.getInstance().getPosts(form).fold( + onSuccess = { + pageController.nextPage(it.next_page) + postController.addAll(it.posts) + ApiState.Success(postController.feed) + }, + onFailure = { ApiState.Failure(it) }, + ) } } fun appendPosts() { + Log.d("PostsViewModel", "Appending posts") viewModelScope.launch { val oldRes = postsRes - postsRes = - when (oldRes) { - is ApiState.Appending -> return@launch - is ApiState.Holder -> ApiState.Appending(oldRes.data) - else -> return@launch - } - - // Update the page cursor before fetching again - pageCursor = oldRes.data.next_page - nextPage() - val newRes = API.getInstance().getPosts(getForm()).toApiState() - - postsRes = - when (newRes) { - is ApiState.Success -> { - val res = - GetPostsResponse( - posts = - mergePosts( - oldRes.data.posts, - newRes.data.posts, - ), - next_page = newRes.data.next_page, - ) - ApiState.Success( - res, - ) - } - - else -> { - prevPage() - ApiState.AppendingFailure(oldRes.data) - } - } - } - } - - fun updatePost(postView: PostView) { - when (val existing = postsRes) { - is ApiState.Success -> { - val newPosts = findAndUpdatePost(existing.data.posts, postView) - val newRes = ApiState.Success(existing.data.copy(posts = newPosts)) - postsRes = newRes + postsRes = when (oldRes) { + is ApiState.Appending -> return@launch + is ApiState.Holder -> ApiState.Appending(oldRes.data) + else -> return@launch } - else -> {} - } - } + when (val newRes = API.getInstance().getPosts(getForm()).toApiState()) { + is ApiState.Success -> { + pageController.nextPage(newRes.data.next_page) + postController.addAll(newRes.data.posts) + postsRes = ApiState.Success(oldRes.data) + } - private fun updatePostHidden(form: HidePost) { - when (val existing = postsRes) { - is ApiState.Success -> { - val newPosts = findAndUpdatePostHidden(existing.data.posts, form) - val newRes = ApiState.Success(existing.data.copy(posts = newPosts)) - postsRes = newRes + else -> { + postsRes = ApiState.AppendingFailure(oldRes.data) + } } - - else -> {} } } 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 -> {} - } + postController.findAndUpdateCreator(personView.person) } 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 -> {} - } + postController.findAndUpdatePostCreatorBannedFromCommunity(banData) } - fun resetPosts() { - resetPage() - getPosts( + fun resetPosts(state: ApiState> = ApiState.Loading) { + pageController.reset() + postController.clear() + initPosts( getForm(), + state, ) } - fun refreshPosts() { - resetPage() - getPosts( - getForm(), - ApiState.Refreshing, - ) - } + fun refreshPosts() = resetPosts(ApiState.Refreshing) protected open fun getForm(): GetPosts { return GetPosts( - page = page, - page_cursor = pageCursor, + page = pageController.page, + page_cursor = pageController.pageCursor, sort = sortType, type_ = listingType, ) @@ -252,7 +183,7 @@ open class PostsViewModel(protected val accountRepository: AccountRepository) : viewModelScope.launch { val msg = if (form.hide) R.string.post_hidden else R.string.post_unhidden API.getInstance().hidePost(form).onSuccess { - updatePostHidden(form) + postController.findAndUpdatePostHidden(form) Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show() } } @@ -273,4 +204,8 @@ open class PostsViewModel(protected val accountRepository: AccountRepository) : } } } + + fun updatePost(postView: PostView) { + postController.findAndUpdatePost(postView) + } } diff --git a/app/src/main/java/com/jerboa/ui/components/common/StateTriggers.kt b/app/src/main/java/com/jerboa/ui/components/common/StateTriggers.kt new file mode 100644 index 000000000..5e827296e --- /dev/null +++ b/app/src/main/java/com/jerboa/ui/components/common/StateTriggers.kt @@ -0,0 +1,30 @@ +package com.jerboa.ui.components.common + +import androidx.compose.foundation.lazy.LazyListState +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 com.jerboa.isScrolledToEnd + +@Composable +fun TriggerWhenReachingEnd( + listState: LazyListState, + loadMorePosts: () -> Unit, + showPostAppendRetry: Boolean, +) { + // observer when reached end of list + val endOfListReached by remember { + derivedStateOf { + listState.isScrolledToEnd() + } + } + + // Act when end of list reached + if (endOfListReached && !showPostAppendRetry) { + LaunchedEffect(Unit) { + loadMorePosts() + } + } +} diff --git a/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt b/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt index 2a410723e..c579a8886 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/TextBadge.kt @@ -2,6 +2,7 @@ package com.jerboa.ui.components.common import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -52,13 +53,13 @@ fun TextBadge( /** * Displays activitypub items (communities, users), with a smaller @instance shown */ -@OptIn(ExperimentalFoundationApi::class) @Composable fun ItemAndInstanceTitle( modifier: Modifier = Modifier, title: String, actorId: String?, local: Boolean, + onClick: (() -> Unit)?, itemColor: Color = MaterialTheme.colorScheme.primary, itemStyle: TextStyle = MaterialTheme.typography.bodyMedium, instanceColor: Color = MaterialTheme.colorScheme.onSurface.muted, @@ -89,9 +90,15 @@ fun ItemAndInstanceTitle( } } + val baseModifier = if (onClick != null) { + modifier.clickable { onClick() } + } else { + modifier + } + Text( text = text, maxLines = 1, - modifier = modifier.customMarquee(), + modifier = baseModifier.customMarquee(), ) } 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 28e92a884..7f5db5bfe 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 @@ -3,7 +3,6 @@ package com.jerboa.ui.components.community import android.util.Log import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.ExperimentalMaterial3Api @@ -91,15 +90,15 @@ fun CommunityActivity( ) { Log.d("jerboa", "got to community activity") + val ctx = LocalContext.current val scope = rememberCoroutineScope() - val postListState = rememberLazyListState() val snackbarHostState = remember { SnackbarHostState() } - val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel) val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) val communityViewModel: CommunityViewModel = viewModel(factory = CommunityViewModel.Companion.Factory(communityArg)) + val postListState = communityViewModel.lazyListState appState.ConsumeReturn(PostEditReturn.POST_VIEW, communityViewModel::updatePost) appState.ConsumeReturn(PostRemoveReturn.POST_VIEW, communityViewModel::updatePost) @@ -205,7 +204,7 @@ fun CommunityActivity( } PostListings( - posts = postsRes.data.posts, + posts = postsRes.data, admins = siteViewModel.admins(), moderators = remember(moderators) { moderators?.map { it.moderator.id } }, contentAboveListings = { diff --git a/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt b/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt index b91b9eeac..cbf149691 100644 --- a/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt +++ b/app/src/main/java/com/jerboa/ui/components/community/CommunityLink.kt @@ -40,13 +40,15 @@ import it.vercruysse.lemmyapi.v0x19.datatypes.CommunityView @Composable fun CommunityName( - modifier: Modifier = Modifier, community: Community, + modifier: Modifier = Modifier, + onClick: (() -> Unit)?, color: Color = MaterialTheme.colorScheme.primary, style: TextStyle = MaterialTheme.typography.bodyMedium, ) { ItemAndInstanceTitle( title = community.title, + onClick = onClick, actorId = community.actor_id, local = community.local, modifier = modifier, @@ -58,13 +60,13 @@ fun CommunityName( @Preview @Composable fun CommunityNamePreview() { - CommunityName(community = sampleCommunity) + CommunityName(sampleCommunity, onClick = null) } @Preview @Composable fun CommunityFederatedNamePreview() { - CommunityName(community = sampleCommunityFederated) + CommunityName(community = sampleCommunityFederated, onClick = null) } @Composable @@ -113,7 +115,7 @@ fun CommunityLink( } } Column { - CommunityName(community = community, color = color, style = style) + CommunityName(community = community, color = color, style = style, onClick = null) usersPerMonth?.also { Text( text = stringResource(R.string.community_link_users_month, usersPerMonth), 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 8bf77047a..52eb9fb72 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 @@ -2,6 +2,7 @@ package com.jerboa.ui.components.home import android.util.Log import androidx.activity.compose.ReportDrawn +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding @@ -51,7 +52,6 @@ import com.jerboa.model.SiteViewModel 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.JerboaLoadingBar import com.jerboa.ui.components.common.JerboaSnackbarHost @@ -113,10 +113,7 @@ fun HomeActivity( 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, - ) + appState.ConsumeReturn(BanFromCommunityReturn.BAN_DATA_VIEW, homeViewModel::updateBannedFromCommunity) LaunchedEffect(account) { if (!account.isAnon() && !account.isReady()) { @@ -161,24 +158,25 @@ fun HomeActivity( ) }, content = { innerPadding -> - MainPostListingsContent( - padding = innerPadding, - homeViewModel = homeViewModel, - siteViewModel = siteViewModel, - appSettingsViewModel = appSettingsViewModel, - account = account, - appState = appState, - postListState = postListState, - showVotingArrowsInListView = showVotingArrowsInListView, - useCustomTabs = useCustomTabs, - usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, - showPostLinkPreviews = showPostLinkPreviews, - markAsReadOnScroll = markAsReadOnScroll, - snackbarHostState = snackbarHostState, - postActionBarMode = postActionBarMode, - swipeToActionPreset = swipeToActionPreset, - ) + Box(modifier = Modifier.padding(innerPadding)) { + MainPostListingsContent( + homeViewModel = homeViewModel, + siteViewModel = siteViewModel, + appSettingsViewModel = appSettingsViewModel, + account = account, + appState = appState, + postListState = postListState, + showVotingArrowsInListView = showVotingArrowsInListView, + useCustomTabs = useCustomTabs, + usePrivateTabs = usePrivateTabs, + blurNSFW = blurNSFW, + showPostLinkPreviews = showPostLinkPreviews, + markAsReadOnScroll = markAsReadOnScroll, + snackbarHostState = snackbarHostState, + postActionBarMode = postActionBarMode, + swipeToActionPreset = swipeToActionPreset, + ) + } }, floatingActionButtonPosition = FabPosition.End, floatingActionButton = { @@ -215,7 +213,6 @@ fun MainPostListingsContent( siteViewModel: SiteViewModel, account: Account, appState: JerboaAppState, - padding: PaddingValues, postListState: LazyListState, appSettingsViewModel: AppSettingsViewModel, showVotingArrowsInListView: Boolean, @@ -233,9 +230,8 @@ fun MainPostListingsContent( var taglines: List? = null when (val siteRes = siteViewModel.siteRes) { - ApiState.Loading -> LoadingBar(padding) - ApiState.Empty -> ApiEmptyText() - is ApiState.Failure -> ApiErrorText(siteRes.msg, padding) + ApiState.Loading -> LoadingBar() + is ApiState.Failure -> ApiErrorText(siteRes.msg) is ApiState.Success -> { taglines = siteRes.data.taglines } @@ -246,29 +242,27 @@ fun MainPostListingsContent( ReportDrawn() PullToRefreshBox( - modifier = Modifier.padding(padding), isRefreshing = homeViewModel.postsRes.isRefreshing(), onRefresh = homeViewModel::refreshPosts, ) { JerboaLoadingBar(homeViewModel.postsRes) - val posts = - when (val postsRes = homeViewModel.postsRes) { - is ApiState.Failure -> { - apiErrorToast(ctx, postsRes.msg) - emptyList() - } - - is ApiState.Holder -> postsRes.data.posts.toList() - else -> emptyList() + val posts: List = when (val postsRes = homeViewModel.postsRes) { + is ApiState.Failure -> { + apiErrorToast(ctx, postsRes.msg) + listOf() } + is ApiState.Holder -> postsRes.data + else -> listOf() + } + PostListings( posts = posts, admins = siteViewModel.admins(), // No community moderators available here moderators = null, - contentAboveListings = { if (taglines !== null) Taglines(taglines = taglines.toList()) }, + contentAboveListings = { if (taglines !== null) Taglines(taglines = taglines) }, onUpvoteClick = { postView -> account.doIfReadyElseDisplayInfo( appState, diff --git a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt index 4840816ed..8a7576f18 100644 --- a/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt +++ b/app/src/main/java/com/jerboa/ui/components/person/PersonProfileLink.kt @@ -45,6 +45,7 @@ fun PersonName( local = person.local, itemColor = color, itemStyle = style, + onClick = null, ) } } 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 6fd141f48..a69553893 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 @@ -5,7 +5,6 @@ import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -85,7 +84,6 @@ import com.jerboa.ui.components.common.apiErrorToast import com.jerboa.ui.components.common.getCurrentAccount import com.jerboa.ui.components.common.isLoading import com.jerboa.ui.components.common.isRefreshing -import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.components.post.edit.PostEditReturn import com.jerboa.ui.components.remove.comment.CommentRemoveReturn import com.jerboa.ui.components.remove.post.PostRemoveReturn @@ -284,11 +282,7 @@ fun PostActivity( } LazyColumn( state = listState, - modifier = - Modifier - .fillMaxSize() - .simpleVerticalScrollbar(listState) - .testTag("jerboa:comments"), + modifier = Modifier.testTag("jerboa:comments"), ) { item(key = "${postView.post.id}_listing", "post_listing") { PostListing( 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 62d10b550..06e71eb25 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 @@ -128,7 +128,10 @@ import kotlinx.coroutines.CoroutineScope @Composable fun PostHeaderLine( - postView: PostView, + community: Community, + post: Post, + creator: Person, + creatorBannedFromCommunity: Boolean, instantScores: InstantScores, onCommunityClick: (community: Community) -> Unit, onPersonClick: (personId: PersonId) -> Unit, @@ -139,7 +142,6 @@ fun PostHeaderLine( blurNSFW: BlurNSFW, voteDisplayMode: VoteDisplayMode, ) { - val community = postView.community Column(modifier = modifier) { Row( modifier = Modifier.fillMaxWidth(), @@ -151,22 +153,13 @@ fun PostHeaderLine( modifier = Modifier.weight(1f), ) { if (showCommunityName && showAvatar) { - community.icon?.let { - CircularIcon( - icon = it, - contentDescription = stringResource(R.string.postListing_goToCommunity), - size = MEDIUM_ICON_SIZE, - modifier = Modifier.clickable { onCommunityClick(community) }, - thumbnailSize = LARGER_ICON_THUMBNAIL_SIZE, - blur = blurNSFW.needBlur(community.nsfw), - ) - } + CommunityIcon(community, onCommunityClick, blurNSFW) } Column { if (showCommunityName) { CommunityName( - community = postView.community, - modifier = Modifier.clickable { onCommunityClick(community) }, + community = community, + onClick = { onCommunityClick(community) }, ) } Row( @@ -174,16 +167,16 @@ fun PostHeaderLine( horizontalArrangement = Arrangement.spacedBy(SMALL_PADDING), ) { PersonProfileLink( - person = postView.creator, + person = creator, onClick = onPersonClick, showTags = fullBody, // Set this to false, we already know this isPostCreator = false, - isCommunityBanned = postView.creator_banned_from_community, + isCommunityBanned = creatorBannedFromCommunity, color = MaterialTheme.colorScheme.onSurface.muted, showAvatar = !showCommunityName && showAvatar, ) - if (postView.post.featured_local) { + if (post.featured_local) { DotSpacer() Icon( imageVector = Icons.Outlined.PushPin, @@ -192,7 +185,7 @@ fun PostHeaderLine( modifier = Modifier.size(ACTION_BAR_ICON_SIZE), ) } - if (postView.post.featured_community) { + if (post.featured_community) { DotSpacer() Icon( imageVector = Icons.Outlined.PushPin, @@ -201,7 +194,7 @@ fun PostHeaderLine( modifier = Modifier.size(ACTION_BAR_ICON_SIZE), ) } - if (postView.post.locked) { + if (post.locked) { DotSpacer() Icon( imageVector = Icons.Outlined.CommentsDisabled, @@ -212,14 +205,14 @@ fun PostHeaderLine( } } } - if (postView.post.deleted) { + if (post.deleted) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = stringResource(R.string.postListing_deleted), tint = MaterialTheme.colorScheme.error, ) } - if (postView.post.removed) { + if (post.removed) { Icon( imageVector = Icons.Outlined.Gavel, contentDescription = stringResource(R.string.removed), @@ -229,21 +222,42 @@ fun PostHeaderLine( } ScoreAndTime( instantScores = instantScores, - published = postView.post.published, - updated = postView.post.updated, - isNsfw = nsfwCheck(postView), + published = post.published, + updated = post.updated, + isNsfw = nsfwCheck(post, community), voteDisplayMode = voteDisplayMode, ) } } } +@Composable +fun CommunityIcon( + community: Community, + onCommunityClick: (community: Community) -> Unit, + blurNSFW: BlurNSFW, +) { + community.icon?.let { + CircularIcon( + icon = it, + contentDescription = stringResource(R.string.postListing_goToCommunity), + size = MEDIUM_ICON_SIZE, + modifier = Modifier.clickable { onCommunityClick(community) }, + thumbnailSize = LARGER_ICON_THUMBNAIL_SIZE, + blur = blurNSFW.needBlur(community.nsfw), + ) + } +} + @Preview @Composable fun PostHeaderLinePreview() { val postView = sampleLinkPostView PostHeaderLine( - postView = postView, + post = postView.post, + community = postView.community, + creator = postView.creator, + creatorBannedFromCommunity = postView.creator_banned_from_community, instantScores = InstantScores( myVote = 0, score = 10, @@ -286,31 +300,34 @@ fun PostNodeHeader( @Composable fun PostTitleBlock( - postView: PostView, + post: Post, + read: Boolean, expandedImage: Boolean, account: Account, useCustomTabs: Boolean, usePrivateTabs: Boolean, - blurNSFW: BlurNSFW, + blurEnabled: Boolean, appState: JerboaAppState, showIfRead: Boolean, ) { - val imagePost = postView.post.url?.let { getPostType(it) == PostType.Image } ?: false + val imagePost = post.url?.let { getPostType(it) == PostType.Image } ?: false if (imagePost && expandedImage) { PostTitleAndImageLink( - postView = postView, - blurNSFW = blurNSFW, + post = post, + read = read, appState = appState, + blurEnabled = blurEnabled, showIfRead = showIfRead, ) } else { PostTitleAndThumbnail( - postView = postView, + post = post, + read = read, account = account, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, + blurEnabled = blurEnabled, appState = appState, showIfRead = showIfRead, ) @@ -319,24 +336,25 @@ fun PostTitleBlock( @Composable fun PostName( - postView: PostView, + post: Post, + read: Boolean, showIfRead: Boolean, ) { var color = - if (postView.post.featured_local) { + if (post.featured_local) { MaterialTheme.colorScheme.primary - } else if (postView.post.featured_community) { + } else if (post.featured_community) { MaterialTheme.colorScheme.secondary } else { MaterialTheme.colorScheme.onSurface } - if (showIfRead && postView.read) { + if (showIfRead && read) { color = color.muted } Text( - text = postView.post.name, + text = post.name, style = MaterialTheme.typography.headlineMedium, color = color, modifier = Modifier.testTag("jerboa:posttitle"), @@ -346,24 +364,25 @@ fun PostName( @OptIn(ExperimentalFoundationApi::class) @Composable fun PostTitleAndImageLink( - postView: PostView, - blurNSFW: BlurNSFW, + post: Post, + read: Boolean, + blurEnabled: Boolean, appState: JerboaAppState, showIfRead: Boolean, ) { // This was tested, we know it exists - val url = postView.post.url?.toHttps() + val url = post.url?.toHttps() Column( - modifier = - Modifier.padding( - vertical = MEDIUM_PADDING, - horizontal = MEDIUM_PADDING, - ), + modifier = Modifier.padding( + vertical = MEDIUM_PADDING, + horizontal = MEDIUM_PADDING, + ), ) { // Title of the post PostName( - postView = postView, + post = post, + read = read, showIfRead = showIfRead, ) } @@ -371,8 +390,8 @@ fun PostTitleAndImageLink( url?.let { cUrl -> PictrsUrlImage( url = cUrl, - blur = blurNSFW.needBlur(postView), - contentDescription = postView.post.alt_text, + blur = blurEnabled, + contentDescription = post.alt_text, modifier = Modifier .combinedClickable( @@ -385,11 +404,12 @@ fun PostTitleAndImageLink( @Composable fun PostTitleAndThumbnail( - postView: PostView, + post: Post, + read: Boolean, account: Account, useCustomTabs: Boolean, usePrivateTabs: Boolean, - blurNSFW: BlurNSFW, + blurEnabled: Boolean, appState: JerboaAppState, showIfRead: Boolean, ) { @@ -404,8 +424,8 @@ fun PostTitleAndThumbnail( verticalArrangement = Arrangement.spacedBy(MEDIUM_PADDING), modifier = Modifier.weight(1f), ) { - PostName(postView = postView, showIfRead = showIfRead) - postView.post.url?.also { postUrl -> + PostName(post = post, read = read, showIfRead = showIfRead) + post.url?.also { postUrl -> if (!isSameInstance(postUrl, account.instance)) { val hostName = hostName(postUrl) hostName?.also { @@ -419,10 +439,10 @@ fun PostTitleAndThumbnail( } } ThumbnailTile( - postView = postView, + post = post, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, + blurEnabled = blurEnabled, appState = appState, ) } @@ -431,30 +451,31 @@ fun PostTitleAndThumbnail( @Composable fun PostBody( - postView: PostView, + post: Post, + read: Boolean, fullBody: Boolean, viewSource: Boolean, expandedImage: Boolean, account: Account, useCustomTabs: Boolean, usePrivateTabs: Boolean, - blurNSFW: BlurNSFW, + blurEnabled: Boolean, showPostLinkPreview: Boolean, appState: JerboaAppState, clickBody: () -> Unit = {}, showIfRead: Boolean, ) { - val post = postView.post Column( verticalArrangement = Arrangement.spacedBy(MEDIUM_PADDING), ) { PostTitleBlock( - postView = postView, + post = post, + read = read, expandedImage = expandedImage, account = account, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, + blurEnabled = blurEnabled, appState = appState, showIfRead = showIfRead, ) @@ -516,14 +537,15 @@ fun PostBody( @Composable fun PreviewStoryTitleAndMetadata() { PostBody( - postView = samplePostView, + post = samplePostView.post, + read = samplePostView.read, fullBody = false, viewSource = false, expandedImage = false, account = AnonAccount, useCustomTabs = false, usePrivateTabs = false, - blurNSFW = BlurNSFW.NSFW, + blurEnabled = BlurNSFW.NSFW.needBlur(samplePostView), showPostLinkPreview = true, appState = rememberJerboaAppState(), showIfRead = true, @@ -533,15 +555,17 @@ fun PreviewStoryTitleAndMetadata() { @Preview @Composable fun PreviewSourcePost() { + val pv = sampleMarkdownPostView PostBody( - postView = sampleMarkdownPostView, + post = pv.post, + read = pv.read, fullBody = true, viewSource = true, expandedImage = false, account = AnonAccount, useCustomTabs = false, usePrivateTabs = false, - blurNSFW = BlurNSFW.NSFW, + blurEnabled = BlurNSFW.NSFW.needBlur(pv), showPostLinkPreview = true, appState = rememberJerboaAppState(), showIfRead = true, @@ -691,27 +715,10 @@ fun PostFooterLine( account = account, ) } - ActionBarButton( - icon = - if (postView.saved) { - Icons.Filled.Bookmark - } else { - Icons.Outlined.BookmarkBorder - }, - contentDescription = - if (postView.saved) { - stringResource(R.string.removeBookmark) - } else { - stringResource(R.string.addBookmark) - }, - onClick = { onSaveClick(postView) }, - contentColor = - if (postView.saved) { - MaterialTheme.colorScheme.primary - } else { - MaterialTheme.colorScheme.onBackground.muted - }, + SavedButton( + saved = postView.saved, account = account, + onSaveClick = { onSaveClick(postView) }, ) ActionBarButton( icon = Icons.Outlined.MoreVert, @@ -739,6 +746,33 @@ fun PostFooterLine( } } +@Composable +fun SavedButton( + saved: Boolean, + account: Account, + onSaveClick: () -> Unit, +) { + ActionBarButton( + icon = if (saved) { + Icons.Filled.Bookmark + } else { + Icons.Outlined.BookmarkBorder + }, + contentDescription = if (saved) { + stringResource(R.string.removeBookmark) + } else { + stringResource(R.string.addBookmark) + }, + onClick = onSaveClick, + contentColor = if (saved) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onBackground.muted + }, + account = account, + ) +} + @Composable fun CommentNewCountRework( comments: Long, @@ -1090,17 +1124,30 @@ fun PostListing( ) { val ctx = LocalContext.current // This stores vote data - var instantScores by - remember { - mutableStateOf( - InstantScores( - myVote = postView.my_vote, - score = postView.counts.score, - upvotes = postView.counts.upvotes, - downvotes = postView.counts.downvotes, - ), - ) + var instantScores by remember { + mutableStateOf( + InstantScores( + myVote = postView.my_vote, + score = postView.counts.score, + upvotes = postView.counts.upvotes, + downvotes = postView.counts.downvotes, + ), + ) + } + + val upvoteClick = remember(postView, instantScores) { + { + instantScores = instantScores.update(VoteType.Upvote) + onUpvoteClick(postView) } + } + + val downvoteClick = remember(postView, instantScores) { + { + instantScores = instantScores.update(VoteType.Downvote) + onDownvoteClick(postView) + } + } var viewSource by remember { mutableStateOf(false) } @@ -1108,25 +1155,10 @@ fun PostListing( { if (account.isReadyAndIfNotShowSimplifiedInfoToast(ctx)) { when (it) { - SwipeToActionType.Upvote -> { - instantScores = - instantScores.update(VoteType.Upvote) - onUpvoteClick(postView) - } - - SwipeToActionType.Downvote -> { - instantScores = - instantScores.update(VoteType.Downvote) - onDownvoteClick(postView) - } - - SwipeToActionType.Reply -> { - onReplyClick(postView) - } - - SwipeToActionType.Save -> { - onSaveClick(postView) - } + SwipeToActionType.Upvote -> upvoteClick() + SwipeToActionType.Downvote -> downvoteClick() + SwipeToActionType.Reply -> onReplyClick(postView) + SwipeToActionType.Save -> onSaveClick(postView) } } } @@ -1148,14 +1180,8 @@ fun PostListing( admins = admins, moderators = moderators, instantScores = instantScores, - onUpvoteClick = { - instantScores = instantScores.update(VoteType.Upvote) - onUpvoteClick(postView) - }, - onDownvoteClick = { - instantScores = instantScores.update(VoteType.Downvote) - onDownvoteClick(postView) - }, + onUpvoteClick = upvoteClick, + onDownvoteClick = downvoteClick, onReplyClick = onReplyClick, onPostClick = onPostClick, onSaveClick = onSaveClick, @@ -1198,14 +1224,8 @@ fun PostListing( admins = admins, moderators = moderators, instantScores = instantScores, - onUpvoteClick = { - instantScores = instantScores.update(VoteType.Upvote) - onUpvoteClick(postView) - }, - onDownvoteClick = { - instantScores = instantScores.update(VoteType.Downvote) - onDownvoteClick(postView) - }, + onUpvoteClick = upvoteClick, + onDownvoteClick = downvoteClick, onReplyClick = onReplyClick, onPostClick = onPostClick, onSaveClick = onSaveClick, @@ -1245,14 +1265,8 @@ fun PostListing( PostListingList( postView = postView, instantScores = instantScores, - onUpvoteClick = { - instantScores = instantScores.update(VoteType.Upvote) - onUpvoteClick(postView) - }, - onDownvoteClick = { - instantScores = instantScores.update(VoteType.Downvote) - onDownvoteClick(postView) - }, + onUpvoteClick = upvoteClick, + onDownvoteClick = downvoteClick, onPostClick = onPostClick, showCommunityName = showCommunityName, account = account, @@ -1386,7 +1400,7 @@ fun PostListingList( .clickable { onPostClick(postView) }, verticalArrangement = Arrangement.spacedBy(SMALL_PADDING), ) { - PostName(postView = postView, showIfRead = showIfRead) + PostName(post = postView.post, read = postView.read, showIfRead = showIfRead) FlowRow( horizontalArrangement = Arrangement.spacedBy(SMALLER_PADDING, Alignment.Start), ) { @@ -1463,10 +1477,10 @@ fun PostListingList( } } ThumbnailTile( - postView = postView, + post = postView.post, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, + blurEnabled = blurNSFW.needBlur(postView), appState = appState, ) } @@ -1476,42 +1490,41 @@ fun PostListingList( @OptIn(ExperimentalFoundationApi::class) @Composable private fun ThumbnailTile( - postView: PostView, + post: Post, useCustomTabs: Boolean, usePrivateTabs: Boolean, - blurNSFW: BlurNSFW, + blurEnabled: Boolean, appState: JerboaAppState, ) { - postView.post.url?.also { url -> + post.url?.also { url -> val postType = getPostType(url) - val postLinkPicMod = - Modifier - .size(POST_LINK_PIC_SIZE) - .combinedClickable( - onClick = { - if (postType != PostType.Link) { - appState.openImageViewer(url) - } else { - appState.openLink( - url, - useCustomTabs, - usePrivateTabs, - ) - } - }, - onLongClick = { - appState.showLinkPopup(url) - }, - ) + val postLinkPicMod = Modifier + .size(POST_LINK_PIC_SIZE) + .combinedClickable( + onClick = { + if (postType != PostType.Link) { + appState.openImageViewer(url) + } else { + appState.openLink( + url, + useCustomTabs, + usePrivateTabs, + ) + } + }, + onLongClick = { + appState.showLinkPopup(url) + }, + ) Box { - postView.post.thumbnail_url?.also { thumbnail -> + post.thumbnail_url?.also { thumbnail -> PictrsThumbnailImage( thumbnail = thumbnail, - blur = blurNSFW.needBlur(postView), + blur = blurEnabled, roundBottomEndCorner = postType != PostType.Link, - contentDescription = postView.post.alt_text, + contentDescription = post.alt_text, modifier = postLinkPicMod, ) } ?: run { @@ -1663,7 +1676,10 @@ fun PostListingCard( ) { // Header PostHeaderLine( - postView = postView, + post = postView.post, + creator = postView.creator, + community = postView.community, + creatorBannedFromCommunity = postView.creator_banned_from_community, instantScores = instantScores, onCommunityClick = onCommunityClick, onPersonClick = onPersonClick, @@ -1679,14 +1695,15 @@ fun PostListingCard( // Title + metadata PostBody( - postView = postView, + post = postView.post, + read = postView.read, fullBody = fullBody, viewSource = viewSource, expandedImage = expandedImage, account = account, useCustomTabs = useCustomTabs, usePrivateTabs = usePrivateTabs, - blurNSFW = blurNSFW, + blurEnabled = blurNSFW.needBlur(postView), showPostLinkPreview = showPostLinkPreview, appState = appState, clickBody = { onPostClick(postView) }, 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 3ae510cca..4d564d2d7 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 @@ -7,10 +7,6 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview @@ -26,10 +22,9 @@ import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.BlurNSFW import com.jerboa.feat.PostActionBarMode import com.jerboa.feat.SwipeToActionPreset -import com.jerboa.isScrolledToEnd import com.jerboa.rememberJerboaAppState import com.jerboa.ui.components.common.RetryLoadingPosts -import com.jerboa.ui.components.common.simpleVerticalScrollbar +import com.jerboa.ui.components.common.TriggerWhenReachingEnd import it.vercruysse.lemmyapi.v0x19.datatypes.Community import it.vercruysse.lemmyapi.v0x19.datatypes.Person import it.vercruysse.lemmyapi.v0x19.datatypes.PersonId @@ -83,11 +78,9 @@ fun PostListings( ) { LazyColumn( state = listState, - modifier = - Modifier - .fillMaxSize() - .simpleVerticalScrollbar(listState) - .testTag("jerboa:posts"), + modifier = Modifier + .fillMaxSize() + .testTag("jerboa:posts"), ) { item(contentType = "aboveContent") { contentAboveListings() @@ -154,19 +147,7 @@ fun PostListings( } } - // observer when reached end of list - val endOfListReached by remember { - derivedStateOf { - listState.isScrolledToEnd() - } - } - - // Act when end of list reached - if (endOfListReached && !showPostAppendRetry) { - LaunchedEffect(Unit) { - loadMorePosts() - } - } + TriggerWhenReachingEnd(listState, loadMorePosts, showPostAppendRetry) } @Preview diff --git a/app/src/main/java/com/jerboa/ui/components/reports/PostReportItem.kt b/app/src/main/java/com/jerboa/ui/components/reports/PostReportItem.kt index 4a432e39b..cdd487534 100644 --- a/app/src/main/java/com/jerboa/ui/components/reports/PostReportItem.kt +++ b/app/src/main/java/com/jerboa/ui/components/reports/PostReportItem.kt @@ -16,6 +16,7 @@ import com.jerboa.db.entity.Account import com.jerboa.db.entity.AnonAccount import com.jerboa.feat.BlurNSFW import com.jerboa.feat.InstantScores +import com.jerboa.feat.needBlur import com.jerboa.rememberJerboaAppState import com.jerboa.ui.components.post.PostBody import com.jerboa.ui.components.post.PostHeaderLine @@ -83,7 +84,10 @@ fun PostReportItem( .clickable { onPostClick(postView) }, ) { PostHeaderLine( - postView = postView, + post = postView.post, + creator = postView.creator, + community = postView.community, + creatorBannedFromCommunity = postView.creator_banned_from_community, instantScores = InstantScores( myVote = postView.my_vote, score = postView.counts.score, @@ -102,14 +106,15 @@ fun PostReportItem( // Title + metadata PostBody( - postView = postView, + post = postView.post, + read = postView.read, fullBody = false, viewSource = false, expandedImage = false, account = account, useCustomTabs = false, usePrivateTabs = false, - blurNSFW = blurNSFW, + blurEnabled = blurNSFW.needBlur(postView), showPostLinkPreview = true, appState = appState, clickBody = { onPostClick(postView) }, diff --git a/compose_compiler_config.conf b/compose_compiler_config.conf index b14c28154..77a2a4e42 100644 --- a/compose_compiler_config.conf +++ b/compose_compiler_config.conf @@ -7,4 +7,7 @@ it.vercruysse.lemmyapi.*.datatypes.** arrow.core.Either // Consider List, etc stable, see https://github.com/LemmyNet/jerboa/issues/1332 -kotlin.collections.* \ No newline at end of file +kotlin.collections.* + +// TODO check if still needed in PostOptionsDropdown +kotlinx.coroutines.CoroutineScope