diff --git a/CHANGELOG.md b/CHANGELOG.md index 0898a2540..175f3437f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- A brand new tablet-specific UI — Thanks [@ThanaReka](https://github.com/@ThanaReka) + ### Fixes - Add a workaround for rare crashes while loading SQLite with older devices diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 0039be930..5bfce4300 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -17,6 +17,7 @@ plugins { id("dev.msfjarvis.claw.kotlin-kapt") id("dev.msfjarvis.claw.sentry") id("dev.msfjarvis.claw.versioning-plugin") + id("kotlin-parcelize") alias(libs.plugins.aboutlibraries) alias(libs.plugins.android.junit5) alias(libs.plugins.anvil) @@ -89,6 +90,9 @@ dependencies { implementation(libs.androidx.compose.material.icons.extended) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3.window.size) + implementation(libs.androidx.compose.material3.adaptive) + implementation(libs.androidx.compose.material3.adaptive.layout) + implementation(libs.androidx.compose.material3.adaptive.navigation) implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.text) diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/MainActivity.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/MainActivity.kt index 2023751b9..81963eff8 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/MainActivity.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/MainActivity.kt @@ -6,12 +6,16 @@ */ package dev.msfjarvis.claw.android +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import com.deliveryhero.whetstone.activity.ContributesActivityInjector import dev.msfjarvis.claw.android.ui.screens.LobstersPostsScreen +import dev.msfjarvis.claw.android.ui.screens.TwoPaneLayout @ContributesActivityInjector class MainActivity : BaseActivity() { @@ -20,12 +24,25 @@ class MainActivity : BaseActivity() { @Composable override fun Content() { val windowSizeClass = calculateWindowSizeClass(this) - LobstersPostsScreen( - urlLauncher = urlLauncher, - htmlConverter = htmlConverter, - windowSizeClass = windowSizeClass, - setWebUri = { url -> webUri = url }, - ) + + when (windowSizeClass.widthSizeClass) { + WindowWidthSizeClass.Compact -> { + LobstersPostsScreen( + urlLauncher = urlLauncher, + htmlConverter = htmlConverter, + windowSizeClass = windowSizeClass, + setWebUri = { url -> webUri = url }, + ) + } + + else -> { + TwoPaneLayout( + urlLauncher = urlLauncher, + htmlConverter = htmlConverter, + modifier = Modifier.fillMaxSize(), + ) + } + } } override fun preLaunch() { diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt index 7de6d3ca2..e3be04653 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt @@ -80,6 +80,57 @@ fun PostActions( } } +fun TwoPaneLayoutPostActions( + context: Context, + urlLauncher: UrlLauncher, + viewModel: ClawViewModel, + setSelectedPost: (String) -> Unit, +): PostActions { + return object : PostActions { + override fun viewPost(postId: String, postUrl: String, commentsUrl: String) { + viewModel.markPostAsRead(postId) + urlLauncher.openUri(postUrl.ifEmpty { commentsUrl }) + } + + override fun viewComments(postId: String) { + viewModel.markPostAsRead(postId) + setSelectedPost(postId) // Update selectedPostId + } + + override fun viewCommentsPage(post: UIPost) { + urlLauncher.openUri(post.commentsUrl) + } + + override fun toggleSave(post: UIPost) { + viewModel.toggleSave(post) + } + + override fun share(post: UIPost) { + val sendIntent = + Intent().apply { + action = Intent.ACTION_SEND + putExtra(Intent.EXTRA_TEXT, post.url.ifEmpty { post.commentsUrl }) + putExtra(Intent.EXTRA_TITLE, post.title) + type = "text/plain" + } + val shareIntent = Intent.createChooser(sendIntent, null) + context.startActivity(shareIntent) + } + + override fun isPostRead(post: UIPost): Boolean = viewModel.isPostRead(post) + + override fun isPostSaved(post: UIPost): Boolean = viewModel.isPostSaved(post) + + override suspend fun getComments(postId: String): UIPost { + return viewModel.getPostComments(postId) + } + + override suspend fun getLinkMetadata(url: String): LinkMetadata { + return viewModel.getLinkMetadata(url) + } + } +} + /** * Convert an [ApiResult.Failure.HttpFailure] to a scoped down error with a more useful user-facing * message. diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt index 78ebc62a3..1a505e935 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPosts.kt @@ -7,6 +7,7 @@ package dev.msfjarvis.claw.android.ui.lists import androidx.activity.compose.ReportDrawnWhen +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -50,6 +51,7 @@ fun NetworkPosts( postActions: PostActions, contentPadding: PaddingValues, modifier: Modifier = Modifier, + onPostClick: (String) -> Unit, ) { ReportDrawnWhen { lazyPagingItems.itemCount > 0 } val refreshLoadState by rememberUpdatedState(lazyPagingItems.loadState.refresh) @@ -82,7 +84,14 @@ fun NetworkPosts( ) { index -> val item = lazyPagingItems[index] if (item != null) { - LobstersListItem(item = item, postActions = postActions) + LobstersListItem( + item = item, + postActions = postActions, + modifier = + Modifier.clickable { + onPostClick(item.shortId) // Trigger the click listener + }, + ) HorizontalDivider() } } @@ -112,6 +121,7 @@ private fun ListPreview() { listState = rememberLazyListState(), postActions = TEST_POST_ACTIONS, contentPadding = PaddingValues(), + onPostClick = {}, ) } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPostsforTwoPaneLayout.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPostsforTwoPaneLayout.kt new file mode 100644 index 000000000..738be5f99 --- /dev/null +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPostsforTwoPaneLayout.kt @@ -0,0 +1,127 @@ +/* + * Copyright © 2021-2024 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.android.ui.lists + +import androidx.activity.compose.ReportDrawnWhen +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.pulltorefresh.PullToRefreshBox +import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.paging.LoadState +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey +import dev.msfjarvis.claw.common.posts.PostActions +import dev.msfjarvis.claw.common.posts.TEST_POST +import dev.msfjarvis.claw.common.posts.TEST_POST_ACTIONS +import dev.msfjarvis.claw.common.theme.LobstersTheme +import dev.msfjarvis.claw.common.ui.NetworkError +import dev.msfjarvis.claw.common.ui.ProgressBar +import dev.msfjarvis.claw.common.ui.preview.DevicePreviews +import dev.msfjarvis.claw.model.UIPost +import kotlinx.coroutines.flow.MutableStateFlow + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun NetworkPostsForTwoPaneLayout( + lazyPagingItems: LazyPagingItems, + listState: LazyListState, + postActions: PostActions, + contentPadding: PaddingValues, + modifier: Modifier = Modifier, + onPostClick: (String) -> Unit, +) { + ReportDrawnWhen { lazyPagingItems.itemCount > 0 } + val refreshLoadState by rememberUpdatedState(lazyPagingItems.loadState.refresh) + val state = rememberPullToRefreshState() + PullToRefreshBox( + modifier = modifier.fillMaxSize(), + isRefreshing = refreshLoadState == LoadState.Loading, + state = state, + onRefresh = { lazyPagingItems.refresh() }, + indicator = { + PullToRefreshDefaults.Indicator( + state = state, + isRefreshing = refreshLoadState == LoadState.Loading, + modifier = Modifier.align(Alignment.TopCenter).padding(contentPadding), + ) + }, + ) { + if (lazyPagingItems.itemCount == 0 && refreshLoadState is LoadState.Error) { + NetworkError( + label = "Failed to load posts", + error = (refreshLoadState as LoadState.Error).error, + modifier = Modifier.align(Alignment.Center), + ) + } else { + LazyColumn(contentPadding = contentPadding, state = listState) { + items( + count = lazyPagingItems.itemCount, + key = lazyPagingItems.itemKey { it.shortId }, + contentType = lazyPagingItems.itemContentType { "LobstersItem" }, + ) { index -> + val item = lazyPagingItems[index] + if (item != null) { + LobstersListItem( + item = item, + postActions = postActions, + modifier = + Modifier.clickable { + onPostClick(item.shortId) // Trigger the click listener) + }, + ) + HorizontalDivider() + } + } + if (lazyPagingItems.loadState.append == LoadState.Loading) { + item(key = "progressbar") { + ProgressBar( + modifier = + Modifier.fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + .padding(vertical = 16.dp) + ) + } + } + } + } + } +} + +@DevicePreviews +@Composable +private fun ListPreview() { + val items = List(20) { TEST_POST.copy(shortId = "${TEST_POST.shortId}${it}") } + val flow = MutableStateFlow(PagingData.from(items)) + LobstersTheme { + NetworkPostsForTwoPaneLayout( + lazyPagingItems = flow.collectAsLazyPagingItems(), + listState = rememberLazyListState(), + postActions = TEST_POST_ACTIONS, + contentPadding = PaddingValues(), + onPostClick = {}, + ) + } +} diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt index e7d21bbe9..909aa917a 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/SearchList.kt @@ -33,6 +33,7 @@ fun SearchList( setSearchQuery: (String) -> Unit, contentPadding: PaddingValues, modifier: Modifier = Modifier, + onPostClick: (String) -> Unit, ) { val triggerSearch = { query: String -> setSearchQuery(query) @@ -53,6 +54,7 @@ fun SearchList( listState = listState, postActions = postActions, contentPadding = contentPadding, + onPostClick = onPostClick, ) } } diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destination.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destination.kt index bc4000aa9..8396578a3 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destination.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/navigation/Destination.kt @@ -6,22 +6,24 @@ */ package dev.msfjarvis.claw.android.ui.navigation +import android.os.Parcelable +import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable -sealed interface Destination +sealed interface Destination : Parcelable -@Serializable data object Hottest : Destination +@Parcelize @Serializable data object Hottest : Destination -@Serializable data object Newest : Destination +@Parcelize @Serializable data object Newest : Destination -@Serializable data object Saved : Destination +@Parcelize @Serializable data object Saved : Destination -@Serializable data class Comments(val postId: String) : Destination +@Parcelize @Serializable data class Comments(val postId: String) : Destination -@Serializable data class User(val username: String) : Destination +@Parcelize @Serializable data class User(val username: String) : Destination -@Serializable data object Search : Destination +@Parcelize @Serializable data object Search : Destination -@Serializable data object Settings : Destination +@Parcelize @Serializable data object Settings : Destination -@Serializable data object AboutLibraries : Destination +@Parcelize @Serializable data object AboutLibraries : Destination diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt index 8919e5a41..6bfd00aae 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/LobstersPostsScreen.kt @@ -167,7 +167,7 @@ fun LobstersPostsScreen( navController.previousBackStackEntry != null && currentDestination.none(navDestinations) ) { IconButton( - onClick = { if (!navController.navigateUp()) context.getActivity()?.finish() } + onClick = { if (!navController.popBackStack()) context.getActivity()?.finish() } ) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, @@ -238,6 +238,7 @@ fun LobstersPostsScreen( listState = hottestListState, postActions = postActions, contentPadding = contentPadding, + onPostClick = { postId -> navController.navigate(Comments(postId)) }, ) } composable { @@ -247,6 +248,7 @@ fun LobstersPostsScreen( listState = newestListState, postActions = postActions, contentPadding = contentPadding, + onPostClick = { postId -> navController.navigate(Comments(postId)) }, ) } composable { diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt index 2b55eb77f..ececc2fc0 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/SearchScreen.kt @@ -42,6 +42,8 @@ fun SearchScreen( val postActions = remember { PostActions(context, urlLauncher, navController, viewModel) } val listState = rememberLazyListState() val searchResults = viewModel.searchResults.collectAsLazyPagingItems() + + val onPostClick: (String) -> Unit = { postId -> navController.navigate("comments/$postId") } Scaffold(modifier = modifier) { contentPadding -> NavHost(navController = navController, startDestination = Search) { composable { @@ -53,6 +55,7 @@ fun SearchScreen( searchQuery = viewModel.searchQuery, contentPadding = contentPadding, setSearchQuery = { query -> viewModel.searchQuery = query }, + onPostClick = onPostClick, ) } composable { backStackEntry -> diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TwoPaneLayout.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TwoPaneLayout.kt new file mode 100644 index 000000000..6c313d8f3 --- /dev/null +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/screens/TwoPaneLayout.kt @@ -0,0 +1,125 @@ +/* + * Copyright © 2024 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.android.ui.screens + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.navigation.NavigableListDetailPaneScaffold +import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.navigation.compose.rememberNavController +import androidx.paging.compose.collectAsLazyPagingItems +import com.deliveryhero.whetstone.compose.injectedViewModel +import dev.msfjarvis.claw.android.R +import dev.msfjarvis.claw.android.ui.TwoPaneLayoutPostActions +import dev.msfjarvis.claw.android.ui.lists.NetworkPostsForTwoPaneLayout +import dev.msfjarvis.claw.android.ui.navigation.User +import dev.msfjarvis.claw.android.viewmodel.ClawViewModel +import dev.msfjarvis.claw.common.comments.CommentsPage +import dev.msfjarvis.claw.common.comments.HTMLConverter +import dev.msfjarvis.claw.common.urllauncher.UrlLauncher + +@OptIn(ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterial3Api::class) +@Composable +fun TwoPaneLayout( + urlLauncher: UrlLauncher, + htmlConverter: HTMLConverter, + modifier: Modifier = Modifier, + viewModel: ClawViewModel = injectedViewModel(), +) { + + val context = LocalContext.current + val hottestListState = rememberLazyListState() + val navController = rememberNavController() + + // Track the selected postId as a mutable state + var selectedPostId by remember { mutableStateOf(null) } + + // Initialize postActions with selectedPostId as a mutable state + val postActions = remember { + TwoPaneLayoutPostActions(context, urlLauncher, viewModel, { selectedPostId = it }) + } + + val hottestPosts = viewModel.hottestPosts.collectAsLazyPagingItems() + + // Navigator state + val navigator = rememberListDetailPaneScaffoldNavigator() + + Scaffold( + topBar = { + TopAppBar( + navigationIcon = { + Icon( + painter = painterResource(id = R.drawable.ic_launcher_foreground), + contentDescription = "The app icon for Claw", + modifier = Modifier.size(48.dp), + ) + }, + title = { Text(text = stringResource(R.string.app_name), fontWeight = FontWeight.Bold) }, + ) + }, + content = { paddingValues -> + NavigableListDetailPaneScaffold( + modifier = modifier.padding(paddingValues), + navigator = navigator, + listPane = { + NetworkPostsForTwoPaneLayout( + lazyPagingItems = hottestPosts, + listState = hottestListState, + postActions = postActions, + contentPadding = PaddingValues(), + modifier = Modifier.fillMaxSize(), + ) { postId -> + selectedPostId = postId // Update selectedPostId on click + } + }, + detailPane = { + selectedPostId?.let { postId -> + CommentsPage( + postId = postId, + postActions = postActions, + htmlConverter = htmlConverter, + getSeenComments = viewModel::getSeenComments, + markSeenComments = viewModel::markSeenComments, + openUserProfile = { navController.navigate(User(it)) }, + contentPadding = PaddingValues(), + modifier = Modifier.fillMaxSize(), + ) + } + ?: Box(Modifier.fillMaxSize()) { + // Placeholder when no post is selected + Text( + text = "Select a post to view comments", + modifier = Modifier.align(Alignment.Center), + ) + } + }, + ) + }, + ) +} diff --git a/android/src/screenshotTest/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPostsTest.kt b/android/src/screenshotTest/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPostsTest.kt index 9520ec17e..d4757e077 100644 --- a/android/src/screenshotTest/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPostsTest.kt +++ b/android/src/screenshotTest/kotlin/dev/msfjarvis/claw/android/ui/lists/NetworkPostsTest.kt @@ -31,6 +31,7 @@ class NetworkPostsTest { listState = rememberLazyListState(), postActions = TEST_POST_ACTIONS, contentPadding = PaddingValues(), + onPostClick = {}, ) } } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 6ed5a3619..33177705b 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.material.icons.extended) implementation(libs.androidx.compose.material3) + implementation(libs.androidx.compose.material3.adaptive) implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.ui.text) implementation(libs.androidx.core) diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt index 17cc2921a..71d6ca29f 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentEntry.kt @@ -51,7 +51,7 @@ internal fun CommentsHeader( modifier: Modifier = Modifier, ) { val linkMetadata by - produceState(initialValue = LinkMetadata(post.url, null)) { + produceState(initialValue = LinkMetadata(post.url, null), key1 = post) { runSuspendCatching { postActions.getLinkMetadata(post.url) } .onSuccess { metadata -> value = metadata } } diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt index ef1a75352..ea49c1db9 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPage.kt @@ -40,7 +40,7 @@ fun CommentsPage( openUserProfile: (String) -> Unit, ) { val postDetails by - produceState(Loading) { + produceState(Loading, key1 = postId) { runSuspendCatching { postActions.getComments(postId) } .fold( success = { details -> value = Success(details) }, @@ -48,7 +48,9 @@ fun CommentsPage( ) } val commentState by - produceState(initialValue = null) { value = getSeenComments(postId) } + produceState(initialValue = null, key1 = postId) { + value = getSeenComments(postId) + } when (postDetails) { is Success<*> -> { @@ -60,7 +62,6 @@ fun CommentsPage( markSeenComments = markSeenComments, openUserProfile = openUserProfile, contentPadding = contentPadding, - commentsHandler = CommentsHandler(), modifier = modifier.fillMaxSize(), ) } diff --git a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt index bb923b75a..8e2906578 100644 --- a/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt +++ b/common/src/main/kotlin/dev/msfjarvis/claw/common/comments/CommentsPageImpl.kt @@ -64,11 +64,12 @@ internal fun CommentsPageInternal( markSeenComments: (String, List) -> Unit, openUserProfile: (String) -> Unit, contentPadding: PaddingValues, - commentsHandler: CommentsHandler, modifier: Modifier = Modifier, ) { - - LaunchedEffect(Unit) { commentsHandler.createListNode(details.comments, commentState) } + val commentsHandler = CommentsHandler() + LaunchedEffect(key1 = details, key2 = commentState) { + commentsHandler.createListNode(details.comments, commentState) + } val onToggleExpandedState = { shortId: String, isExpanded: Boolean -> commentsHandler.updateListNode(shortId, isExpanded) @@ -77,7 +78,7 @@ internal fun CommentsPageInternal( val context = LocalContext.current val commentNodes by commentsHandler.listItems.collectAsStateWithLifecycle() - LaunchedEffect(key1 = commentNodes) { + LaunchedEffect(key1 = details, key2 = commentState) { if (details.comments.isNotEmpty() && !commentState?.commentIds.isNullOrEmpty()) { val unreadCount = details.comments.size - (commentState?.commentIds?.size ?: 0) if (unreadCount > 0) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4677ee95f..e651e3325 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ coil = "2.7.0" coroutines = "1.9.0" dagger = "2.52" glance = "1.0.0" -haze = "0.9.0-rc01" +haze = "0.9.0-rc02" junit = "5.11.3" konvert = "3.2.2" kotlin = "2.0.21" @@ -37,6 +37,9 @@ androidx-compose-glance-m3 = { module = "androidx.glance:glance-material3", vers androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } androidx-compose-material3 = { module = "androidx.compose.material3:material3" } androidx-compose-material3-window-size = { module = "androidx.compose.material3:material3-window-size-class" } +androidx-compose-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive"} +androidx-compose-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout"} +androidx-compose-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation"} androidx-compose-runtime = { module = "androidx.compose.runtime:runtime" } androidx-compose-ui = { module = "androidx.compose.ui:ui" } androidx-compose-ui-text = { module = "androidx.compose.ui:ui-text" }