mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-15 02:57:04 +05:30
fix: handle user profile navigation correctly
This commit is contained in:
parent
2b9680d3d8
commit
d0bf2a4fb2
7 changed files with 80 additions and 14 deletions
|
@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
* Fixed a crash when clicking an item on the bottom navigation bar
|
* Fixed a crash when clicking an item on the bottom navigation bar
|
||||||
too quickly
|
too quickly
|
||||||
* Removed buggy deeplinks
|
* Removed buggy deeplinks
|
||||||
|
* Clicking a username now correctly navigates to the right page in-app
|
||||||
|
|
||||||
## [1.44.0] - 2024-03-19
|
## [1.44.0] - 2024-03-19
|
||||||
|
|
||||||
|
|
|
@ -261,6 +261,11 @@ fun LobstersPostsScreen(
|
||||||
htmlConverter = htmlConverter,
|
htmlConverter = htmlConverter,
|
||||||
getSeenComments = viewModel::getSeenComments,
|
getSeenComments = viewModel::getSeenComments,
|
||||||
markSeenComments = viewModel::markSeenComments,
|
markSeenComments = viewModel::markSeenComments,
|
||||||
|
openUserProfile = {
|
||||||
|
navController.navigate(
|
||||||
|
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(
|
composable(
|
||||||
|
@ -272,7 +277,15 @@ fun LobstersPostsScreen(
|
||||||
"Navigating to ${Destinations.User.route} without necessary 'username' argument"
|
"Navigating to ${Destinations.User.route} without necessary 'username' argument"
|
||||||
}
|
}
|
||||||
setWebUri("https://lobste.rs/u/$username")
|
setWebUri("https://lobste.rs/u/$username")
|
||||||
UserProfile(username = username, getProfile = viewModel::getUserProfile)
|
UserProfile(
|
||||||
|
username = username,
|
||||||
|
getProfile = viewModel::getUserProfile,
|
||||||
|
openUserProfile = {
|
||||||
|
navController.navigate(
|
||||||
|
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, it)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(route = Destinations.Settings.route) {
|
composable(route = Destinations.Settings.route) {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
|
|
|
@ -67,6 +67,11 @@ fun SearchScreen(
|
||||||
htmlConverter = htmlConverter,
|
htmlConverter = htmlConverter,
|
||||||
getSeenComments = viewModel::getSeenComments,
|
getSeenComments = viewModel::getSeenComments,
|
||||||
markSeenComments = viewModel::markSeenComments,
|
markSeenComments = viewModel::markSeenComments,
|
||||||
|
openUserProfile = { username: String ->
|
||||||
|
navController.navigate(
|
||||||
|
Destinations.User.route.replace(Destinations.User.PLACEHOLDER, username)
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,6 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
@ -56,9 +55,9 @@ internal fun CommentsHeader(
|
||||||
post: UIPost,
|
post: UIPost,
|
||||||
postActions: PostActions,
|
postActions: PostActions,
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
val linkMetadata by
|
val linkMetadata by
|
||||||
produceState(initialValue = LinkMetadata(post.url, null)) {
|
produceState(initialValue = LinkMetadata(post.url, null)) {
|
||||||
runSuspendCatching { postActions.getLinkMetadata(post.url) }
|
runSuspendCatching { postActions.getLinkMetadata(post.url) }
|
||||||
|
@ -93,8 +92,7 @@ internal fun CommentsHeader(
|
||||||
text = AnnotatedString("Submitted by ${post.submitter}"),
|
text = AnnotatedString("Submitted by ${post.submitter}"),
|
||||||
avatarUrl = "https://lobste.rs/avatars/${post.submitter}-100.png",
|
avatarUrl = "https://lobste.rs/avatars/${post.submitter}-100.png",
|
||||||
contentDescription = "User avatar for ${post.submitter}",
|
contentDescription = "User avatar for ${post.submitter}",
|
||||||
modifier =
|
modifier = Modifier.clickable { openUserProfile(post.submitter) },
|
||||||
Modifier.clickable { uriHandler.openUri("https://lobste.rs/u/${post.submitter}") },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,9 +131,9 @@ internal fun CommentEntry(
|
||||||
commentNode: CommentNode,
|
commentNode: CommentNode,
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
toggleExpanded: (CommentNode) -> Unit,
|
toggleExpanded: (CommentNode) -> Unit,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val uriHandler = LocalUriHandler.current
|
|
||||||
val comment = commentNode.comment
|
val comment = commentNode.comment
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
|
@ -162,7 +160,7 @@ internal fun CommentEntry(
|
||||||
),
|
),
|
||||||
avatarUrl = "https://lobste.rs/avatars/${comment.user}-100.png",
|
avatarUrl = "https://lobste.rs/avatars/${comment.user}-100.png",
|
||||||
contentDescription = "User avatar for ${comment.user}",
|
contentDescription = "User avatar for ${comment.user}",
|
||||||
modifier = Modifier.clickable { uriHandler.openUri("https://lobste.rs/u/${comment.user}") },
|
modifier = Modifier.clickable { openUserProfile(comment.user) },
|
||||||
)
|
)
|
||||||
if (commentNode.isExpanded) {
|
if (commentNode.isExpanded) {
|
||||||
ThemedRichText(
|
ThemedRichText(
|
||||||
|
|
|
@ -84,9 +84,15 @@ internal fun LazyListScope.nodes(
|
||||||
nodes: List<CommentNode>,
|
nodes: List<CommentNode>,
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
toggleExpanded: (CommentNode) -> Unit,
|
toggleExpanded: (CommentNode) -> Unit,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
nodes.forEach { node ->
|
nodes.forEach { node ->
|
||||||
node(node = node, htmlConverter = htmlConverter, toggleExpanded = toggleExpanded)
|
node(
|
||||||
|
node = node,
|
||||||
|
htmlConverter = htmlConverter,
|
||||||
|
toggleExpanded = toggleExpanded,
|
||||||
|
openUserProfile = openUserProfile,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,16 +100,27 @@ private fun LazyListScope.node(
|
||||||
node: CommentNode,
|
node: CommentNode,
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
toggleExpanded: (CommentNode) -> Unit,
|
toggleExpanded: (CommentNode) -> Unit,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
// Skip the node if neither the node nor its parent is expanded
|
// Skip the node if neither the node nor its parent is expanded
|
||||||
if (!node.isExpanded && node.parent?.isExpanded == false) {
|
if (!node.isExpanded && node.parent?.isExpanded == false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
CommentEntry(commentNode = node, htmlConverter = htmlConverter, toggleExpanded = toggleExpanded)
|
CommentEntry(
|
||||||
|
commentNode = node,
|
||||||
|
htmlConverter = htmlConverter,
|
||||||
|
toggleExpanded = toggleExpanded,
|
||||||
|
openUserProfile = openUserProfile,
|
||||||
|
)
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
if (node.children.isNotEmpty()) {
|
if (node.children.isNotEmpty()) {
|
||||||
nodes(node.children, htmlConverter = htmlConverter, toggleExpanded = toggleExpanded)
|
nodes(
|
||||||
|
node.children,
|
||||||
|
htmlConverter = htmlConverter,
|
||||||
|
toggleExpanded = toggleExpanded,
|
||||||
|
openUserProfile = openUserProfile,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ private fun CommentsPageInternal(
|
||||||
htmlConverter: HTMLConverter,
|
htmlConverter: HTMLConverter,
|
||||||
commentState: PostComments?,
|
commentState: PostComments?,
|
||||||
markSeenComments: (String, List<Comment>) -> Unit,
|
markSeenComments: (String, List<Comment>) -> Unit,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val commentNodes = createListNode(details.comments, commentState).toMutableStateList()
|
val commentNodes = createListNode(details.comments, commentState).toMutableStateList()
|
||||||
|
@ -54,7 +55,12 @@ private fun CommentsPageInternal(
|
||||||
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
Surface(color = MaterialTheme.colorScheme.surfaceVariant) {
|
||||||
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
LazyColumn(modifier = modifier, contentPadding = PaddingValues(bottom = 24.dp)) {
|
||||||
item {
|
item {
|
||||||
CommentsHeader(post = details, postActions = postActions, htmlConverter = htmlConverter)
|
CommentsHeader(
|
||||||
|
post = details,
|
||||||
|
postActions = postActions,
|
||||||
|
htmlConverter = htmlConverter,
|
||||||
|
openUserProfile = openUserProfile,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commentNodes.isNotEmpty()) {
|
if (commentNodes.isNotEmpty()) {
|
||||||
|
@ -79,6 +85,7 @@ private fun CommentsPageInternal(
|
||||||
commentNodes.add(index, parent)
|
commentNodes.add(index, parent)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
openUserProfile = openUserProfile,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
item {
|
item {
|
||||||
|
@ -104,6 +111,7 @@ fun CommentsPage(
|
||||||
getSeenComments: suspend (String) -> PostComments?,
|
getSeenComments: suspend (String) -> PostComments?,
|
||||||
markSeenComments: (String, List<Comment>) -> Unit,
|
markSeenComments: (String, List<Comment>) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val postDetails by
|
val postDetails by
|
||||||
produceState<NetworkState>(Loading) {
|
produceState<NetworkState>(Loading) {
|
||||||
|
@ -124,6 +132,7 @@ fun CommentsPage(
|
||||||
htmlConverter = htmlConverter,
|
htmlConverter = htmlConverter,
|
||||||
commentState = commentState,
|
commentState = commentState,
|
||||||
markSeenComments = markSeenComments,
|
markSeenComments = markSeenComments,
|
||||||
|
openUserProfile = openUserProfile,
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.text.LinkAnnotation
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.github.michaelbull.result.coroutines.runSuspendCatching
|
import com.github.michaelbull.result.coroutines.runSuspendCatching
|
||||||
import com.github.michaelbull.result.fold
|
import com.github.michaelbull.result.fold
|
||||||
|
@ -42,6 +44,7 @@ import dev.msfjarvis.claw.model.User
|
||||||
fun UserProfile(
|
fun UserProfile(
|
||||||
username: String,
|
username: String,
|
||||||
getProfile: suspend (username: String) -> User,
|
getProfile: suspend (username: String) -> User,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val user by
|
val user by
|
||||||
|
@ -56,7 +59,11 @@ fun UserProfile(
|
||||||
}
|
}
|
||||||
when (user) {
|
when (user) {
|
||||||
is Success<*> -> {
|
is Success<*> -> {
|
||||||
UserProfileInternal(user = (user as Success<User>).data, modifier = modifier)
|
UserProfileInternal(
|
||||||
|
user = (user as Success<User>).data,
|
||||||
|
openUserProfile = openUserProfile,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is Error -> {
|
is Error -> {
|
||||||
val error = user as Error
|
val error = user as Error
|
||||||
|
@ -77,7 +84,11 @@ fun UserProfile(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UserProfileInternal(user: User, modifier: Modifier = Modifier) {
|
private fun UserProfileInternal(
|
||||||
|
user: User,
|
||||||
|
openUserProfile: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
Surface(modifier = modifier) {
|
Surface(modifier = modifier) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
@ -93,7 +104,19 @@ private fun UserProfileInternal(user: User, modifier: Modifier = Modifier) {
|
||||||
Text(text = user.username, style = MaterialTheme.typography.displaySmall)
|
Text(text = user.username, style = MaterialTheme.typography.displaySmall)
|
||||||
ThemedRichText(text = user.about)
|
ThemedRichText(text = user.about)
|
||||||
user.invitedBy?.let { invitedBy ->
|
user.invitedBy?.let { invitedBy ->
|
||||||
ThemedRichText(text = "Invited by [${invitedBy}](https://lobste.rs/u/${user.invitedBy})")
|
Text(
|
||||||
|
text =
|
||||||
|
buildAnnotatedString {
|
||||||
|
append("Invited by ")
|
||||||
|
pushLink(
|
||||||
|
LinkAnnotation.Clickable(
|
||||||
|
tag = "username",
|
||||||
|
linkInteractionListener = { openUserProfile(invitedBy) },
|
||||||
|
)
|
||||||
|
)
|
||||||
|
append(invitedBy)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue