feat: double click management (#130)

This commit is contained in:
Diego Beraldin 2023-11-13 22:17:59 +01:00 committed by GitHub
parent aecf2549fb
commit e474c9227d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 1389 additions and 725 deletions

View File

@ -26,6 +26,7 @@ object IconSize {
val s = 20.dp
val m = 26.dp
val l = 30.dp
val xl = 46.dp
val xxl = 60.dp
}

View File

@ -97,7 +97,7 @@ fun FloatingActionButtonMenu(
) {
Row(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
fabExpanded = false
item.onSelected?.invoke()
},

View File

@ -111,8 +111,8 @@ class InboxChatScreen(
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,

View File

@ -46,6 +46,7 @@ interface CommunityDetailMviModel :
val blurNsfw: Boolean = true,
val currentUserId: Int? = null,
val swipeActionsEnabled: Boolean = true,
val doubleTapActionEnabled: Boolean = false,
val postLayout: PostLayout = PostLayout.Card,
val fullHeightImages: Boolean = true,
val separateUpAndDownVotes: Boolean = false,

View File

@ -237,7 +237,7 @@ class CommunityDetailScreen(
if (!isOnOtherInstance && uiState.isLogged) {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
when (uiState.community.subscribed) {
true -> model.reduce(CommunityDetailMviModel.Intent.Unsubscribe)
false -> model.reduce(CommunityDetailMviModel.Intent.Subscribe)
@ -259,11 +259,11 @@ class CommunityDetailScreen(
// sort button
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val sheet = SortBottomSheet(
expandTop = true,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
),
imageVector = uiState.sortType.toIcon(),
@ -297,7 +297,7 @@ class CommunityDetailScreen(
modifier = Modifier.onGloballyPositioned {
optionsOffset = it.positionInParent()
}.padding(start = Spacing.s).onClick(
rememberCallback {
onClick = rememberCallback {
optionsExpanded = true
},
),
@ -321,7 +321,7 @@ class CommunityDetailScreen(
horizontal = Spacing.m,
vertical = Spacing.s,
).onClick(
rememberCallback {
onClick = rememberCallback {
optionsExpanded = false
when (option.id) {
OptionId.BlockInstance -> model.reduce(
@ -333,19 +333,17 @@ class CommunityDetailScreen(
)
OptionId.InfoInstance -> {
navigationCoordinator.getRootNavigator()
?.push(
InstanceInfoScreen(
url = uiState.community.instanceUrl,
),
)
navigationCoordinator.pushScreen(
InstanceInfoScreen(
url = uiState.community.instanceUrl,
),
)
}
OptionId.Info -> {
navigationCoordinator.getBottomNavigator()
?.show(
CommunityInfoScreen(uiState.community),
)
navigationCoordinator.showBottomSheet(
CommunityInfoScreen(uiState.community),
)
}
else -> Unit
@ -359,12 +357,11 @@ class CommunityDetailScreen(
}
},
navigationIcon = {
val navigator = navigationCoordinator.getRootNavigator()
if (navigator?.canPop == true) {
if (navigationCoordinator.canPop) {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigator.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -437,7 +434,7 @@ class CommunityDetailScreen(
val screen = CreatePostScreen(
communityId = uiState.community.id,
)
navigationCoordinator.getBottomNavigator()?.show(screen)
navigationCoordinator.showBottomSheet(screen)
},
)
}
@ -476,8 +473,7 @@ class CommunityDetailScreen(
community = uiState.community,
autoLoadImages = uiState.autoLoadImages,
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()
?.push(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
},
)
Spacer(modifier = Modifier.height(Spacing.m))
@ -559,15 +555,27 @@ class CommunityDetailScreen(
post.id
)
)
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(
post = post,
otherInstance = otherInstanceName,
),
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled || !uiState.isLogged || isOnOtherInstance) {
null
} else {
rememberCallback(model) {
model.reduce(
CommunityDetailMviModel.Intent.UpVotePost(
id = post.id,
feedback = true,
),
)
}
},
onOpenCreator = rememberCallbackArgs { user ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(
user = user,
otherInstance = otherInstanceName,
@ -609,8 +617,7 @@ class CommunityDetailScreen(
val screen = CreateCommentScreen(
originalPost = post,
)
navigationCoordinator.getBottomNavigator()
?.show(screen)
navigationCoordinator.showBottomSheet(screen)
}
},
onImageClick = rememberCallbackArgs(model) { url ->
@ -619,7 +626,7 @@ class CommunityDetailScreen(
post.id
)
)
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
)
},
@ -680,24 +687,21 @@ class CommunityDetailScreen(
)
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(editedPost = post)
)
navigationCoordinator.showBottomSheet(
CreatePostScreen(editedPost = post)
)
}
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(postId = post.id)
)
navigationCoordinator.showBottomSheet(
CreateReportScreen(postId = post.id)
)
}
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(crossPost = post)
)
navigationCoordinator.showBottomSheet(
CreatePostScreen(crossPost = post)
)
}
OptionId.SeeRaw -> {

View File

@ -66,6 +66,7 @@ class CommunityDetailViewModel(
it.copy(
blurNsfw = settings.blurNsfw,
swipeActionsEnabled = settings.enableSwipeActions,
doubleTapActionEnabled = settings.enableDoubleTapAction,
sortType = settings.defaultPostSortType.toSortType(),
fullHeightImages = settings.fullHeightImages,
separateUpAndDownVotes = settings.separateUpAndDownVotes,

View File

@ -21,7 +21,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.getThemeRepos
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
@ -42,6 +41,7 @@ fun CommentCard(
autoLoadImages: Boolean = true,
options: List<Option> = emptyList(),
onClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
onUpVote: (() -> Unit)? = null,
onDownVote: (() -> Unit)? = null,
onSave: (() -> Unit)? = null,
@ -63,9 +63,10 @@ fun CommentCard(
endColor = MaterialTheme.colorScheme.background,
)
Box(
modifier = Modifier.onClick(rememberCallback {
onClick?.invoke()
}).padding(
modifier = Modifier.onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {}
).padding(
start = if (hideIndent) 0.dp else (INDENT_AMOUNT * comment.depth).dp
),
) {
@ -96,6 +97,7 @@ fun CommentCard(
text = comment.text,
autoLoadImages = autoLoadImages,
onClick = onClick,
onDoubleClick = onDoubleClick,
)
}
PostCardFooter(

View File

@ -38,6 +38,7 @@ fun CommunityAndCreatorInfo(
onOpenCommunity: ((CommunityModel) -> Unit)? = null,
onOpenCreator: ((UserModel) -> Unit)? = null,
onToggleExpanded: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
) {
val communityName = community?.name.orEmpty()
val communityIcon = community?.icon.orEmpty()
@ -56,11 +57,12 @@ fun CommunityAndCreatorInfo(
CustomImage(
modifier = Modifier
.onClick(
rememberCallback {
onClick = rememberCallback {
if (community != null) {
onOpenCommunity?.invoke(community)
}
},
onDoubleClick = onDoubleClick ?: {},
)
.padding(Spacing.xxxs)
.size(iconSize)
@ -73,11 +75,12 @@ fun CommunityAndCreatorInfo(
} else {
PlaceholderImage(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
if (community != null) {
onOpenCommunity?.invoke(community)
}
},
onDoubleClick = onDoubleClick ?: {},
),
size = IconSize.l,
title = communityName,
@ -88,11 +91,12 @@ fun CommunityAndCreatorInfo(
CustomImage(
modifier = Modifier
.onClick(
rememberCallback {
onClick = rememberCallback {
if (creator != null) {
onOpenCreator?.invoke(creator)
}
},
onDoubleClick = onDoubleClick ?: {},
)
.padding(Spacing.xxxs)
.size(iconSize)
@ -105,11 +109,12 @@ fun CommunityAndCreatorInfo(
} else {
PlaceholderImage(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
if (creator != null) {
onOpenCreator?.invoke(creator)
}
},
onDoubleClick = onDoubleClick ?: {},
),
size = iconSize,
title = creatorName,
@ -123,9 +128,10 @@ fun CommunityAndCreatorInfo(
Text(
modifier = Modifier
.onClick(
rememberCallback {
onClick = rememberCallback {
onOpenCommunity?.invoke(community)
},
onDoubleClick = onDoubleClick ?: {},
),
text = buildString {
append(communityName)
@ -141,9 +147,10 @@ fun CommunityAndCreatorInfo(
Text(
modifier = Modifier
.onClick(
rememberCallback {
onClick = rememberCallback {
onOpenCreator?.invoke(creator)
},
onDoubleClick = onDoubleClick ?: {},
),
text = buildString {
append(creatorName)
@ -161,7 +168,7 @@ fun CommunityAndCreatorInfo(
val expandedModifier = Modifier
.padding(end = Spacing.xs)
.onClick(
rememberCallback {
onClick = rememberCallback {
onToggleExpanded?.invoke()
},
)

View File

@ -92,7 +92,7 @@ fun CommunityHeader(
.size(IconSize.xxl)
.clip(RoundedCornerShape(IconSize.xxl / 2))
.onClick(
rememberCallback {
onClick = rememberCallback {
onOpenImage?.invoke(communityIcon)
},
),

View File

@ -34,11 +34,13 @@ fun InboxCard(
autoLoadImages: Boolean = true,
separateUpAndDownVotes: Boolean = true,
postLayout: PostLayout = PostLayout.Card,
options: List<Option> = emptyList(),
onOpenPost: (PostModel) -> Unit,
onOpenCreator: (UserModel) -> Unit,
onOpenCommunity: (CommunityModel) -> Unit,
onUpVote: ((CommentModel) -> Unit)? = null,
onDownVote: ((CommentModel) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
) {
Box(
modifier = Modifier.let {
@ -52,7 +54,7 @@ fun InboxCard(
it.background(MaterialTheme.colorScheme.background)
}
}.onClick(
rememberCallback {
onClick = rememberCallback {
onOpenPost(mention.post)
},
),
@ -92,6 +94,7 @@ fun InboxCard(
separateUpAndDownVotes = separateUpAndDownVotes,
upVoted = mention.myVote > 0,
downVoted = mention.myVote < 0,
options = options,
onOpenCommunity = onOpenCommunity,
onOpenCreator = { user ->
onOpenCreator(user)
@ -102,6 +105,7 @@ fun InboxCard(
onDownVote = {
onDownVote?.invoke(mention.comment)
},
onOptionSelected = onOptionSelected,
)
}
}

View File

@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@ -11,6 +12,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowCircleDown
import androidx.compose.material.icons.filled.ArrowCircleUp
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -18,17 +20,23 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
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.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.getThemeRepository
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
@ -36,6 +44,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
@ -50,6 +59,7 @@ fun InboxReplySubtitle(
score: Int = 0,
upvotes: Int = 0,
downvotes: Int = 0,
options: List<Option> = emptyList(),
separateUpAndDownVotes: Boolean = false,
upVoted: Boolean = false,
downVoted: Boolean = false,
@ -57,13 +67,17 @@ fun InboxReplySubtitle(
onOpenCreator: ((UserModel) -> Unit)? = null,
onUpVote: (() -> Unit)? = null,
onDownVote: (() -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
) {
val buttonModifier = Modifier.size(IconSize.m).padding(4.dp)
val buttonModifier = Modifier.size(IconSize.m).padding(3.5.dp)
val themeRepository = remember { getThemeRepository() }
val upvoteColor by themeRepository.upvoteColor.collectAsState()
val downvoteColor by themeRepository.downvoteColor.collectAsState()
val defaultUpvoteColor = MaterialTheme.colorScheme.primary
val defaultDownVoteColor = MaterialTheme.colorScheme.tertiary
var optionsExpanded by remember { mutableStateOf(false) }
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
Column(
modifier = modifier,
) {
@ -83,7 +97,7 @@ fun InboxReplySubtitle(
modifier = Modifier
.weight(1f)
.onClick(
rememberCallback {
onClick = rememberCallback {
if (creator != null) {
onOpenCreator?.invoke(creator)
}
@ -124,7 +138,7 @@ fun InboxReplySubtitle(
modifier = Modifier
.weight(1f)
.onClick(
rememberCallback {
onClick = rememberCallback {
if (community != null) {
onOpenCommunity?.invoke(community)
}
@ -162,96 +176,143 @@ fun InboxReplySubtitle(
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = buttonModifier,
imageVector = Icons.Default.Schedule,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
Text(
text = date?.prettifyDate() ?: "",
)
Spacer(modifier = Modifier.weight(1f))
Image(
modifier = buttonModifier
.onClick(
rememberCallback {
onUpVote?.invoke()
Box {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
modifier = buttonModifier,
imageVector = Icons.Default.Schedule,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
Text(
text = date?.prettifyDate() ?: "",
)
if (options.isNotEmpty()) {
Icon(
modifier = buttonModifier
.padding(top = Spacing.xxs)
.onGloballyPositioned {
optionsOffset = it.positionInParent()
}
.onClick(
onClick = rememberCallback {
optionsExpanded = true
},
),
imageVector = Icons.Default.MoreHoriz,
contentDescription = null,
)
}
Spacer(modifier = Modifier.weight(1f))
Image(
modifier = buttonModifier
.onClick(
onClick = rememberCallback {
onUpVote?.invoke()
},
),
imageVector = Icons.Default.ArrowCircleUp,
contentDescription = null,
colorFilter = ColorFilter.tint(
color = if (upVoted) {
upvoteColor ?: defaultUpvoteColor
} else {
MaterialTheme.colorScheme.onSurface
},
),
imageVector = Icons.Default.ArrowCircleUp,
contentDescription = null,
colorFilter = ColorFilter.tint(
color = if (upVoted) {
upvoteColor ?: defaultUpvoteColor
} else {
MaterialTheme.colorScheme.onSurface
)
Text(
text = buildAnnotatedString {
if (separateUpAndDownVotes) {
val upvoteText = upvotes.toString()
append(upvoteText)
if (upVoted) {
addStyle(
style = SpanStyle(color = upvoteColor ?: defaultUpvoteColor),
start = 0,
end = upvoteText.length
)
}
append(" / ")
val downvoteText = downvotes.toString()
append(downvoteText)
if (downVoted) {
addStyle(
style = SpanStyle(
color = downvoteColor ?: defaultDownVoteColor
),
start = upvoteText.length + 3,
end = upvoteText.length + 3 + downvoteText.length
)
}
} else {
val text = score.toString()
append(text)
if (upVoted) {
addStyle(
style = SpanStyle(color = upvoteColor ?: defaultUpvoteColor),
start = 0,
end = text.length
)
} else if (downVoted) {
addStyle(
style = SpanStyle(
color = downvoteColor ?: defaultDownVoteColor
),
start = 0,
end = length
)
}
}
},
),
)
Text(
text = buildAnnotatedString {
if (separateUpAndDownVotes) {
val upvoteText = upvotes.toString()
append(upvoteText)
if (upVoted) {
addStyle(
style = SpanStyle(color = upvoteColor ?: defaultUpvoteColor),
start = 0,
end = upvoteText.length
)
}
append(" / ")
val downvoteText = downvotes.toString()
append(downvoteText)
if (downVoted) {
addStyle(
style = SpanStyle(color = downvoteColor ?: defaultDownVoteColor),
start = upvoteText.length + 3,
end = upvoteText.length + 3 + downvoteText.length
)
}
} else {
val text = score.toString()
append(text)
if (upVoted) {
addStyle(
style = SpanStyle(color = upvoteColor ?: defaultUpvoteColor),
start = 0,
end = text.length
)
} else if (downVoted) {
addStyle(
style = SpanStyle(color = downvoteColor ?: defaultDownVoteColor),
start = 0,
end = length
)
}
}
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onBackground,
)
Image(
modifier = buttonModifier
.onClick(
onClick = rememberCallback {
onDownVote?.invoke()
},
),
imageVector = Icons.Default.ArrowCircleDown,
contentDescription = null,
colorFilter = ColorFilter.tint(
color = if (downVoted) {
downvoteColor ?: defaultDownVoteColor
} else {
MaterialTheme.colorScheme.onSurface
},
),
)
}
CustomDropDown(
expanded = optionsExpanded,
onDismiss = {
optionsExpanded = false
},
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onBackground,
)
Image(
modifier = buttonModifier
.onClick(
rememberCallback {
onDownVote?.invoke()
},
),
imageVector = Icons.Default.ArrowCircleDown,
contentDescription = null,
colorFilter = ColorFilter.tint(
color = if (downVoted) {
downvoteColor ?: defaultDownVoteColor
} else {
MaterialTheme.colorScheme.onSurface
},
offset = DpOffset(
x = optionsOffset.x.toLocalDp(),
y = optionsOffset.y.toLocalDp(),
),
)
) {
options.forEach { option ->
Text(
modifier = Modifier.padding(
horizontal = Spacing.m,
vertical = Spacing.s,
).onClick(
onClick = rememberCallback {
optionsExpanded = false
onOptionSelected?.invoke(option.id)
},
),
text = option.text,
)
}
}
}
}
}

View File

@ -17,4 +17,6 @@ sealed class OptionId(val value: Int) {
data object InfoInstance : OptionId(8)
data object Block : OptionId(9)
data object BlockInstance : OptionId(10)
data object MarkRead : OptionId(11)
data object MarkUnread : OptionId(12)
}

View File

@ -26,14 +26,17 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.CornerSize
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.web.WebViewScreen
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalPixel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
@ -63,6 +66,7 @@ fun PostCard(
onImageClick: ((String) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
) {
Box(
modifier = modifier.let {
@ -74,7 +78,10 @@ fun PostCard(
} else {
it
}
}.onClick(rememberCallback { onClick?.invoke() }),
}.onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
),
) {
if (postLayout != PostLayout.Compact) {
ExtendedPost(
@ -101,6 +108,7 @@ fun PostCard(
onImageClick = onImageClick,
onOptionSelected = onOptionSelected,
onClick = onClick,
onDoubleClick = onDoubleClick,
)
} else {
CompactPost(
@ -119,6 +127,7 @@ fun PostCard(
onImageClick = onImageClick,
onOptionSelected = onOptionSelected,
onClick = onClick,
onDoubleClick = onDoubleClick,
)
}
}
@ -142,6 +151,7 @@ private fun CompactPost(
onImageClick: ((String) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
) {
Column(
modifier = modifier.background(MaterialTheme.colorScheme.background),
@ -153,6 +163,7 @@ private fun CompactPost(
onOpenCommunity = onOpenCommunity,
onOpenCreator = onOpenCreator,
autoLoadImages = autoLoadImages,
onDoubleClick = onDoubleClick,
)
Row(
modifier = Modifier.padding(horizontal = Spacing.s),
@ -165,6 +176,7 @@ private fun CompactPost(
text = post.title,
autoLoadImages = autoLoadImages,
onClick = onClick,
onDoubleClick = onDoubleClick,
)
}
PostCardImage(
@ -180,6 +192,7 @@ private fun CompactPost(
},
blurred = blurNsfw && post.nsfw,
onImageClick = onImageClick,
onDoubleClick = onDoubleClick,
)
}
PostCardFooter(
@ -225,6 +238,7 @@ private fun ExtendedPost(
onImageClick: ((String) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
) {
Column(
modifier = modifier.background(backgroundColor),
@ -237,6 +251,7 @@ private fun ExtendedPost(
onOpenCommunity = onOpenCommunity,
onOpenCreator = onOpenCreator,
autoLoadImages = autoLoadImages,
onDoubleClick = onDoubleClick,
)
ScaledContent {
PostCardTitle(
@ -247,6 +262,7 @@ private fun ExtendedPost(
text = post.title,
autoLoadImages = autoLoadImages,
onClick = onClick,
onDoubleClick = onDoubleClick,
)
}
@ -269,6 +285,7 @@ private fun ExtendedPost(
imageUrl = post.imageUrl,
blurred = blurNsfw && post.nsfw,
onImageClick = onImageClick,
onDoubleClick = onDoubleClick,
autoLoadImages = autoLoadImages,
)
if (showBody) {
@ -290,6 +307,7 @@ private fun ExtendedPost(
text = post.text,
autoLoadImages = autoLoadImages,
onClick = onClick,
onDoubleClick = onDoubleClick,
)
if (limitBodyHeight && textHeightPx >= maxHeightPx) {
Box(
@ -307,9 +325,25 @@ private fun ExtendedPost(
}
}
}
if (post.url != post.imageUrl) {
if (post.url != post.imageUrl && !post.url.isNullOrEmpty()) {
val url = post.url.orEmpty()
val settingsRepository = remember { getSettingsRepository() }
val uriHandler = LocalUriHandler.current
val navigationCoordinator = remember { getNavigationCoordinator() }
PostLinkBanner(
modifier = Modifier.padding(vertical = Spacing.xs),
modifier = Modifier
.padding(vertical = Spacing.xs)
.onClick(
onClick = {
if (settingsRepository.currentSettings.value.openUrlsInExternalBrowser) {
uriHandler.openUri(url)
} else {
navigationCoordinator.pushScreen(WebViewScreen(url))
}
},
onDoubleClick = onDoubleClick ?: {},
),
url = post.url?.takeIf { !it.looksLikeAnImage }.orEmpty(),
)
}

View File

@ -15,6 +15,7 @@ fun PostCardBody(
text: String,
autoLoadImages: Boolean = true,
onClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
) {
val uriHandler = LocalUriHandler.current
val navigationCoordinator = remember { getNavigationCoordinator() }
@ -27,17 +28,17 @@ fun PostCardBody(
inlineImages = false,
autoLoadImages = autoLoadImages,
onOpenUrl = { url ->
handleUrl(
navigationCoordinator.handleUrl(
url = url,
openExternal = settingsRepository.currentSettings.value.openUrlsInExternalBrowser,
uriHandler = uriHandler,
navigator = navigationCoordinator.getRootNavigator()
)
},
onOpenImage = { url ->
navigationCoordinator.getRootNavigator()?.push(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
},
onClick = onClick,
onDoubleClick = onDoubleClick,
)
}
}

View File

@ -79,7 +79,7 @@ fun PostCardFooter(
Image(
modifier = buttonModifier.padding(1.dp)
.onClick(
rememberCallback {
onClick = rememberCallback {
onReply?.invoke()
},
),
@ -110,11 +110,12 @@ fun PostCardFooter(
if (options.isNotEmpty()) {
Icon(
modifier = buttonModifier
.padding(top = Spacing.xxs)
.onGloballyPositioned {
optionsOffset = it.positionInParent()
}
.onClick(
rememberCallback {
onClick = rememberCallback {
optionsExpanded = true
},
),
@ -125,7 +126,7 @@ fun PostCardFooter(
Spacer(modifier = Modifier.weight(1f))
Image(
modifier = buttonModifier.onClick(
rememberCallback {
onClick = rememberCallback {
onSave?.invoke()
},
),
@ -146,7 +147,7 @@ fun PostCardFooter(
Image(
modifier = buttonModifier
.onClick(
rememberCallback {
onClick = rememberCallback {
onUpVote?.invoke()
},
),
@ -206,7 +207,7 @@ fun PostCardFooter(
Image(
modifier = buttonModifier
.onClick(
rememberCallback {
onClick = rememberCallback {
onDownVote?.invoke()
},
),
@ -238,7 +239,7 @@ fun PostCardFooter(
horizontal = Spacing.m,
vertical = Spacing.s,
).onClick(
rememberCallback {
onClick = rememberCallback {
optionsExpanded = false
onOptionSelected?.invoke(option.id)
},

View File

@ -19,7 +19,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource
@ -33,6 +32,7 @@ fun PostCardImage(
maxHeight: Dp = Dp.Unspecified,
blurred: Boolean = false,
onImageClick: ((String) -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
) {
if (imageUrl.isNotEmpty()) {
CustomImage(
@ -40,9 +40,8 @@ fun PostCardImage(
.heightIn(min = minHeight, max = maxHeight)
.blur(radius = if (blurred) 60.dp else 0.dp)
.onClick(
rememberCallback {
onImageClick?.invoke(imageUrl)
},
onClick = { onImageClick?.invoke(imageUrl) },
onDoubleClick = onDoubleClick ?: {},
),
url = imageUrl,
quality = FilterQuality.Low,

View File

@ -15,6 +15,7 @@ fun PostCardTitle(
autoLoadImages: Boolean = true,
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
) {
val uriHandler = LocalUriHandler.current
val navigationCoordinator = remember { getNavigationCoordinator() }
@ -25,16 +26,16 @@ fun PostCardTitle(
content = text,
autoLoadImages = autoLoadImages,
onOpenUrl = { url ->
handleUrl(
navigationCoordinator.handleUrl(
url = url,
openExternal = settingsRepository.currentSettings.value.openUrlsInExternalBrowser,
uriHandler = uriHandler,
navigator = navigationCoordinator.getRootNavigator(),
)
},
onOpenImage = { url ->
navigationCoordinator.getRootNavigator()?.push(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
},
onClick = onClick,
onDoubleClick = onDoubleClick,
)
}

View File

@ -29,24 +29,12 @@ fun PostLinkBanner(
modifier: Modifier = Modifier,
url: String,
) {
val uriHandler = LocalUriHandler.current
val navigationCoordinator = remember { getNavigationCoordinator() }
val settingsRepository = remember { getSettingsRepository() }
if (url.isNotEmpty()) {
Row(
modifier = modifier
.background(
color = MaterialTheme.colorScheme.tertiary.copy(alpha = 0.1f),
shape = RoundedCornerShape(CornerSize.l),
).onClick(
rememberCallback {
if (settingsRepository.currentSettings.value.openUrlsInExternalBrowser) {
uriHandler.openUri(url)
} else {
navigationCoordinator.getRootNavigator()?.push(WebViewScreen(url))
}
},
).padding(
horizontal = Spacing.m,
vertical = Spacing.s,

View File

@ -145,7 +145,7 @@ fun TextFormattingBar(
)
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onSelectImage()
},
),

View File

@ -1,8 +1,8 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
import androidx.compose.ui.platform.UriHandler
import cafe.adriel.voyager.navigator.Navigator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.NavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.web.WebViewScreen
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
@ -39,18 +39,17 @@ fun getPostFromUrl(url: String?): Pair<PostModel, String>? {
}
}
fun handleUrl(
fun NavigationCoordinator.handleUrl(
url: String,
openExternal: Boolean,
uriHandler: UriHandler,
navigator: Navigator? = null,
) {
val community = getCommunityFromUrl(url)
val user = getUserFromUrl(url)
when {
community != null && navigator != null -> {
navigator.push(
community != null -> {
pushScreen(
CommunityDetailScreen(
community = community,
otherInstance = community.host
@ -58,8 +57,8 @@ fun handleUrl(
)
}
user != null && navigator != null -> {
navigator.push(
user != null -> {
pushScreen(
UserDetailScreen(
user = user,
otherInstance = user.host
@ -71,11 +70,7 @@ fun handleUrl(
uriHandler.openUri(url)
}
navigator != null -> {
navigator.push(WebViewScreen(url))
}
else -> Unit
else -> pushScreen(WebViewScreen(url))
}
}

View File

@ -90,7 +90,7 @@ fun UserHeader(
.size(IconSize.xxl)
.clip(RoundedCornerShape(IconSize.xxl / 2))
.onClick(
rememberCallback {
onClick = rememberCallback {
onOpenImage?.invoke(userAvatar)
},
),

View File

@ -126,7 +126,7 @@ class CreateCommentScreen(
CreateCommentMviModel.Effect.Success -> {
notificationCenter.getObserver(NotificationCenterContractKeys.CommentCreated)
?.also { o -> o.invoke(Unit) }
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
}
is CreateCommentMviModel.Effect.AddImageToText -> {

View File

@ -176,7 +176,7 @@ class CreatePostScreen(
CreatePostMviModel.Effect.Success -> {
notificationCenter.getObserver(NotificationCenterContractKeys.PostCreated)
?.also { o -> o.invoke(Unit) }
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
}
is CreatePostMviModel.Effect.AddImageToBody -> {
@ -190,7 +190,7 @@ class CreatePostScreen(
DisposableEffect(key) {
notificationCenter.addObserver(
{
(it as CommunityModel)?.also { community ->
(it as? CommunityModel)?.also { community ->
model.reduce(CreatePostMviModel.Intent.SetCommunity(community))
focusManager.clearFocus()
}
@ -357,7 +357,7 @@ class CreatePostScreen(
trailingIcon = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
openImagePicker = true
},
),

View File

@ -190,7 +190,7 @@ object ModalDrawerContent : Tab {
items(uiState.multiCommunities) { community ->
MultiCommunityItem(
modifier = Modifier.fillMaxWidth().onClick(
rememberCallback {
onClick = rememberCallback {
scope.launch {
coordinator.toggleDrawer()
coordinator.sendEvent(
@ -207,7 +207,7 @@ object ModalDrawerContent : Tab {
items(uiState.communities) { community ->
CommunityItem(
modifier = Modifier.fillMaxWidth().onClick(
rememberCallback {
onClick = rememberCallback {
scope.launch {
coordinator.toggleDrawer()
coordinator.sendEvent(
@ -338,7 +338,7 @@ private fun DrawerHeader(
Spacer(modifier = Modifier.weight(1f))
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onOpenChangeInstance?.invoke()
},
),
@ -362,7 +362,7 @@ private fun DrawerShortcut(
horizontal = Spacing.s,
vertical = Spacing.xs,
).onClick(
rememberCallback {
onClick = rememberCallback {
onSelected?.invoke()
},
),
@ -443,7 +443,7 @@ private fun ChangeInstanceDialog(
if (instanceName.isNotEmpty()) {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onChangeInstanceName?.invoke("")
},
),

View File

@ -81,8 +81,8 @@ class ZoomableImageScreen(
navigationIcon = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -93,7 +93,7 @@ class ZoomableImageScreen(
actions = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
model.reduce(ZoomableImageMviModel.Intent.SaveToGallery(url))
},
),
@ -104,7 +104,7 @@ class ZoomableImageScreen(
Spacer(Modifier.width(Spacing.s))
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
model.reduce(ZoomableImageMviModel.Intent.Share(url))
},
),

View File

@ -114,8 +114,8 @@ class InstanceInfoScreen(
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -145,7 +145,7 @@ class InstanceInfoScreen(
}
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val sheet = SortBottomSheet(
values = listOf(
SortType.Active,
@ -158,7 +158,7 @@ class InstanceInfoScreen(
),
expandTop = true,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
),
imageVector = uiState.sortType.toIcon(),
@ -232,8 +232,8 @@ class InstanceInfoScreen(
items(uiState.communities) {
CommunityItem(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
onClick = rememberCallback {
navigationCoordinator.pushScreen(
CommunityDetailScreen(
community = it,
otherInstance = instanceName,

View File

@ -95,13 +95,13 @@ class ColorBottomSheet : Screen {
horizontal = Spacing.s,
vertical = Spacing.s,
).fillMaxWidth().onClick(
rememberCallback {
onClick = rememberCallback {
if (!isChooseCustom) {
notificationCenter.getObserver(NotificationCenterContractKeys.ChangeColor)
?.also {
it.invoke(value.first ?: Unit)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
} else {
customPickerDialogOpened = true
}
@ -149,7 +149,7 @@ class ColorBottomSheet : Screen {
?.also {
it.invoke(color)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
}
)
}

View File

@ -73,13 +73,13 @@ class DurationBottomSheet(
horizontal = Spacing.s,
vertical = Spacing.s,
).fillMaxWidth().onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(
NotificationCenterContractKeys.ChangeZombieInterval
)?.also {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -74,14 +74,14 @@ class FontFamilyBottomSheet(
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(
NotificationCenterContractKeys.ChangeFontFamily
)
?.also {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -75,11 +75,11 @@ class FontScaleBottomSheet(
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(contract)?.also {
it.invoke(value.scaleFactor)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -61,12 +61,12 @@ class InboxTypeSheet : Screen {
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(NotificationCenterContractKeys.ChangeInboxType)
?.also {
it.invoke(true)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {
@ -83,12 +83,12 @@ class InboxTypeSheet : Screen {
horizontal = Spacing.s,
vertical = Spacing.m,
).onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(NotificationCenterContractKeys.ChangeInboxType)
?.also {
it.invoke(false)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -75,14 +75,14 @@ class LanguageBottomSheet : Screen {
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(
NotificationCenterContractKeys.ChangeLanguage
)
?.also {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -77,13 +77,13 @@ class ListingTypeBottomSheet(
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getAllObservers(
NotificationCenterContractKeys.ChangeFeedType
).forEach {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -67,12 +67,12 @@ class PostLayoutBottomSheet : Screen {
horizontal = Spacing.s,
vertical = Spacing.m,
).fillMaxWidth().onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(NotificationCenterContractKeys.ChangePostLayout)
?.also {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -76,7 +76,7 @@ class SliderBottomSheet(
?.also {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
) {
Text(text = stringResource(MR.strings.button_confirm))

View File

@ -109,14 +109,14 @@ internal class SortBottomSheetMain(
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
if (value == SortType.Top.Generic && expandTop) {
navigator.push(SortBottomSheetTop(contract = contract))
} else {
notificationCenter.getAllObservers(contract).forEach {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
}
},
),
@ -172,7 +172,7 @@ internal class SortBottomSheetTop(
) {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
navigator.pop()
},
),
@ -199,13 +199,13 @@ internal class SortBottomSheetTop(
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getAllObservers(
contract,
).forEach {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -75,12 +75,12 @@ class ThemeBottomSheet : Screen {
)
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
notificationCenter.getObserver(NotificationCenterContractKeys.ChangeTheme)
?.also {
it.invoke(value)
}
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
},
),
) {

View File

@ -1,20 +1,31 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
import cafe.adriel.voyager.navigator.tab.Tab
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
private const val NAVIGATION_DELAY = 250L
private const val BOTTOM_NAVIGATION_DELAY = 150L
private const val DEEP_LINK_DELAY = 500L
internal class DefaultNavigationCoordinator : NavigationCoordinator {
override val onDoubleTabSelection = MutableSharedFlow<Tab>()
override val deepLinkUrl = MutableSharedFlow<String>()
override val inboxUnread = MutableStateFlow(0)
override val canPop: Boolean
get() = navigator?.canPop == true
private var connection: NestedScrollConnection? = null
private var navigator: Navigator? = null
@ -22,14 +33,12 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
private var currentTab: Tab? = null
private val scope = CoroutineScope(SupervisorJob())
private var canGoBackCallback: (() -> Boolean)? = null
override val inboxUnread = MutableStateFlow(0)
private var job: Job? = null
override fun setRootNavigator(value: Navigator?) {
navigator = value
}
override fun getRootNavigator(): Navigator? = navigator
override fun setBottomBarScrollConnection(value: NestedScrollConnection?) {
connection = value
}
@ -48,8 +57,11 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
override fun submitDeeplink(url: String) {
scope.launch {
delay(500)
deepLinkUrl.emit(url)
delay(DEEP_LINK_DELAY)
runCatching {
ensureActive()
deepLinkUrl.emit(url)
}
}
}
@ -67,7 +79,47 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
bottomNavigator = value
}
override fun getBottomNavigator(): BottomSheetNavigator? {
return bottomNavigator
override fun showBottomSheet(screen: Screen) {
job?.cancel()
job = scope.launch {
delay(BOTTOM_NAVIGATION_DELAY)
runCatching {
ensureActive()
bottomNavigator?.show(screen)
}
}
}
override fun pushScreen(screen: Screen) {
job?.cancel()
job = scope.launch {
delay(NAVIGATION_DELAY)
runCatching {
ensureActive()
navigator?.push(screen)
}
}
}
override fun hideBottomSheet() {
job?.cancel()
job = scope.launch {
delay(BOTTOM_NAVIGATION_DELAY)
runCatching {
ensureActive()
bottomNavigator?.hide()
}
}
}
override fun popScreen() {
job?.cancel()
job = scope.launch {
delay(NAVIGATION_DELAY)
runCatching {
ensureActive()
navigator?.pop()
}
}
}
}

View File

@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation
import androidx.compose.runtime.Stable
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.bottomSheet.BottomSheetNavigator
import cafe.adriel.voyager.navigator.tab.Tab
@ -14,26 +15,19 @@ interface NavigationCoordinator {
val onDoubleTabSelection: Flow<Tab>
val inboxUnread: StateFlow<Int>
val deepLinkUrl: Flow<String?>
val canPop: Boolean
fun setCurrentSection(tab: Tab)
fun submitDeeplink(url: String)
fun setRootNavigator(value: Navigator?)
fun setCanGoBackCallback(value: (() -> Boolean)?)
fun getCanGoBackCallback(): (() -> Boolean)?
fun getRootNavigator(): Navigator?
fun setBottomBarScrollConnection(value: NestedScrollConnection?)
fun getBottomBarScrollConnection(): NestedScrollConnection?
fun setInboxUnread(count: Int)
fun setBottomNavigator(value: BottomSheetNavigator?)
fun getBottomNavigator(): BottomSheetNavigator?
fun showBottomSheet(screen: Screen)
fun hideBottomSheet()
fun pushScreen(screen: Screen)
fun popScreen()
}

View File

@ -43,6 +43,7 @@ interface PostDetailMviModel :
val comments: List<CommentModel> = emptyList(),
val currentUserId: Int? = null,
val swipeActionsEnabled: Boolean = true,
val doubleTapActionEnabled: Boolean = true,
val postLayout: PostLayout = PostLayout.Card,
val fullHeightImages: Boolean = true,
val separateUpAndDownVotes: Boolean = false,

View File

@ -230,7 +230,7 @@ class PostDetailScreen(
model.effects.onEach { evt ->
when (evt) {
PostDetailMviModel.Effect.Close -> {
navigationCoordinator.getRootNavigator()?.pop()
navigationCoordinator.popScreen()
}
is PostDetailMviModel.Effect.ScrollToComment -> {
@ -263,7 +263,7 @@ class PostDetailScreen(
actions = {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val sheet = SortBottomSheet(
values = listOf(
SortType.Hot,
@ -273,7 +273,7 @@ class PostDetailScreen(
SortType.Controversial,
),
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
),
imageVector = uiState.sortType.toIcon(),
@ -282,12 +282,11 @@ class PostDetailScreen(
)
},
navigationIcon = {
val navigator = navigationCoordinator.getRootNavigator()
if (navigator?.canPop == true) {
if (navigationCoordinator.canPop) {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigator.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -328,7 +327,7 @@ class PostDetailScreen(
val screen = CreateCommentScreen(
originalPost = uiState.post,
)
navigationCoordinator.getBottomNavigator()?.show(screen)
navigationCoordinator.showBottomSheet(screen)
},
)
}
@ -365,12 +364,12 @@ class PostDetailScreen(
autoLoadImages = uiState.autoLoadImages,
blurNsfw = false,
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
CommunityDetailScreen(community = community)
)
},
onOpenCreator = rememberCallbackArgs { user ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(user = user)
)
},
@ -405,7 +404,7 @@ class PostDetailScreen(
val screen = CreateCommentScreen(
originalPost = uiState.post,
)
navigationCoordinator.getBottomNavigator()?.show(screen)
navigationCoordinator.showBottomSheet(screen)
}
},
options = buildList {
@ -455,19 +454,19 @@ class PostDetailScreen(
OptionId.Delete -> model.reduce(PostDetailMviModel.Intent.DeletePost)
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()?.show(
navigationCoordinator.showBottomSheet(
CreatePostScreen(editedPost = uiState.post)
)
}
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()?.show(
navigationCoordinator.showBottomSheet(
CreateReportScreen(postId = uiState.post.id)
)
}
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()?.show(
navigationCoordinator.showBottomSheet(
CreatePostScreen(crossPost = uiState.post)
)
}
@ -482,7 +481,7 @@ class PostDetailScreen(
}
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
)
},
@ -523,15 +522,14 @@ class PostDetailScreen(
}
Text(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val post = PostModel(
id = crossPost.id,
community = community,
)
navigationCoordinator.getRootNavigator()
?.push(
PostDetailScreen(post)
)
navigationCoordinator.pushScreen(
PostDetailScreen(post)
)
},
),
text = string,
@ -639,6 +637,18 @@ class PostDetailScreen(
)
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled) {
null
} else {
rememberCallback(model) {
model.reduce(
PostDetailMviModel.Intent.UpVoteComment(
commentId = comment.id,
feedback = true,
),
)
}
},
onUpVote = rememberCallback(model) {
if (uiState.isLogged && !isOnOtherInstance) {
model.reduce(
@ -675,32 +685,31 @@ class PostDetailScreen(
originalPost = uiState.post,
originalComment = comment,
)
navigationCoordinator.getBottomNavigator()
?.show(screen)
navigationCoordinator.showBottomSheet(
screen
)
}
},
onOpenCreator = rememberCallbackArgs {
val user = comment.creator
if (user != null) {
navigationCoordinator.getRootNavigator()
?.push(
UserDetailScreen(
user = user,
otherInstance = otherInstanceName,
),
)
navigationCoordinator.pushScreen(
UserDetailScreen(
user = user,
otherInstance = otherInstanceName,
),
)
}
},
onOpenCommunity = rememberCallbackArgs {
val community = comment.community
if (community != null) {
navigationCoordinator.getRootNavigator()
?.push(
CommunityDetailScreen(
community = community,
otherInstance = otherInstanceName,
),
)
navigationCoordinator.pushScreen(
CommunityDetailScreen(
community = community,
otherInstance = otherInstanceName,
),
)
}
},
options = buildList {
@ -742,21 +751,19 @@ class PostDetailScreen(
)
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateCommentScreen(
editedComment = comment,
)
navigationCoordinator.showBottomSheet(
CreateCommentScreen(
editedComment = comment,
)
)
}
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
commentId = comment.id
)
navigationCoordinator.showBottomSheet(
CreateReportScreen(
commentId = comment.id
)
)
}
OptionId.SeeRaw -> {
@ -815,14 +822,13 @@ class PostDetailScreen(
originalPost = uiState.post,
originalComment = comment,
)
navigationCoordinator.getBottomNavigator()
?.show(screen)
navigationCoordinator.showBottomSheet(screen)
}
},
onOpenCreator = rememberCallbackArgs {
val user = comment.creator
if (user != null) {
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(
user = user,
otherInstance = otherInstanceName,
@ -867,21 +873,19 @@ class PostDetailScreen(
)
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateCommentScreen(
editedComment = comment,
)
navigationCoordinator.showBottomSheet(
CreateCommentScreen(
editedComment = comment,
)
)
}
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
commentId = comment.id
)
navigationCoordinator.showBottomSheet(
CreateReportScreen(
commentId = comment.id
)
)
}
OptionId.SeeRaw -> {

View File

@ -70,6 +70,7 @@ class PostDetailViewModel(
mvi.updateState {
it.copy(
swipeActionsEnabled = settings.enableSwipeActions,
doubleTapActionEnabled = settings.enableDoubleTapAction,
sortType = settings.defaultCommentSortType.toSortType(),
separateUpAndDownVotes = settings.separateUpAndDownVotes,
autoLoadImages = settings.autoLoadImages,

View File

@ -73,7 +73,7 @@ class CreateReportScreen(
}
CreateReportMviModel.Effect.Success -> {
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
}
}
}.launchIn(this)

View File

@ -138,7 +138,7 @@ class SavedItemsScreen : Screen {
actions = {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val sheet = SortBottomSheet(
values = listOf(
SortType.Hot,
@ -146,7 +146,7 @@ class SavedItemsScreen : Screen {
SortType.Old,
),
)
navigatorCoordinator.getBottomNavigator()?.show(sheet)
navigatorCoordinator.showBottomSheet(sheet)
},
),
imageVector = uiState.sortType.toIcon(),
@ -157,8 +157,8 @@ class SavedItemsScreen : Screen {
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigatorCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigatorCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -250,19 +250,18 @@ class SavedItemsScreen : Screen {
autoLoadImages = uiState.autoLoadImages,
blurNsfw = uiState.blurNsfw,
onClick = {
navigatorCoordinator.getRootNavigator()?.push(
navigatorCoordinator.pushScreen(
PostDetailScreen(post),
)
},
onOpenCommunity = { community ->
navigatorCoordinator.getRootNavigator()?.push(
navigatorCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
onOpenCreator = { u ->
if (u.id != uiState.user?.id) {
navigatorCoordinator.getRootNavigator()
?.push(UserDetailScreen(u))
navigatorCoordinator.pushScreen(UserDetailScreen(u))
}
},
onUpVote = {
@ -293,10 +292,10 @@ class SavedItemsScreen : Screen {
val screen = CreateCommentScreen(
originalPost = post,
)
navigatorCoordinator.getBottomNavigator()?.show(screen)
navigatorCoordinator.showBottomSheet(screen)
},
onImageClick = { url ->
navigatorCoordinator.getRootNavigator()?.push(
navigatorCoordinator.pushScreen(
ZoomableImageScreen(url),
)
},
@ -323,7 +322,7 @@ class SavedItemsScreen : Screen {
onOptionSelected = { optionIndex ->
when (optionIndex) {
OptionId.Report -> {
navigatorCoordinator.getBottomNavigator()?.show(
navigatorCoordinator.showBottomSheet(
CreateReportScreen(
postId = post.id
)
@ -373,7 +372,7 @@ class SavedItemsScreen : Screen {
autoLoadImages = uiState.autoLoadImages,
hideIndent = true,
onClick = {
navigatorCoordinator.getRootNavigator()?.push(
navigatorCoordinator.pushScreen(
PostDetailScreen(
post = PostModel(id = comment.postId),
highlightCommentId = comment.id,
@ -409,7 +408,7 @@ class SavedItemsScreen : Screen {
originalPost = PostModel(id = comment.postId),
originalComment = comment,
)
navigatorCoordinator.getBottomNavigator()?.show(screen)
navigatorCoordinator.showBottomSheet(screen)
},
options = buildList {
add(
@ -428,7 +427,7 @@ class SavedItemsScreen : Screen {
onOptionSelected = { optionIndex ->
when (optionIndex) {
OptionId.Report -> {
navigatorCoordinator.getBottomNavigator()?.show(
navigatorCoordinator.showBottomSheet(
CreateReportScreen(
commentId = comment.id
)

View File

@ -97,7 +97,7 @@ class SelectCommunityDialog : Screen {
trailingIcon = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
if (uiState.searchText.isNotEmpty()) {
model.reduce(SelectCommunityMviModel.Intent.SetSearch(""))
}
@ -127,16 +127,18 @@ class SelectCommunityDialog : Screen {
CommunityItem(
modifier = Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.onClick(rememberCallback {
notificationCenter.getObserver(
NotificationCenterContractKeys.SelectCommunity
)
?.invoke(community)
notificationCenter.getObserver(
NotificationCenterContractKeys.CloseDialog
)
?.invoke(Unit)
}),
.onClick(
onClick = rememberCallback {
notificationCenter.getObserver(
NotificationCenterContractKeys.SelectCommunity
)
?.invoke(community)
notificationCenter.getObserver(
NotificationCenterContractKeys.CloseDialog
)
?.invoke(Unit)
},
),
autoLoadImages = uiState.autoLoadImages,
community = community,
)

View File

@ -46,6 +46,7 @@ interface UserDetailMviModel :
val user: UserModel = UserModel(),
val blurNsfw: Boolean = true,
val swipeActionsEnabled: Boolean = true,
val doubleTapActionEnabled: Boolean = true,
val postLayout: PostLayout = PostLayout.Card,
val fullHeightImages: Boolean = true,
val separateUpAndDownVotes: Boolean = false,

View File

@ -227,11 +227,11 @@ class UserDetailScreen(
// sort button
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val sheet = SortBottomSheet(
expandTop = true,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
),
imageVector = uiState.sortType.toIcon(),
@ -257,7 +257,7 @@ class UserDetailScreen(
modifier = Modifier.onGloballyPositioned {
optionsOffset = it.positionInParent()
}.padding(start = Spacing.s).onClick(
rememberCallback {
onClick = rememberCallback {
optionsExpanded = true
},
),
@ -281,7 +281,7 @@ class UserDetailScreen(
horizontal = Spacing.m,
vertical = Spacing.s,
).onClick(
rememberCallback {
onClick = rememberCallback {
optionsExpanded = false
when (option.id) {
OptionId.BlockInstance -> model.reduce(
@ -303,12 +303,11 @@ class UserDetailScreen(
}
},
navigationIcon = {
val navigator = navigationCoordinator.getRootNavigator()
if (navigator?.canPop == true) {
if (navigationCoordinator.canPop) {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigator.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -348,7 +347,7 @@ class UserDetailScreen(
text = stringResource(MR.strings.action_chat),
onSelected = rememberCallback {
val screen = InboxChatScreen(otherUserId = user.id)
navigationCoordinator.getRootNavigator()?.push(screen)
navigationCoordinator.pushScreen(screen)
},
)
}
@ -391,8 +390,7 @@ class UserDetailScreen(
user = uiState.user,
autoLoadImages = uiState.autoLoadImages,
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()
?.push(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
},
)
SectionSelector(
@ -486,8 +484,21 @@ class UserDetailScreen(
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
autoLoadImages = uiState.autoLoadImages,
onClick = rememberCallback {
navigationCoordinator.getRootNavigator()
?.push(PostDetailScreen(post = post))
navigationCoordinator.pushScreen(
PostDetailScreen(post = post),
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled) {
null
} else {
rememberCallback(model) {
model.reduce(
UserDetailMviModel.Intent.UpVotePost(
id = post.id,
feedback = true,
),
)
}
},
onUpVote = if (!uiState.isLogged || isOnOtherInstance) {
null
@ -526,8 +537,9 @@ class UserDetailScreen(
}
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()
?.push(CommunityDetailScreen(community))
navigationCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
onReply = if (!uiState.isLogged || isOnOtherInstance) {
null
@ -536,12 +548,11 @@ class UserDetailScreen(
val screen = CreateCommentScreen(
originalPost = post,
)
navigationCoordinator.getBottomNavigator()
?.show(screen)
navigationCoordinator.showBottomSheet(screen)
}
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
)
},
@ -576,19 +587,17 @@ class UserDetailScreen(
onOptionSelected = rememberCallbackArgs { optionId ->
when (optionId) {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
postId = post.id
)
navigationCoordinator.showBottomSheet(
CreateReportScreen(
postId = post.id
)
)
}
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(crossPost = post)
)
navigationCoordinator.showBottomSheet(
CreatePostScreen(crossPost = post)
)
}
OptionId.SeeRaw -> {
@ -689,13 +698,25 @@ class UserDetailScreen(
hideAuthor = true,
hideIndent = true,
onClick = rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(
post = PostModel(id = comment.postId),
highlightCommentId = comment.id,
)
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled) {
null
} else {
rememberCallback(model) {
model.reduce(
UserDetailMviModel.Intent.UpVoteComment(
id = comment.id,
feedback = true,
),
)
}
},
onSave = if (!uiState.isLogged || isOnOtherInstance) {
null
} else {
@ -740,13 +761,15 @@ class UserDetailScreen(
originalPost = PostModel(id = comment.postId),
originalComment = comment,
)
navigationCoordinator.getBottomNavigator()
?.show(screen)
navigationCoordinator.showBottomSheet(screen)
}
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()
?.push(CommunityDetailScreen(community))
navigationCoordinator.pushScreen(
CommunityDetailScreen(
community
)
)
},
options = buildList {
add(
@ -767,12 +790,11 @@ class UserDetailScreen(
onOptionSelected = rememberCallbackArgs { optionId ->
when (optionId) {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
commentId = comment.id
)
)
navigationCoordinator.showBottomSheet(
CreateReportScreen(
commentId = comment.id
),
)
}
OptionId.SeeRaw -> {

View File

@ -79,6 +79,7 @@ class UserDetailViewModel(
it.copy(
blurNsfw = settings.blurNsfw,
swipeActionsEnabled = settings.enableSwipeActions,
doubleTapActionEnabled = settings.enableDoubleTapAction,
sortType = settings.defaultPostSortType.toSortType(),
separateUpAndDownVotes = settings.separateUpAndDownVotes,
autoLoadImages = settings.autoLoadImages,

View File

@ -35,7 +35,7 @@ class WebViewScreen(
override fun Content() {
val navigationCoordinator = remember { getNavigationCoordinator() }
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
var shareHelper = remember { getShareHelper() }
val shareHelper = remember { getShareHelper() }
val drawerCoordinator = remember { getDrawerCoordinator() }
DisposableEffect(key) {
@ -53,8 +53,8 @@ class WebViewScreen(
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -65,7 +65,7 @@ class WebViewScreen(
actions = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
shareHelper.share(url, "text/plain")
},
),
@ -80,7 +80,6 @@ class WebViewScreen(
Box(
modifier = Modifier.padding(paddingValues)
) {
val navigationCoordinator = remember { getNavigationCoordinator() }
val webNavigator = rememberWebViewNavigator()
DisposableEffect(key) {

View File

@ -46,6 +46,7 @@ kotlin {
api(libs.markdown)
implementation(projects.coreCommonui.components)
implementation(projects.coreUtils)
implementation(projects.resources)
}
}

View File

@ -3,11 +3,11 @@ package com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose
import android.content.Context
import android.graphics.Typeface
import android.util.TypedValue
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import androidx.annotation.IdRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
@ -30,6 +30,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownColo
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownPadding
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownTypography
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.ReferenceLinkHandlerImpl
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.DateTime
import io.noties.markwon.image.AsyncDrawableSpan
import org.intellij.markdown.flavours.MarkdownFlavourDescriptor
@ -50,6 +52,7 @@ actual fun CustomMarkdown(
autoLoadImages: Boolean,
onOpenImage: ((String) -> Unit)?,
onClick: (() -> Unit)?,
onDoubleClick: (() -> Unit)?,
) {
CompositionLocalProvider(
LocalReferenceLinkHandler provides ReferenceLinkHandlerImpl(),
@ -64,10 +67,10 @@ actual fun CustomMarkdown(
)
}
BoxWithConstraints(
modifier = modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) { onClick?.invoke() }
modifier = modifier.onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
)
) {
val style = LocalMarkdownTypography.current.text
val fontScale = LocalDensity.current.fontScale * 1.25f
@ -93,9 +96,35 @@ actual fun CustomMarkdown(
typeface = typeface,
fontSize = style.fontSize * fontScale,
).apply {
setOnClickListener {
onClick?.invoke()
}
val gestureDetector =
GestureDetector(
ctx,
object : GestureDetector.SimpleOnGestureListener() {
private var lastClickTime = 0L
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
val currentTime = DateTime.epochMillis()
if ((currentTime - lastClickTime) < 300) return false
lastClickTime = currentTime
onClick?.invoke()
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
val currentTime = DateTime.epochMillis()
if ((currentTime - lastClickTime) < 300) return false
lastClickTime = currentTime
onDoubleClick?.invoke()
return true
}
override fun onDown(e: MotionEvent): Boolean {
return true
}
}
)
setOnTouchListener { _, evt -> gestureDetector.onTouchEvent(evt) }
}
},
update = { textView ->

View File

@ -38,4 +38,5 @@ expect fun CustomMarkdown(
autoLoadImages: Boolean = true,
onOpenImage: ((String) -> Unit)? = null,
onClick: (() -> Unit)? = null,
onDoubleClick: (() -> Unit)? = null,
)

View File

@ -4,9 +4,7 @@ import androidx.compose.animation.core.InfiniteRepeatableSpec
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
@ -40,6 +38,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose.LocalMarkd
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose.LocalReferenceLinkHandler
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.utils.TAG_IMAGE_URL
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.utils.TAG_URL
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource
@ -121,12 +120,9 @@ internal fun MarkdownText(
CustomImage(
modifier = Modifier
.fillMaxWidth()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
onOpenImage?.invoke(imageUrl)
},
.onClick(
onClick = { onOpenImage?.invoke(imageUrl) },
),
url = link,
autoload = autoLoadImages,
quality = FilterQuality.Low,
@ -175,12 +171,10 @@ internal fun MarkdownText(
// TODO: improve fixed values
.heightIn(min = 200.dp, max = Dp.Unspecified)
.clip(RoundedCornerShape(20.dp))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
onOpenImage?.invoke(imageUrl)
},
.onClick(
onClick = { onOpenImage?.invoke(imageUrl) },
onDoubleClick = {},
),
url = imageUrl,
autoload = autoLoadImages,
quality = FilterQuality.Low,

View File

@ -44,3 +44,4 @@ internal fun List<ASTNode>.innerList(): List<ASTNode> = this.subList(1, this.siz
internal fun List<ASTNode>.filterNonListTypes(): List<ASTNode> = this.filter { n ->
n.type != MarkdownElementTypes.ORDERED_LIST && n.type != MarkdownElementTypes.UNORDERED_LIST && n.type != MarkdownTokenTypes.EOL
}

View File

@ -1,13 +1,10 @@
package com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose.elements.MarkdownBlockQuote
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.compose.elements.MarkdownBulletList
@ -22,6 +19,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownColo
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownPadding
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownTypography
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.ReferenceLinkHandlerImpl
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownElementTypes.ATX_1
import org.intellij.markdown.MarkdownElementTypes.ATX_2
@ -62,6 +60,7 @@ actual fun CustomMarkdown(
autoLoadImages: Boolean,
onOpenImage: ((String) -> Unit)?,
onClick: (() -> Unit)?,
onDoubleClick: (() -> Unit)?,
) {
val matches = Regex("::: spoiler (?<title>.*?)\\n(?<content>.*?)\\n:::\\n").findAll(content)
val mangledContent = buildString {
@ -97,10 +96,12 @@ actual fun CustomMarkdown(
LocalMarkdownColors provides colors,
LocalMarkdownTypography provides typography,
) {
Column(modifier.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) { onClick?.invoke() }) {
Column(
modifier = modifier.onClick(
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
)
) {
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(mangledContent)
parsedTree.children.forEach { node ->
if (!node.handleElement(

View File

@ -20,6 +20,7 @@ data class SettingsModel(
val dynamicColors: Boolean = false,
val openUrlsInExternalBrowser: Boolean = false,
val enableSwipeActions: Boolean = true,
val enableDoubleTapAction: Boolean = false,
val customSeedColor: Int? = null,
val upvoteColor: Int? = null,
val downvoteColor: Int? = null,

View File

@ -25,6 +25,7 @@ private object KeyStoreKeys {
const val DynamicColors = "dynamicColors"
const val OpenUrlsInExternalBrowser = "openUrlsInExternalBrowser"
const val EnableSwipeActions = "enableSwipeActions"
const val EnableDoubleTapAction = "enableDoubleTapAction"
const val CustomSeedColor = "customPrimaryColor"
const val PostLayout = "postLayout"
const val SeparateUpAndDownVotes = "separateUpAndDownVotes"
@ -64,6 +65,7 @@ internal class DefaultSettingsRepository(
dynamicColors = if (settings.dynamicColors) 1L else 0L,
openUrlsInExternalBrowser = if (settings.openUrlsInExternalBrowser) 1L else 0L,
enableSwipeActions = if (settings.enableSwipeActions) 1L else 0L,
enableDoubleTapAction = if (settings.enableDoubleTapAction) 1L else 0L,
customSeedColor = settings.customSeedColor?.toLong(),
account_id = accountId,
postLayout = settings.postLayout.toLong(),
@ -100,6 +102,7 @@ internal class DefaultSettingsRepository(
dynamicColors = keyStore[KeyStoreKeys.DynamicColors, false],
openUrlsInExternalBrowser = keyStore[KeyStoreKeys.OpenUrlsInExternalBrowser, false],
enableSwipeActions = keyStore[KeyStoreKeys.EnableSwipeActions, true],
enableDoubleTapAction = keyStore[KeyStoreKeys.EnableDoubleTapAction, false],
customSeedColor = if (!keyStore.containsKey(KeyStoreKeys.CustomSeedColor)) null else keyStore[KeyStoreKeys.CustomSeedColor, 0],
postLayout = keyStore[KeyStoreKeys.PostLayout, 0],
separateUpAndDownVotes = keyStore[KeyStoreKeys.SeparateUpAndDownVotes, false],
@ -148,6 +151,7 @@ internal class DefaultSettingsRepository(
value = settings.openUrlsInExternalBrowser
)
keyStore.save(KeyStoreKeys.EnableSwipeActions, settings.enableSwipeActions)
keyStore.save(KeyStoreKeys.EnableDoubleTapAction, settings.enableDoubleTapAction)
if (settings.customSeedColor != null) {
keyStore.save(KeyStoreKeys.CustomSeedColor, settings.customSeedColor)
} else {
@ -196,6 +200,7 @@ internal class DefaultSettingsRepository(
dynamicColors = if (settings.dynamicColors) 1L else 0L,
openUrlsInExternalBrowser = if (settings.openUrlsInExternalBrowser) 1L else 0L,
enableSwipeActions = if (settings.enableSwipeActions) 1L else 0L,
enableDoubleTapAction = if (settings.enableDoubleTapAction) 1L else 0L,
customSeedColor = settings.customSeedColor?.toLong(),
postLayout = settings.postLayout.toLong(),
separateUpAndDownVotes = if (settings.separateUpAndDownVotes) 1L else 0L,
@ -233,6 +238,7 @@ private fun GetBy.toModel() = SettingsModel(
dynamicColors = dynamicColors != 0L,
openUrlsInExternalBrowser = openUrlsInExternalBrowser != 0L,
enableSwipeActions = enableSwipeActions != 0L,
enableDoubleTapAction = enableDoubleTapAction != 0L,
customSeedColor = customSeedColor?.toInt(),
postLayout = postLayout.toInt(),
separateUpAndDownVotes = separateUpAndDownVotes != 0L,

View File

@ -14,6 +14,7 @@ CREATE TABLE SettingsEntity (
dynamicColors INTEGER NOT NULL DEFAULT 0,
openUrlsInExternalBrowser INTEGER NOT NULL DEFAULT 0,
enableSwipeActions INTEGER NOT NULL DEFAULT 1,
enableDoubleTapAction INTEGER NOT NULL DEFAULT 1,
customSeedColor INTEGER DEFAULT NULL,
postLayout INTEGER NOT NULL DEFAULT 0,
separateUpAndDownVotes INTEGER NOT NULL DEFAULT 0,
@ -46,6 +47,7 @@ INSERT OR IGNORE INTO SettingsEntity (
dynamicColors,
openUrlsInExternalBrowser,
enableSwipeActions,
enableDoubleTapAction,
customSeedColor,
postLayout,
separateUpAndDownVotes,
@ -84,6 +86,7 @@ INSERT OR IGNORE INTO SettingsEntity (
?,
?,
?,
?,
?
);
@ -103,6 +106,7 @@ SET theme = ?,
dynamicColors = ?,
openUrlsInExternalBrowser = ?,
enableSwipeActions = ?,
enableDoubleTapAction = ?,
customSeedColor = ?,
postLayout = ?,
separateUpAndDownVotes = ?,
@ -133,6 +137,7 @@ SELECT
dynamicColors,
openUrlsInExternalBrowser,
enableSwipeActions,
enableDoubleTapAction,
customSeedColor,
postLayout,
separateUpAndDownVotes,

View File

@ -0,0 +1,2 @@
ALTER TABLE SettingsEntity
ADD COLUMN enableDoubleTapAction INTEGER NOT NULL DEFAULT 1;

View File

@ -1,16 +1,37 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils.compose
import androidx.compose.foundation.clickable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.DateTime
fun Modifier.onClick(onClick: () -> Unit): Modifier = composed {
clickable(
@OptIn(ExperimentalFoundationApi::class)
fun Modifier.onClick(
debounceInterval: Long = 300,
onClick: () -> Unit = {},
onDoubleClick: () -> Unit = {},
): Modifier = composed {
var lastClickTime by remember { mutableStateOf(0L) }
combinedClickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
) {
onClick()
}
onClick = {
val currentTime = DateTime.epochMillis()
if ((currentTime - lastClickTime) < debounceInterval) return@combinedClickable
lastClickTime = currentTime
onClick()
},
onDoubleClick = {
val currentTime = DateTime.epochMillis()
if ((currentTime - lastClickTime) < debounceInterval) return@combinedClickable
lastClickTime = currentTime
onDoubleClick()
},
)
}

View File

@ -35,6 +35,6 @@ val PostModel.shareUrl: String
}
val PostModel.imageUrl: String
get() = thumbnailUrl?.takeIf { it.isNotEmpty() } ?: run {
url?.takeIf { it.looksLikeAnImage }
get() = url?.takeIf { it.looksLikeAnImage }?.takeIf { it.isNotEmpty() } ?: run {
thumbnailUrl
}.orEmpty()

View File

@ -44,6 +44,7 @@ interface PostListMviModel :
val blurNsfw: Boolean = true,
val currentUserId: Int? = null,
val swipeActionsEnabled: Boolean = true,
val doubleTapActionEnabled: Boolean = false,
val postLayout: PostLayout = PostLayout.Card,
val fullHeightImages: Boolean = true,
val separateUpAndDownVotes: Boolean = false,

View File

@ -198,13 +198,13 @@ class PostListScreen : Screen {
val sheet = ListingTypeBottomSheet(
isLogged = uiState.isLogged,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
onSelectSortType = rememberCallback {
val sheet = SortBottomSheet(
expandTop = true,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
},
@ -315,6 +315,14 @@ class PostListScreen : Screen {
SwipeableCard(
modifier = Modifier.fillMaxWidth(),
enabled = uiState.swipeActionsEnabled,
directions = if (!uiState.isLogged) {
emptySet()
} else {
setOf(
DismissDirection.StartToEnd,
DismissDirection.EndToStart,
)
},
backgroundColor = rememberCallbackArgs {
when (it) {
DismissValue.DismissedToStart -> upvoteColor
@ -356,51 +364,72 @@ class PostListScreen : Screen {
blurNsfw = uiState.blurNsfw,
onClick = rememberCallback(model) {
model.reduce(PostListMviModel.Intent.MarkAsRead(post.id))
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(post),
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled || !uiState.isLogged) {
null
} else {
rememberCallback(model) {
model.reduce(
PostListMviModel.Intent.UpVotePost(
id = post.id,
feedback = true,
),
)
}
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
onOpenCreator = rememberCallbackArgs { user ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(user),
)
},
onUpVote = rememberCallback(model) {
model.reduce(
PostListMviModel.Intent.UpVotePost(
id = post.id,
feedback = true,
),
)
if (uiState.isLogged) {
model.reduce(
PostListMviModel.Intent.UpVotePost(
id = post.id,
feedback = true,
),
)
}
},
onDownVote = rememberCallback(model) {
model.reduce(
PostListMviModel.Intent.DownVotePost(
id = post.id,
feedback = true,
),
)
if (uiState.isLogged) {
model.reduce(
PostListMviModel.Intent.DownVotePost(
id = post.id,
feedback = true,
),
)
}
},
onSave = rememberCallback(model) {
model.reduce(
PostListMviModel.Intent.SavePost(
id = post.id,
feedback = true,
),
)
if (uiState.isLogged) {
model.reduce(
PostListMviModel.Intent.SavePost(
id = post.id,
feedback = true,
),
)
}
},
onReply = rememberCallback(model) {
val screen = CreateCommentScreen(originalPost = post)
navigationCoordinator.getBottomNavigator()?.show(screen)
if (uiState.isLogged) {
val screen =
CreateCommentScreen(originalPost = post)
navigationCoordinator.showBottomSheet(screen)
}
},
onImageClick = rememberCallbackArgs(model, post) { url ->
model.reduce(PostListMviModel.Intent.MarkAsRead(post.id))
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
ZoomableImageScreen(url)
)
},
@ -411,7 +440,7 @@ class PostListScreen : Screen {
stringResource(MR.strings.post_action_share)
)
)
if (uiState.currentUserId != null) {
if (uiState.isLogged) {
add(
Option(
OptionId.Hide,
@ -425,7 +454,7 @@ class PostListScreen : Screen {
stringResource(MR.strings.post_action_see_raw)
)
)
if (uiState.currentUserId != null) {
if (uiState.isLogged) {
add(
Option(
OptionId.CrossPost,
@ -461,24 +490,21 @@ class PostListScreen : Screen {
)
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(editedPost = post)
)
navigationCoordinator.showBottomSheet(
CreatePostScreen(editedPost = post)
)
}
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(postId = post.id)
)
navigationCoordinator.showBottomSheet(
CreateReportScreen(postId = post.id)
)
}
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(crossPost = post)
)
navigationCoordinator.showBottomSheet(
CreatePostScreen(crossPost = post)
)
}
OptionId.SeeRaw -> {

View File

@ -106,6 +106,7 @@ class PostListViewModel(
it.copy(
blurNsfw = settings.blurNsfw,
swipeActionsEnabled = settings.enableSwipeActions,
doubleTapActionEnabled = settings.enableDoubleTapAction,
separateUpAndDownVotes = settings.separateUpAndDownVotes,
autoLoadImages = settings.autoLoadImages,
fullHeightImages = settings.fullHeightImages,

View File

@ -47,7 +47,7 @@ internal fun PostsTopBar(
onHamburgerTapped != null -> {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onHamburgerTapped()
},
),
@ -60,7 +60,7 @@ internal fun PostsTopBar(
listingType != null -> {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onSelectListingType?.invoke()
},
),
@ -80,7 +80,7 @@ internal fun PostsTopBar(
modifier = Modifier
.padding(horizontal = Spacing.s)
.onClick(
rememberCallback {
onClick = rememberCallback {
onSelectListingType?.invoke()
},
),
@ -115,7 +115,7 @@ internal fun PostsTopBar(
if (sortType != null) {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onSelectSortType?.invoke()
},
),

View File

@ -105,7 +105,7 @@ object InboxScreen : Tab {
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
scope.launch {
drawerCoordinator.toggleDrawer()
}
@ -128,9 +128,9 @@ object InboxScreen : Tab {
}
Text(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val sheet = InboxTypeSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
),
text = text,
@ -142,7 +142,7 @@ object InboxScreen : Tab {
if (uiState.isLogged == true) {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
model.reduce(InboxMviModel.Intent.ReadAll)
},
),

View File

@ -46,6 +46,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.Co
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.InboxCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.InboxCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.InboxCardType
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Option
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.OptionId
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen
@ -181,7 +183,7 @@ class InboxMentionsScreen : Tab {
autoLoadImages = uiState.autoLoadImages,
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
onOpenPost = rememberCallbackArgs { post ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(
post = post,
highlightCommentId = mention.comment.id,
@ -189,12 +191,12 @@ class InboxMentionsScreen : Tab {
)
},
onOpenCreator = rememberCallbackArgs { user ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(user),
)
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
@ -208,6 +210,39 @@ class InboxMentionsScreen : Tab {
)
)
},
options = buildList {
add(
Option(
OptionId.MarkRead,
stringResource(MR.strings.inbox_action_mark_read)
)
)
add(
Option(
OptionId.MarkUnread,
stringResource(MR.strings.inbox_action_mark_unread)
)
)
},
onOptionSelected = rememberCallbackArgs(model) { optionId ->
when (optionId) {
OptionId.MarkRead -> model.reduce(
InboxMentionsMviModel.Intent.MarkAsRead(
read = true,
id = mention.id,
),
)
OptionId.MarkUnread -> model.reduce(
InboxMentionsMviModel.Intent.MarkAsRead(
read = false,
id = mention.id,
),
)
else -> Unit
}
}
)
},
)

View File

@ -1,32 +1,47 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.inbox.messages
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
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.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomDropDown
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomImage
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Option
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.OptionId
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PlaceholderImage
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ScaledContent
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
@Composable
@ -36,24 +51,30 @@ internal fun ChatCard(
lastMessage: String,
lastMessageDate: String? = null,
modifier: Modifier = Modifier,
options: List<Option> = emptyList(),
onOpenUser: ((UserModel) -> Unit)? = null,
onOpen: (() -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
) {
var optionsExpanded by remember { mutableStateOf(false) }
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
val creatorName = user?.name.orEmpty()
val creatorHost = user?.host.orEmpty()
val creatorAvatar = user?.avatar.orEmpty()
val iconSize = IconSize.xl
Row(
modifier = modifier
.padding(Spacing.xs)
.onClick(
rememberCallback {
onClick = rememberCallback {
onOpen?.invoke()
},
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Spacing.m),
) {
val creatorName = user?.name.orEmpty()
val creatorHost = user?.host.orEmpty()
val creatorAvatar = user?.avatar.orEmpty()
val iconSize = 46.dp
if (creatorAvatar.isNotEmpty()) {
CustomImage(
@ -62,7 +83,7 @@ internal fun ChatCard(
.size(iconSize)
.clip(RoundedCornerShape(iconSize / 2))
.onClick(
rememberCallback {
onClick = rememberCallback {
if (user != null) {
onOpenUser?.invoke(user)
}
@ -77,7 +98,7 @@ internal fun ChatCard(
} else {
PlaceholderImage(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
if (user != null) {
onOpenUser?.invoke(user)
}
@ -115,22 +136,67 @@ internal fun ChatCard(
// last message date
if (lastMessageDate != null) {
Row(
horizontalArrangement = Arrangement.spacedBy(Spacing.xxs),
verticalAlignment = Alignment.CenterVertically,
) {
val buttonModifier = Modifier.size(IconSize.m).padding(3.25.dp)
Icon(
modifier = buttonModifier.padding(1.dp),
imageVector = Icons.Default.Schedule,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
Text(
text = lastMessageDate.prettifyDate(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurface,
)
Box {
Row(
horizontalArrangement = Arrangement.spacedBy(Spacing.xxs),
verticalAlignment = Alignment.CenterVertically,
) {
val buttonModifier = Modifier.size(IconSize.m).padding(3.5.dp)
Icon(
modifier = buttonModifier.padding(1.dp),
imageVector = Icons.Default.Schedule,
contentDescription = null,
tint = MaterialTheme.colorScheme.onBackground,
)
Text(
text = lastMessageDate.prettifyDate(),
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.weight(1f))
if (options.isNotEmpty()) {
Icon(
modifier = buttonModifier
.padding(top = Spacing.xxs)
.onGloballyPositioned {
optionsOffset = it.positionInParent()
}
.onClick(
onClick = rememberCallback {
optionsExpanded = true
},
),
imageVector = Icons.Default.MoreHoriz,
contentDescription = null,
)
}
}
CustomDropDown(
expanded = optionsExpanded,
onDismiss = {
optionsExpanded = false
},
offset = DpOffset(
x = optionsOffset.x.toLocalDp(),
y = optionsOffset.y.toLocalDp(),
),
) {
options.forEach { option ->
Text(
modifier = Modifier.padding(
horizontal = Spacing.m,
vertical = Spacing.s,
).onClick(
onClick = rememberCallback {
optionsExpanded = false
onOptionSelected?.invoke(option.id)
},
),
text = option.text,
)
}
}
}
}
}

View File

@ -118,18 +118,18 @@ class InboxMessagesScreen : Tab {
lastMessage = chat.content.orEmpty(),
lastMessageDate = chat.publishDate,
onOpenUser = rememberCallbackArgs { user ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(user)
)
},
onOpen = rememberCallback {
val userId = otherUser?.id
if (userId != null) {
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
InboxChatScreen(userId)
)
}
}
},
)
}
item {

View File

@ -46,6 +46,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.Co
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.InboxCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.InboxCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.InboxCardType
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Option
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.OptionId
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen
@ -180,7 +182,7 @@ class InboxRepliesScreen : Tab {
autoLoadImages = uiState.autoLoadImages,
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
onOpenPost = rememberCallbackArgs { post ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(
post = post,
highlightCommentId = reply.comment.id,
@ -188,12 +190,12 @@ class InboxRepliesScreen : Tab {
)
},
onOpenCreator = rememberCallbackArgs { user ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(user),
)
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
@ -203,6 +205,39 @@ class InboxRepliesScreen : Tab {
onDownVote = rememberCallbackArgs(model) {
model.reduce(InboxRepliesMviModel.Intent.DownVoteComment(reply.id))
},
options = buildList {
add(
Option(
OptionId.MarkRead,
stringResource(MR.strings.inbox_action_mark_read)
)
)
add(
Option(
OptionId.MarkUnread,
stringResource(MR.strings.inbox_action_mark_unread)
)
)
},
onOptionSelected = rememberCallbackArgs(model) { optionId ->
when (optionId) {
OptionId.MarkRead -> model.reduce(
InboxRepliesMviModel.Intent.MarkAsRead(
read = true,
id = reply.id,
),
)
OptionId.MarkUnread -> model.reduce(
InboxRepliesMviModel.Intent.MarkAsRead(
read = false,
id = reply.id,
),
)
else -> Unit
}
}
)
},
)

View File

@ -142,8 +142,7 @@ internal object ProfileLoggedScreen : Tab {
user = user,
autoLoadImages = uiState.autoLoadImages,
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()
?.push(ZoomableImageScreen(url))
navigationCoordinator.pushScreen(ZoomableImageScreen(url))
},
)
SectionSelector(
@ -193,17 +192,17 @@ internal object ProfileLoggedScreen : Tab {
hideAuthor = true,
blurNsfw = false,
onClick = rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(post),
)
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
)
},
@ -264,7 +263,7 @@ internal object ProfileLoggedScreen : Tab {
)
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()?.show(
navigationCoordinator.showBottomSheet(
CreatePostScreen(
editedPost = post,
)
@ -324,7 +323,7 @@ internal object ProfileLoggedScreen : Tab {
hideAuthor = true,
hideIndent = true,
onClick = rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(
post = PostModel(id = comment.postId),
highlightCommentId = comment.id,
@ -386,7 +385,7 @@ internal object ProfileLoggedScreen : Tab {
}
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()?.show(
navigationCoordinator.showBottomSheet(
CreateCommentScreen(editedComment = comment)
)
}

View File

@ -87,7 +87,7 @@ class LoginBottomSheet : Screen {
}
LoginBottomSheetMviModel.Effect.LoginSuccess -> {
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
}
}
}.launchIn(this)
@ -125,12 +125,11 @@ class LoginBottomSheet : Screen {
IconButton(
modifier = Modifier.align(Alignment.TopEnd),
onClick = {
navigationCoordinator.getBottomNavigator()?.hide()
handleUrl(
navigationCoordinator.hideBottomSheet()
navigationCoordinator.handleUrl(
url = HELP_URL,
openExternal = settingsRepository.currentSettings.value.openUrlsInExternalBrowser,
uriHandler = uriHandler,
navigator = navigationCoordinator.getRootNavigator(),
)
},
) {
@ -181,7 +180,7 @@ class LoginBottomSheet : Screen {
if (uiState.instanceName.isNotEmpty()) {
Icon(
modifier = Modifier.onClick(
rememberCallback(model) {
onClick = rememberCallback(model) {
model.reduce(
LoginBottomSheetMviModel.Intent.SetInstanceName("")
)
@ -254,7 +253,7 @@ class LoginBottomSheet : Screen {
trailingIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
transformation =
if (transformation == VisualTransformation.None) {
PasswordVisualTransformation()

View File

@ -86,7 +86,7 @@ internal object ProfileMainScreen : Tab {
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
scope.launch {
drawerCoordinator.toggleDrawer()
}
@ -108,9 +108,8 @@ internal object ProfileMainScreen : Tab {
if (uiState.logged == true) {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getBottomNavigator()
?.show(ManageAccountsScreen())
onClick = rememberCallback {
navigationCoordinator.showBottomSheet(ManageAccountsScreen())
},
),
imageVector = Icons.Default.ManageAccounts,
@ -122,7 +121,7 @@ internal object ProfileMainScreen : Tab {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
model.reduce(ProfileMainMviModel.Intent.Logout)
},
),

View File

@ -64,7 +64,7 @@ class ManageAccountsScreen : Screen {
model.effects.onEach { effect ->
when (effect) {
ManageAccountsMviModel.Effect.Close -> {
navigationCoordinator.getBottomNavigator()?.hide()
navigationCoordinator.hideBottomSheet()
}
}
}.launchIn(this)
@ -104,7 +104,7 @@ class ManageAccountsScreen : Screen {
modifier = Modifier
.fillMaxWidth()
.onClick(
rememberCallback {
onClick = rememberCallback {
model.reduce(ManageAccountsMviModel.Intent.SwitchAccount(idx))
},
)
@ -151,7 +151,7 @@ class ManageAccountsScreen : Screen {
Spacer(modifier = Modifier.height(Spacing.m))
Button(
onClick = {
navigationCoordinator.getBottomNavigator()?.show(LoginBottomSheet())
navigationCoordinator.showBottomSheet(LoginBottomSheet())
},
) {
Row(

View File

@ -42,7 +42,7 @@ internal object ProfileNotLoggedScreen : Tab {
Button(
modifier = Modifier.align(Alignment.CenterHorizontally),
onClick = {
navigationCoordinator.getBottomNavigator()?.show(
navigationCoordinator.showBottomSheet(
LoginBottomSheet(),
)
},

View File

@ -18,6 +18,7 @@ interface ExploreMviModel :
data class SetListingType(val value: ListingType) : Intent
data class SetSortType(val value: SortType) : Intent
data class SetResultType(val value: SearchResultType) : Intent
data object HapticIndication : Intent
data class UpVotePost(val id: Int, val feedback: Boolean = false) : Intent
data class DownVotePost(val id: Int, val feedback: Boolean = false) : Intent
data class SavePost(val id: Int, val feedback: Boolean = false) : Intent
@ -31,6 +32,8 @@ interface ExploreMviModel :
val loading: Boolean = false,
val canFetchMore: Boolean = true,
val isLogged: Boolean = false,
val swipeActionsEnabled: Boolean = false,
val doubleTapActionEnabled: Boolean = false,
val blurNsfw: Boolean = true,
val instance: String = "",
val searchText: String = "",

View File

@ -15,12 +15,16 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowCircleDown
import androidx.compose.material.icons.filled.ArrowCircleUp
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DismissDirection
import androidx.compose.material3.DismissValue
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
@ -42,6 +46,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
@ -52,6 +57,7 @@ import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.di.getThemeRepository
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
@ -59,6 +65,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Comment
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommunityItem
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserItem
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getDrawerCoordinator
@ -114,6 +121,12 @@ class ExploreScreen : Screen {
}
val settingsRepository = remember { getSettingsRepository() }
val settings by settingsRepository.currentSettings.collectAsState()
val themeRepository = remember { getThemeRepository() }
val upvoteColor by themeRepository.upvoteColor.collectAsState()
val downvoteColor by themeRepository.downvoteColor.collectAsState()
val defaultUpvoteColor = MaterialTheme.colorScheme.primary
val defaultDownVoteColor = MaterialTheme.colorScheme.tertiary
DisposableEffect(key) {
onDispose {
notificationCenter.removeObserver(key)
@ -176,14 +189,14 @@ class ExploreScreen : Screen {
val sheet = ListingTypeBottomSheet(
isLogged = uiState.isLogged,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
onSelectSortType = rememberCallback {
focusManager.clearFocus()
val sheet = SortBottomSheet(
expandTop = true,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
onHamburgerTapped = rememberCallback {
scope.launch {
@ -217,7 +230,7 @@ class ExploreScreen : Screen {
trailingIcon = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
if (uiState.searchText.isNotEmpty()) {
model.reduce(ExploreMviModel.Intent.SetSearch(""))
}
@ -308,8 +321,8 @@ class ExploreScreen : Screen {
modifier = Modifier
.fillMaxWidth()
.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
onClick = rememberCallback {
navigationCoordinator.pushScreen(
CommunityDetailScreen(result),
)
},
@ -320,61 +333,110 @@ class ExploreScreen : Screen {
}
is PostModel -> {
PostCard(
post = result,
postLayout = uiState.postLayout,
fullHeightImage = uiState.fullHeightImages,
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
autoLoadImages = uiState.autoLoadImages,
blurNsfw = uiState.blurNsfw,
onClick = rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
PostDetailScreen(result),
SwipeableCard(
modifier = Modifier.fillMaxWidth(),
enabled = uiState.swipeActionsEnabled,
backgroundColor = rememberCallbackArgs {
when (it) {
DismissValue.DismissedToStart -> upvoteColor
?: defaultUpvoteColor
DismissValue.DismissedToEnd -> downvoteColor
?: defaultDownVoteColor
DismissValue.Default -> Color.Transparent
}
},
onGestureBegin = {
model.reduce(ExploreMviModel.Intent.HapticIndication)
},
onDismissToStart = rememberCallback(model) {
model.reduce(ExploreMviModel.Intent.UpVotePost(result.id))
},
onDismissToEnd = rememberCallback(model) {
model.reduce(ExploreMviModel.Intent.DownVotePost(result.id))
},
swipeContent = { direction ->
val icon = when (direction) {
DismissDirection.StartToEnd -> Icons.Default.ArrowCircleDown
DismissDirection.EndToStart -> Icons.Default.ArrowCircleUp
}
androidx.compose.material.Icon(
imageVector = icon,
contentDescription = null,
tint = Color.White,
)
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.getRootNavigator()?.push(
CommunityDetailScreen(community),
)
},
onOpenCreator = rememberCallbackArgs { user ->
navigationCoordinator.getRootNavigator()?.push(
UserDetailScreen(user),
)
},
onUpVote = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.UpVotePost(
id = result.id,
feedback = true,
),
)
},
onDownVote = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.DownVotePost(
id = result.id,
feedback = true,
),
)
},
onSave = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.SavePost(
id = result.id,
feedback = true,
),
)
},
onReply = rememberCallback {
val screen = CreateCommentScreen(
originalPost = result,
)
navigationCoordinator.getBottomNavigator()?.show(screen)
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()?.push(
ZoomableImageScreen(url),
content = {
PostCard(
post = result,
postLayout = uiState.postLayout,
fullHeightImage = uiState.fullHeightImages,
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
autoLoadImages = uiState.autoLoadImages,
blurNsfw = uiState.blurNsfw,
onClick = rememberCallback {
navigationCoordinator.pushScreen(
PostDetailScreen(result),
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled) {
null
} else {
rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.UpVotePost(
id = result.id,
feedback = true,
),
)
}
},
onOpenCommunity = rememberCallbackArgs { community ->
navigationCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
onOpenCreator = rememberCallbackArgs { user ->
navigationCoordinator.pushScreen(
UserDetailScreen(user),
)
},
onUpVote = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.UpVotePost(
id = result.id,
feedback = true,
),
)
},
onDownVote = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.DownVotePost(
id = result.id,
feedback = true,
),
)
},
onSave = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.SavePost(
id = result.id,
feedback = true,
),
)
},
onReply = rememberCallback {
val screen = CreateCommentScreen(
originalPost = result,
)
navigationCoordinator.showBottomSheet(screen)
},
onImageClick = rememberCallbackArgs { url ->
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
)
},
)
},
)
@ -386,59 +448,124 @@ class ExploreScreen : Screen {
}
is CommentModel -> {
CommentCard(
modifier = Modifier.background(MaterialTheme.colorScheme.background),
comment = result,
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
autoLoadImages = uiState.autoLoadImages,
hideIndent = true,
onClick = rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
PostDetailScreen(
post = PostModel(id = result.postId),
highlightCommentId = result.id,
),
SwipeableCard(
modifier = Modifier.fillMaxWidth(),
enabled = uiState.swipeActionsEnabled,
directions = if (!uiState.isLogged) {
emptySet()
} else {
setOf(
DismissDirection.StartToEnd,
DismissDirection.EndToStart,
)
},
onUpVote = rememberCallback(model) {
backgroundColor = rememberCallbackArgs {
when (it) {
DismissValue.DismissedToStart -> upvoteColor
?: defaultUpvoteColor
DismissValue.DismissedToEnd -> downvoteColor
?: defaultDownVoteColor
DismissValue.Default -> Color.Transparent
}
},
onGestureBegin = rememberCallback(model) {
model.reduce(ExploreMviModel.Intent.HapticIndication)
},
onDismissToStart = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.UpVoteComment(
id = result.id,
feedback = true,
id = result.id
),
)
},
onDownVote = rememberCallback(model) {
onDismissToEnd = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.DownVoteComment(
id = result.id,
feedback = true,
id = result.id
),
)
},
onSave = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.SaveComment(
id = result.id,
feedback = true,
),
swipeContent = { direction ->
val icon = when (direction) {
DismissDirection.StartToEnd -> Icons.Default.ArrowCircleDown
DismissDirection.EndToStart -> Icons.Default.ArrowCircleUp
}
androidx.compose.material.Icon(
imageVector = icon,
contentDescription = null,
tint = Color.White,
)
},
onReply = rememberCallback {
val screen = CreateCommentScreen(
originalPost = PostModel(id = result.postId),
originalComment = result,
)
navigationCoordinator.getBottomNavigator()?.show(screen)
},
onOpenCommunity = rememberCallbackArgs {
navigationCoordinator.getRootNavigator()?.push(
CommunityDetailScreen(it)
)
},
onOpenCreator = rememberCallbackArgs {
navigationCoordinator.getRootNavigator()?.push(
UserDetailScreen(it)
content = {
CommentCard(
modifier = Modifier.background(MaterialTheme.colorScheme.background),
comment = result,
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
autoLoadImages = uiState.autoLoadImages,
hideIndent = true,
onClick = rememberCallback {
navigationCoordinator.pushScreen(
PostDetailScreen(
post = PostModel(id = result.postId),
highlightCommentId = result.id,
),
)
},
onDoubleClick = if (!uiState.doubleTapActionEnabled) {
null
} else {
rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.UpVoteComment(
id = result.id,
feedback = true,
),
)
}
},
onUpVote = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.UpVoteComment(
id = result.id,
feedback = true,
),
)
},
onDownVote = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.DownVoteComment(
id = result.id,
feedback = true,
),
)
},
onSave = rememberCallback(model) {
model.reduce(
ExploreMviModel.Intent.SaveComment(
id = result.id,
feedback = true,
),
)
},
onReply = rememberCallback {
val screen = CreateCommentScreen(
originalPost = PostModel(id = result.postId),
originalComment = result,
)
navigationCoordinator.showBottomSheet(screen)
},
onOpenCommunity = rememberCallbackArgs {
navigationCoordinator.pushScreen(
CommunityDetailScreen(it)
)
},
onOpenCreator = rememberCallbackArgs {
navigationCoordinator.pushScreen(
UserDetailScreen(it)
)
},
)
},
)
@ -453,8 +580,8 @@ class ExploreScreen : Screen {
modifier = Modifier
.fillMaxWidth()
.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.push(
onClick = rememberCallback {
navigationCoordinator.pushScreen(
UserDetailScreen(result),
)
},

View File

@ -46,7 +46,7 @@ internal fun ExploreTopBar(
onHamburgerTapped != null -> {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onHamburgerTapped()
},
),
@ -59,7 +59,7 @@ internal fun ExploreTopBar(
listingType != null -> {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onSelectListingType?.invoke()
},
),
@ -79,7 +79,7 @@ internal fun ExploreTopBar(
modifier = Modifier
.padding(horizontal = Spacing.s)
.onClick(
rememberCallback {
onClick = rememberCallback {
onSelectListingType?.invoke()
},
),
@ -109,7 +109,7 @@ internal fun ExploreTopBar(
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
onSelectSortType?.invoke()
},
),

View File

@ -89,6 +89,8 @@ class ExploreViewModel(
separateUpAndDownVotes = settings.separateUpAndDownVotes,
autoLoadImages = settings.autoLoadImages,
fullHeightImages = settings.fullHeightImages,
swipeActionsEnabled = settings.enableSwipeActions,
doubleTapActionEnabled = settings.enableDoubleTapAction,
)
}
}.launchIn(this)
@ -126,6 +128,7 @@ class ExploreViewModel(
}
}
ExploreMviModel.Intent.HapticIndication -> hapticFeedback.vibrate()
is ExploreMviModel.Intent.SetSearch -> setSearch(intent.value)
is ExploreMviModel.Intent.SetListingType -> changeListingType(intent.value)
is ExploreMviModel.Intent.SetSortType -> changeSortType(intent.value)

View File

@ -108,8 +108,8 @@ class ManageSubscriptionsScreen : Screen {
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigatorCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigatorCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -178,8 +178,8 @@ class ManageSubscriptionsScreen : Screen {
Spacer(modifier = Modifier.weight(1f))
Icon(
modifier = Modifier.onClick(
rememberCallback {
navigatorCoordinator.getRootNavigator()?.push(
onClick = rememberCallback {
navigatorCoordinator.pushScreen(
MultiCommunityEditorScreen()
)
},
@ -206,7 +206,7 @@ class ManageSubscriptionsScreen : Screen {
model.reduce(ManageSubscriptionsMviModel.Intent.HapticIndication)
},
onDismissToStart = rememberCallback {
navigatorCoordinator.getRootNavigator()?.push(
navigatorCoordinator.pushScreen(
MultiCommunityEditorScreen(community),
)
},
@ -233,8 +233,8 @@ class ManageSubscriptionsScreen : Screen {
MultiCommunityItem(
modifier = Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.background).onClick(
rememberCallback {
navigatorCoordinator.getRootNavigator()?.push(
onClick = rememberCallback {
navigatorCoordinator.pushScreen(
MultiCommunityScreen(community),
)
},
@ -294,8 +294,8 @@ class ManageSubscriptionsScreen : Screen {
modifier = Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.onClick(
rememberCallback {
navigatorCoordinator.getRootNavigator()?.push(
onClick = rememberCallback {
navigatorCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},

View File

@ -159,8 +159,8 @@ class MultiCommunityScreen(
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -184,9 +184,9 @@ class MultiCommunityScreen(
if (sortType != null) {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
val sheet = SortBottomSheet(expandTop = true)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
),
imageVector = sortType.toIcon(),
@ -316,17 +316,29 @@ class MultiCommunityScreen(
blurNsfw = uiState.blurNsfw,
onClick = {
model.reduce(MultiCommunityMviModel.Intent.MarkAsRead(post.id))
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
PostDetailScreen(post),
)
},
onDoubleClick = if (uiState.swipeActionsEnabled) {
null
} else {
rememberCallback(model) {
model.reduce(
MultiCommunityMviModel.Intent.UpVotePost(
id = post.id,
feedback = true,
),
)
}
},
onOpenCommunity = { community ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
CommunityDetailScreen(community),
)
},
onOpenCreator = { user ->
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
UserDetailScreen(user),
)
},
@ -358,11 +370,11 @@ class MultiCommunityScreen(
val screen = CreateCommentScreen(
originalPost = post,
)
navigationCoordinator.getBottomNavigator()?.show(screen)
navigationCoordinator.showBottomSheet(screen)
},
onImageClick = { url ->
model.reduce(MultiCommunityMviModel.Intent.MarkAsRead(post.id))
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
ZoomableImageScreen(url),
)
},
@ -391,7 +403,7 @@ class MultiCommunityScreen(
onOptionSelected = { optionId ->
when (optionId) {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()?.show(
navigationCoordinator.showBottomSheet(
CreateReportScreen(
postId = post.id
)

View File

@ -88,7 +88,7 @@ class MultiCommunityEditorScreen(
model.effects.onEach {
when (it) {
MultiCommunityEditorMviModel.Effect.Close -> {
navigationCoordinator.getRootNavigator()?.pop()
navigationCoordinator.popScreen()
}
}
}.launchIn(this)
@ -114,8 +114,8 @@ class MultiCommunityEditorScreen(
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
navigationCoordinator.getRootNavigator()?.pop()
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
@ -219,7 +219,7 @@ class MultiCommunityEditorScreen(
it
}
}.onClick(
rememberCallback(model) {
onClick = rememberCallback(model) {
model.reduce(
MultiCommunityEditorMviModel.Intent.SelectImage(
idx,
@ -260,7 +260,7 @@ class MultiCommunityEditorScreen(
it
}
}.onClick(
rememberCallback {
onClick = rememberCallback {
model.reduce(
MultiCommunityEditorMviModel.Intent.SelectImage(
null,
@ -307,7 +307,7 @@ class MultiCommunityEditorScreen(
trailingIcon = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
if (uiState.searchText.isNotEmpty()) {
model.reduce(
MultiCommunityEditorMviModel.Intent.SetSearch("")

View File

@ -75,7 +75,7 @@ class AboutDialog : Screen {
viewModel.effects.onEach { effect ->
when (effect) {
is AboutDialogMviModel.Effect.OpenCommunity -> {
navigationCoordinator.getRootNavigator()?.push(
navigationCoordinator.pushScreen(
CommunityDetailScreen(
community = effect.community,
otherInstance = effect.instance,
@ -122,11 +122,10 @@ class AboutDialog : Screen {
vector = Icons.Default.OpenInBrowser,
textDecoration = TextDecoration.Underline,
onClick = {
handleUrl(
navigationCoordinator.handleUrl(
url = CHANGELOG_URL,
openExternal = settings.openUrlsInExternalBrowser,
uriHandler = uriHandler,
navigator = navigationCoordinator.getRootNavigator(),
)
}
)
@ -134,11 +133,10 @@ class AboutDialog : Screen {
item {
Button(
onClick = {
handleUrl(
navigationCoordinator.handleUrl(
url = REPORT_URL,
openExternal = settings.openUrlsInExternalBrowser,
uriHandler = uriHandler,
navigator = navigationCoordinator.getRootNavigator(),
)
},
) {
@ -166,11 +164,10 @@ class AboutDialog : Screen {
text = stringResource(MR.strings.settings_about_view_github),
textDecoration = TextDecoration.Underline,
onClick = {
handleUrl(
navigationCoordinator.handleUrl(
url = WEBSITE_URL,
openExternal = settings.openUrlsInExternalBrowser,
uriHandler = uriHandler,
navigator = navigationCoordinator.getRootNavigator(),
)
},
)
@ -212,7 +209,7 @@ class AboutDialog : Screen {
horizontal = Spacing.xs,
vertical = Spacing.s,
).onClick(
rememberCallback {
onClick = rememberCallback {
onClick?.invoke()
},
),

View File

@ -32,6 +32,7 @@ interface SettingsMviModel :
data class ChangeBlurNsfw(val value: Boolean) : Intent
data class ChangeOpenUrlsInExternalBrowser(val value: Boolean) : Intent
data class ChangeEnableSwipeActions(val value: Boolean) : Intent
data class ChangeEnableDoubleTapAction(val value: Boolean) : Intent
data class ChangeCustomSeedColor(val value: Color?) : Intent
data class ChangeUpvoteColor(val value: Color?) : Intent
data class ChangeDownvoteColor(val value: Color?) : Intent
@ -66,6 +67,7 @@ interface SettingsMviModel :
val blurNsfw: Boolean = true,
val openUrlsInExternalBrowser: Boolean = false,
val enableSwipeActions: Boolean = true,
val enableDoubleTapAction: Boolean = true,
val crashReportEnabled: Boolean = false,
val separateUpAndDownVotes: Boolean = false,
val autoLoadImages: Boolean = false,

View File

@ -65,10 +65,10 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomS
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ThemeBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.getPrettyDuration
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.getPrettyDuration
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLanguageName
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType
@ -278,7 +278,7 @@ class SettingsScreen : Screen {
navigationIcon = {
Image(
modifier = Modifier.onClick(
rememberCallback {
onClick = rememberCallback {
scope.launch {
drawerCoordinator.toggleDrawer()
}
@ -318,7 +318,7 @@ class SettingsScreen : Screen {
value = uiState.lang.toLanguageName(),
onTap = rememberCallback {
val sheet = LanguageBottomSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
@ -328,13 +328,14 @@ class SettingsScreen : Screen {
value = uiState.uiTheme.toReadableName(),
onTap = rememberCallback {
val sheet = ThemeBottomSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
// dynamic colors
if (uiState.supportsDynamicColors) {
SettingsSwitchRow(title = stringResource(MR.strings.settings_dynamic_colors),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_dynamic_colors),
value = uiState.dynamicColors,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -342,7 +343,8 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
}
val colorSchemeProvider = remember { getColorSchemeProvider() }
@ -355,7 +357,7 @@ class SettingsScreen : Screen {
).primary,
onTap = rememberCallback {
val sheet = ColorBottomSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
// upvote and downvote colors
@ -380,7 +382,7 @@ class SettingsScreen : Screen {
value = uiState.uiFontFamily.toReadableName(),
onTap = rememberCallback {
val sheet = FontFamilyBottomSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
// font scale
@ -396,7 +398,7 @@ class SettingsScreen : Screen {
),
contract = NotificationCenterContractKeys.ChangeUiFontSize
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
SettingsRow(
@ -406,7 +408,7 @@ class SettingsScreen : Screen {
val sheet = FontScaleBottomSheet(
contract = NotificationCenterContractKeys.ChangeContentFontSize,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
@ -416,12 +418,13 @@ class SettingsScreen : Screen {
value = uiState.postLayout.toReadableName(),
onTap = rememberCallback {
val sheet = PostLayoutBottomSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
// separate upvotes and downvotes
SettingsSwitchRow(title = stringResource(MR.strings.settings_separate_up_and_downvotes),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_separate_up_and_downvotes),
value = uiState.separateUpAndDownVotes,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -429,10 +432,12 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
// full height images
SettingsSwitchRow(title = stringResource(MR.strings.settings_full_height_images),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_full_height_images),
value = uiState.fullHeightImages,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -440,10 +445,12 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
// navigation bar titles
SettingsSwitchRow(title = stringResource(MR.strings.settings_navigation_bar_titles_visible),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_navigation_bar_titles_visible),
value = uiState.navBarTitlesVisible,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -451,7 +458,8 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
SettingsHeader(
icon = Icons.Default.Tune,
@ -466,7 +474,7 @@ class SettingsScreen : Screen {
val sheet = ListingTypeBottomSheet(
isLogged = uiState.isLogged,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
@ -479,7 +487,7 @@ class SettingsScreen : Screen {
expandTop = true,
contract = NotificationCenterContractKeys.ChangeSortType,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
@ -498,7 +506,7 @@ class SettingsScreen : Screen {
SortType.Controversial,
),
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
@ -517,7 +525,7 @@ class SettingsScreen : Screen {
),
onTap = rememberCallback {
val sheet = DurationBottomSheet()
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
@ -535,12 +543,13 @@ class SettingsScreen : Screen {
max = screenWidth,
initial = uiState.zombieModeScrollAmount,
)
navigationCoordinator.getBottomNavigator()?.show(sheet)
navigationCoordinator.showBottomSheet(sheet)
},
)
// swipe actions
SettingsSwitchRow(title = stringResource(MR.strings.settings_enable_swipe_actions),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_enable_swipe_actions),
value = uiState.enableSwipeActions,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -548,10 +557,25 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
// double tap
SettingsSwitchRow(
title = stringResource(MR.strings.settings_enable_double_tap),
value = uiState.enableDoubleTapAction,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
SettingsMviModel.Intent.ChangeEnableDoubleTapAction(
value
)
)
},
)
// bottom navigation hiding
SettingsSwitchRow(title = stringResource(MR.strings.settings_hide_navigation_bar),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_hide_navigation_bar),
value = uiState.hideNavigationBarWhileScrolling,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -559,10 +583,12 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
// URL open
SettingsSwitchRow(title = stringResource(MR.strings.settings_open_url_external),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_open_url_external),
value = uiState.openUrlsInExternalBrowser,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -570,10 +596,12 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
// auto-expand comments
SettingsSwitchRow(title = stringResource(MR.strings.settings_auto_expand_comments),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_auto_expand_comments),
value = uiState.autoExpandComments,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -581,10 +609,12 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
// image loading
SettingsSwitchRow(title = stringResource(MR.strings.settings_auto_load_images),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_auto_load_images),
value = uiState.autoLoadImages,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(
@ -592,7 +622,8 @@ class SettingsScreen : Screen {
value
)
)
})
},
)
SettingsHeader(
icon = Icons.Default.Shield,
@ -600,16 +631,19 @@ class SettingsScreen : Screen {
)
// NSFW options
SettingsSwitchRow(title = stringResource(MR.strings.settings_include_nsfw),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_include_nsfw),
value = uiState.includeNsfw,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(SettingsMviModel.Intent.ChangeIncludeNsfw(value))
})
SettingsSwitchRow(title = stringResource(MR.strings.settings_blur_nsfw),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_blur_nsfw),
value = uiState.blurNsfw,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(SettingsMviModel.Intent.ChangeBlurNsfw(value))
})
},
)
SettingsHeader(
icon = Icons.Default.BugReport,
@ -617,11 +651,13 @@ class SettingsScreen : Screen {
)
// enable crash report
SettingsSwitchRow(title = stringResource(MR.strings.settings_enable_crash_report),
SettingsSwitchRow(
title = stringResource(MR.strings.settings_enable_crash_report),
value = uiState.crashReportEnabled,
onValueChanged = rememberCallbackArgs(model) { value ->
model.reduce(SettingsMviModel.Intent.ChangeCrashReportEnabled(value))
})
},
)
// about
SettingsRow(

View File

@ -109,6 +109,7 @@ class SettingsViewModel(
supportsDynamicColors = colorSchemeProvider.supportsDynamicColors,
openUrlsInExternalBrowser = settings.openUrlsInExternalBrowser,
enableSwipeActions = settings.enableSwipeActions,
enableDoubleTapAction = settings.enableDoubleTapAction,
crashReportEnabled = crashReportConfiguration.isEnabled(),
separateUpAndDownVotes = settings.separateUpAndDownVotes,
autoLoadImages = settings.autoLoadImages,
@ -179,6 +180,10 @@ class SettingsViewModel(
changeEnableSwipeActions(intent.value)
}
is SettingsMviModel.Intent.ChangeEnableDoubleTapAction -> {
changeEnableDoubleTapAction(intent.value)
}
is SettingsMviModel.Intent.ChangeCustomSeedColor -> changeCustomSeedColor(
intent.value
)
@ -389,6 +394,16 @@ class SettingsViewModel(
}
}
private fun changeEnableDoubleTapAction(value: Boolean) {
mvi.updateState { it.copy(enableDoubleTapAction = value) }
mvi.scope?.launch {
val settings = settingsRepository.currentSettings.value.copy(
enableDoubleTapAction = value
)
saveSettings(settings)
}
}
private fun changePostLayout(value: PostLayout) {
themeRepository.changePostLayout(value)
mvi.scope?.launch {

View File

@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.settings.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
@ -24,25 +25,37 @@ internal fun SettingsColorRow(
title: String,
value: Color,
modifier: Modifier = Modifier,
subtitle: String? = null,
onTap: (() -> Unit)? = null,
) {
Row(
modifier = modifier
.padding(vertical = Spacing.s, horizontal = Spacing.m)
.onClick(
rememberCallback {
onClick = rememberCallback {
onTap?.invoke()
},
),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Column {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier

View File

@ -1,5 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.settings.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
@ -18,25 +19,37 @@ internal fun SettingsRow(
title: String,
value: String,
modifier: Modifier = Modifier,
subtitle: String? = null,
onTap: (() -> Unit)? = null,
) {
Row(
modifier = modifier
.padding(vertical = Spacing.s, horizontal = Spacing.m)
.onClick(
rememberCallback {
onClick = rememberCallback {
onTap?.invoke()
},
),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Column {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Spacer(modifier = Modifier.weight(1f))
Text(
text = value,

View File

@ -1,5 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.feature.settings.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
@ -16,19 +17,31 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
internal fun SettingsSwitchRow(
title: String,
value: Boolean,
subtitle: String? = null,
onValueChanged: (Boolean) -> Unit,
) {
Row(
modifier = Modifier.padding(horizontal = Spacing.m),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Column {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
Spacer(modifier = Modifier.weight(1f))
Switch(
checked = value,

View File

@ -80,6 +80,8 @@
<string name="home_sort_type_top_week_short">week</string>
<string name="home_sort_type_top_year">Top year</string>
<string name="home_sort_type_top_year_short">year</string>
<string name="inbox_action_mark_read">Mark as read</string>
<string name="inbox_action_mark_unread">Mark as unread</string>
<string name="inbox_chat_message">Message</string>
<string name="inbox_item_mention">mentioned you in</string>
<string name="inbox_item_reply_comment">replied to your comment in</string>
@ -200,6 +202,7 @@
<string name="settings_downvote_color">Downvote color</string>
<string name="settings_dynamic_colors">Use dynamic colors</string>
<string name="settings_enable_crash_report">Enable crash reporting</string>
<string name="settings_enable_double_tap">Enable double tap action</string>
<string name="settings_enable_swipe_actions">Enable swipe actions</string>
<string name="settings_full_height_images">Full height images</string>
<string name="settings_include_nsfw">Include NSFW contents</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Alle</string>
<string name="inbox_listing_type_title">Posteingangstyp</string>
<string name="inbox_listing_type_unread">Ungelesen</string>
<string name="inbox_action_mark_read">Als gelesen markieren</string>
<string name="inbox_action_mark_unread">Als ungelesen markieren</string>
<string name="inbox_not_logged_message">Sie sind derzeit nicht angemeldet.\nBitte fügen Sie über
den Profilbildschirm ein Konto hinzu, um Ihren Posteingang anzuzeigen.
</string>
@ -193,6 +195,7 @@
<string name="settings_downvote_color">Farbe für Downvotes</string>
<string name="settings_dynamic_colors">Verwenden Sie dynamische Farben</string>
<string name="settings_enable_crash_report">Absturzberichte aktivieren</string>
<string name="settings_enable_double_tap">Doppeltipp-Aktion aktivieren</string>
<string name="settings_enable_swipe_actions">Wischaktionen aktivieren</string>
<string name="settings_full_height_images">Bilder in voller Höhe</string>
<string name="settings_include_nsfw">NSFW-Inhalte einbeziehen</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Όλα</string>
<string name="inbox_listing_type_title">Τύπος εισερχομένων</string>
<string name="inbox_listing_type_unread">Μη αναγνωσμένα</string>
<string name="inbox_action_mark_read">Σημειώσε ως αναγνωσμένο</string>
<string name="inbox_action_mark_unread">Σημειώστε ως αδιάβαστο</string>
<string name="inbox_not_logged_message">Προς το παρόν, δεν έχετε συνδεθεί.\nΠαρακαλούμε
προσθέστε έναν λογαριασμό από την οθόνη προφίλ για να δείτε τα εισερχόμενά σας.
</string>
@ -194,6 +196,7 @@
<string name="settings_downvote_color">Χρώμα ψήφου κατώτερου</string>
<string name="settings_dynamic_colors">Χρήση δυναμικών χρωμάτων</string>
<string name="settings_enable_crash_report">Ενεργοποίηση αναφοράς καταρρεύσεων</string>
<string name="settings_enable_double_tap">Ενεργοποίηση της δράσης διπλού πατήματος</string>
<string name="settings_enable_swipe_actions">Ενεργοποίηση κινήσεων σαρώσεων</string>
<string name="settings_full_height_images">Εικόνες πλήρους ύψους</string>
<string name="settings_include_nsfw">Συμπερίληψη περιεχομένων NSFW</string>

View File

@ -84,6 +84,8 @@
<string name="inbox_item_mention">te ha mencionado en</string>
<string name="inbox_item_reply_comment">ha contestado a tu comentario en</string>
<string name="inbox_item_reply_post">ha contestado a tu publicación en</string>
<string name="inbox_action_mark_read">mMrcar como leído</string>
<string name="inbox_action_mark_unread">Marcar como no leído</string>
<string name="inbox_listing_type_all">Todos</string>
<string name="inbox_listing_type_title">Tipo de mensajes</string>
<string name="inbox_listing_type_unread">No leídos</string>
@ -192,6 +194,7 @@
<string name="settings_downvote_color">Color votos negativos</string>
<string name="settings_dynamic_colors">Utilizar colores dinámicos</string>
<string name="settings_enable_crash_report">Activar notificación de accidentes</string>
<string name="settings_enable_double_tap">Activar la acción de doble toque</string>
<string name="settings_enable_swipe_actions">Activar acciones de deslizamiento</string>
<string name="settings_full_height_images">Altura completa imágenes</string>
<string name="settings_include_nsfw">Incluir contenidos NSFW</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Tous</string>
<string name="inbox_listing_type_title">Type de boîte de réception</string>
<string name="inbox_listing_type_unread">Non lus</string>
<string name="inbox_action_mark_read">Marquer comme lu</string>
<string name="inbox_action_mark_unread">Marquer comme non lu</string>
<string name="inbox_not_logged_message">Vous n\'êtes pas connecté.\nVeuillez ajouter un compte à
partir de l\'écran de profil pour voir votre boîte de réception.
</string>
@ -192,6 +194,7 @@
<string name="settings_downvote_color">Couleur votes négatifs</string>
<string name="settings_dynamic_colors">Utiliser couleur dynamique</string>
<string name="settings_enable_crash_report">Activer les rapports d\'accidents</string>
<string name="settings_enable_double_tap">Activer l\'action double tap</string>
<string name="settings_enable_swipe_actions">Activer les actions de glissement</string>
<string name="settings_full_height_images">Images en pleine hauteur</string>
<string name="settings_include_nsfw">Inclure contenus NSFW</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Tutti</string>
<string name="inbox_listing_type_title">Tipo inbox</string>
<string name="inbox_listing_type_unread">Non letti</string>
<string name="inbox_action_mark_read">Segna come letto</string>
<string name="inbox_action_mark_unread">Segna come non letto</string>
<string name="inbox_not_logged_message">Login non effettuato.\nAggiungi un account dalla
schermata Profilo per accedere alla inbox.
</string>
@ -192,6 +194,7 @@
<string name="settings_downvote_color">Colore voti negativi</string>
<string name="settings_dynamic_colors">Usa colori dinamici</string>
<string name="settings_enable_crash_report">Abilitare segnalazioni di crash</string>
<string name="settings_enable_double_tap">Abilita azione doppio tap</string>
<string name="settings_enable_swipe_actions">Consenti azioni allo swipe</string>
<string name="settings_full_height_images">Altezza completa immagini</string>
<string name="settings_include_nsfw">Includi contenuti NSFW</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Alle</string>
<string name="inbox_listing_type_title">Type inbox</string>
<string name="inbox_listing_type_unread">Ongelezen</string>
<string name="inbox_action_mark_read">Markeren als gelezen</string>
<string name="inbox_action_mark_unread">Markeren als ongelezen</string>
<string name="inbox_not_logged_message">U bent momenteel niet ingelogd.\nVoeg een account toe
vanuit het profielscherm om je inbox te zien.
</string>
@ -200,6 +202,7 @@
<string name="settings_downvote_color">Kleur voor downvote</string>
<string name="settings_dynamic_colors">Dynamische kleuren gebruiken</string>
<string name="settings_enable_crash_report">Crashrapportage inschakelen</string>
<string name="settings_enable_double_tap">Dubbele tik actie inschakelen</string>
<string name="settings_enable_swipe_actions">Veegacties inschakelen</string>
<string name="settings_full_height_images">Afbeeldingen op volledige hoogte</string>
<string name="settings_include_nsfw">NSFW-inhoud opnemen</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Wszystkie</string>
<string name="inbox_listing_type_title">Typ skrzynki</string>
<string name="inbox_listing_type_unread">Nieprzeczytany</string>
<string name="inbox_action_mark_read">Oznacz jako przeczytane</string>
<string name="inbox_action_mark_unread">Oznacz jako nieprzeczytane</string>
<string name="inbox_not_logged_message">Obecnie nie jesteś zalogowany.\nDodaj konto na ekranie
profilu, aby zobaczyć swoją skrzynkę odbiorczą."
</string>
@ -191,6 +193,7 @@
<string name="settings_downvote_color">Kolor głosów negatywnych</string>
<string name="settings_dynamic_colors">Używaj dynamicznych kolorów</string>
<string name="settings_enable_crash_report">Włącz raportowanie awarii</string>
<string name="settings_enable_double_tap">Włącz akcje podwójnego dotknięcia</string>
<string name="settings_enable_swipe_actions">Włącz akcje przesuwania</string>
<string name="settings_full_height_images">Obrazy o pełnej wysokości</string>
<string name="settings_hide_navigation_bar">Ukryj pasek nawigacji podczas przewijania</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Todas</string>
<string name="inbox_listing_type_title">Tipo de caixa</string>
<string name="inbox_listing_type_unread">Não lidas</string>
<string name="inbox_action_mark_read">Marcar como lido</string>
<string name="inbox_action_mark_unread">Marcar como não lido</string>
<string name="inbox_not_logged_message">Atualmente, não tem sessão iniciada.\nAdicione uma conta
a partir do ecrã de perfil para ver a sua caixa de entrada.
</string>
@ -190,6 +192,7 @@
<string name="settings_downvote_color">Cor votos negativos</string>
<string name="settings_dynamic_colors">Usar cores dinâmicas</string>
<string name="settings_enable_crash_report">Ativar relatórios de falhas</string>
<string name="settings_enable_double_tap">Ativar a ação de duplo toque</string>
<string name="settings_enable_swipe_actions">Ativar ações de deslizar</string>
<string name="settings_full_height_images">Imagens de altura natural</string>
<string name="settings_include_nsfw">Incluir conteúdo NSFW</string>

View File

@ -87,6 +87,8 @@
<string name="inbox_listing_type_all">Toate</string>
<string name="inbox_listing_type_title">Tip de inbox</string>
<string name="inbox_listing_type_unread">Necitit</string>
<string name="inbox_action_mark_read">Marchează ca citit</string>
<string name="inbox_action_mark_unread">Marchează ca necitit</string>
<string name="inbox_not_logged_message">Momentan nu sunteți autentificat.\nVă rugăm să adăugați
un cont din ecranul de profil pentru a vedea mesajele.
</string>
@ -191,6 +193,7 @@
<string name="settings_downvote_color">Culoare voturilor negative</string>
<string name="settings_dynamic_colors">Folosește culori dinamice</string>
<string name="settings_enable_crash_report">Activează raporturi erorilor</string>
<string name="settings_enable_double_tap">Activează acțiunea de dublă atingere</string>
<string name="settings_enable_swipe_actions">Activează acțiunile de glisare</string>
<string name="settings_full_height_images">Imagini pe toată înălțimea</string>
<string name="settings_include_nsfw">Include conținuturile NSFW</string>

View File

@ -172,7 +172,7 @@ fun App() {
else -> null
}
if (newScreen != null) {
navigationCoordinator.getRootNavigator()?.push(newScreen)
navigationCoordinator.pushScreen(newScreen)
}
}.launchIn(this)
}
@ -182,7 +182,6 @@ fun App() {
val drawerGestureEnabled by drawerCoordinator.gesturesEnabled.collectAsState()
LaunchedEffect(drawerCoordinator) {
drawerCoordinator.toggleEvents.onEach { evt ->
val navigator = navigationCoordinator.getRootNavigator()
when (evt) {
DrawerEvent.Toggled -> {
drawerState.apply {
@ -197,19 +196,19 @@ fun App() {
}
is DrawerEvent.OpenCommunity -> {
navigator?.push(CommunityDetailScreen(evt.community))
navigationCoordinator.pushScreen(CommunityDetailScreen(evt.community))
}
is DrawerEvent.OpenMultiCommunity -> {
navigator?.push(MultiCommunityScreen(evt.community))
navigationCoordinator.pushScreen(MultiCommunityScreen(evt.community))
}
DrawerEvent.ManageSubscriptions -> {
navigator?.push(ManageSubscriptionsScreen())
navigationCoordinator.pushScreen(ManageSubscriptionsScreen())
}
DrawerEvent.OpenBookmarks -> {
navigator?.push(SavedItemsScreen())
navigationCoordinator.pushScreen(SavedItemsScreen())
}
}
}.launchIn(this)