feat: cross-post (#117)

* feat: cross-post

* refactor: id-based options

* fix: show actions only for logged users
This commit is contained in:
Diego Beraldin 2023-11-09 19:40:27 +01:00 committed by GitHub
parent a4971ac670
commit 5d032fd583
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1172 additions and 263 deletions

View File

@ -20,6 +20,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.Navigat
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity.SelectCommunityMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
@ -108,9 +109,9 @@ actual fun getCreateCommentViewModel(
return res
}
actual fun getCreatePostViewModel(communityId: Int?, editedPostId: Int?): CreatePostMviModel {
actual fun getCreatePostViewModel(editedPostId: Int?): CreatePostMviModel {
val res: CreatePostMviModel by inject(clazz = CreatePostMviModel::class.java,
parameters = { parametersOf(communityId, editedPostId) })
parameters = { parametersOf(editedPostId) })
return res
}
@ -151,6 +152,11 @@ actual fun getCreateReportViewModel(
return res
}
actual fun getSelectCommunityViewModel(): SelectCommunityMviModel {
val res: SelectCommunityMviModel by inject(SelectCommunityMviModel::class.java)
return res
}
@Composable
actual fun getCustomTextToolbar(

View File

@ -72,6 +72,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communityInfo.Comm
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommunityHeader
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ProgressHud
@ -321,16 +323,18 @@ class CommunityDetailScreen(
}
},
)
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ClearAll,
text = stringResource(MR.strings.action_clear_read),
onSelected = rememberCallback {
model.reduce(CommunityDetailMviModel.Intent.ClearRead)
scope.launch {
lazyListState.scrollToItem(0)
}
},
)
if (uiState.currentUserId != null) {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ClearAll,
text = stringResource(MR.strings.action_clear_read),
onSelected = rememberCallback {
model.reduce(CommunityDetailMviModel.Intent.ClearRead)
scope.launch {
lazyListState.scrollToItem(0)
}
},
)
}
if (!isOnOtherInstance) {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.Create,
@ -378,21 +382,36 @@ class CommunityDetailScreen(
community = uiState.community,
autoLoadImages = uiState.autoLoadImages,
options = listOf(
stringResource(MR.strings.community_detail_info),
stringResource(MR.strings.community_detail_instance_info),
stringResource(MR.strings.community_detail_block),
stringResource(MR.strings.community_detail_block_instance),
Option(
OptionId.Info,
stringResource(MR.strings.community_detail_info)
),
Option(
OptionId.InfoInstance,
stringResource(MR.strings.community_detail_instance_info)
),
Option(
OptionId.Block,
stringResource(MR.strings.community_detail_block)
),
Option(
OptionId.BlockInstance,
stringResource(MR.strings.community_detail_block_instance)
),
),
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()
?.push(ZoomableImageScreen(url))
},
onOptionSelected = rememberCallbackArgs { optionIdx ->
when (optionIdx) {
3 -> model.reduce(CommunityDetailMviModel.Intent.BlockInstance)
2 -> model.reduce(CommunityDetailMviModel.Intent.Block)
onOptionSelected = rememberCallbackArgs { optionId ->
when (optionId) {
OptionId.BlockInstance -> model.reduce(
CommunityDetailMviModel.Intent.BlockInstance
)
1 -> {
OptionId.Block -> model.reduce(CommunityDetailMviModel.Intent.Block)
OptionId.InfoInstance -> {
navigationCoordinator.getRootNavigator()?.push(
InstanceInfoScreen(
url = uiState.community.instanceUrl,
@ -400,11 +419,13 @@ class CommunityDetailScreen(
)
}
else -> {
OptionId.Info -> {
navigationCoordinator.getBottomNavigator()?.show(
CommunityInfoScreen(uiState.community),
)
}
else -> Unit
}
},
)
@ -544,54 +565,95 @@ class CommunityDetailScreen(
)
},
options = buildList {
add(stringResource(MR.strings.post_action_share))
add(stringResource(MR.strings.post_action_hide))
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
if (post.creator?.id == uiState.currentUserId && !isOnOtherInstance) {
add(stringResource(MR.strings.post_action_edit))
add(stringResource(MR.strings.comment_action_delete))
}
},
onOptionSelected = rememberCallbackArgs(model) { optionIdx ->
when (optionIdx) {
5 -> model.reduce(
CommunityDetailMviModel.Intent.DeletePost(
post.id
add(
Option(
OptionId.Share,
stringResource(MR.strings.post_action_share)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.Hide,
stringResource(MR.strings.post_action_hide)
)
)
}
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.CrossPost,
stringResource(MR.strings.post_action_cross_post)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
}
if (post.creator?.id == uiState.currentUserId && !isOnOtherInstance) {
add(
Option(
OptionId.Edit,
stringResource(MR.strings.post_action_edit)
)
)
add(
Option(
OptionId.Delete,
stringResource(MR.strings.comment_action_delete)
)
)
}
},
onOptionSelected = rememberCallbackArgs(model) { optionId ->
when (optionId) {
OptionId.Delete -> model.reduce(
CommunityDetailMviModel.Intent.DeletePost(post.id)
)
4 -> {
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(
editedPost = post,
)
CreatePostScreen(editedPost = post)
)
}
3 -> {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
postId = post.id
)
CreateReportScreen(postId = post.id)
)
}
2 -> {
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(crossPost = post)
)
}
OptionId.SeeRaw -> {
rawContent = post
}
1 -> model.reduce(
CommunityDetailMviModel.Intent.Hide(
post.id
)
OptionId.Hide -> model.reduce(
CommunityDetailMviModel.Intent.Hide(post.id)
)
else -> model.reduce(
OptionId.Share -> model.reduce(
CommunityDetailMviModel.Intent.SharePost(post.id)
)
else -> Unit
}
})
},

View File

@ -31,13 +31,13 @@ fun CollapsedCommentCard(
modifier: Modifier = Modifier,
separateUpAndDownVotes: Boolean = false,
autoLoadImages: Boolean = true,
options: List<String> = emptyList(),
options: List<Option> = emptyList(),
onOpenCreator: ((UserModel) -> Unit)? = null,
onUpVote: (() -> Unit)? = null,
onDownVote: (() -> Unit)? = null,
onSave: (() -> Unit)? = null,
onReply: (() -> Unit)? = null,
onOptionSelected: ((Int) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onToggleExpanded: (() -> Unit)? = null,
) {
val themeRepository = remember { getThemeRepository() }

View File

@ -40,7 +40,7 @@ fun CommentCard(
hideCommunity: Boolean = true,
hideIndent: Boolean = false,
autoLoadImages: Boolean = true,
options: List<String> = emptyList(),
options: List<Option> = emptyList(),
onClick: (() -> Unit)? = null,
onUpVote: (() -> Unit)? = null,
onDownVote: (() -> Unit)? = null,
@ -48,7 +48,7 @@ fun CommentCard(
onReply: (() -> Unit)? = null,
onOpenCommunity: ((CommunityModel) -> Unit)? = null,
onOpenCreator: ((UserModel) -> Unit)? = null,
onOptionSelected: ((Int) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onToggleExpanded: (() -> Unit)? = null,
) {
val themeRepository = remember { getThemeRepository() }

View File

@ -51,8 +51,8 @@ fun CommunityHeader(
community: CommunityModel,
modifier: Modifier = Modifier,
autoLoadImages: Boolean = true,
options: List<String> = emptyList(),
onOptionSelected: ((Int) -> Unit)? = null,
options: List<Option> = emptyList(),
onOptionSelected: ((OptionId) -> Unit)? = null,
onOpenImage: ((String) -> Unit)? = null,
) {
Box(
@ -114,7 +114,7 @@ fun CommunityHeader(
// y = (-50).dp,
),
) {
options.forEachIndexed { idx, option ->
options.forEach { option ->
Text(
modifier = Modifier.padding(
horizontal = Spacing.m,
@ -122,10 +122,10 @@ fun CommunityHeader(
).onClick(
rememberCallback {
optionsExpanded = false
onOptionSelected?.invoke(idx)
onOptionSelected?.invoke(option.id)
},
),
text = option,
text = option.text,
)
}
}

View File

@ -13,7 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
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.domain.lemmy.data.CommunityModel
@ -28,7 +28,7 @@ fun CommunityItem(
val communityName = community.name
val communityIcon = community.icon.orEmpty()
val communityHost = community.host
val iconSize = if (small) 24.dp else 30.dp
val iconSize = if (small) IconSize.m else IconSize.l
Row(
modifier = modifier.padding(
vertical = Spacing.xs,
@ -62,6 +62,7 @@ fun CommunityItem(
append(title)
},
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onBackground,
)
Text(
text = buildString {

View File

@ -13,7 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
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.persistence.data.MultiCommunityModel
@ -26,7 +26,7 @@ fun MultiCommunityItem(
) {
val title = community.name
val communityIcon = community.icon.orEmpty()
val iconSize = if (small) 24.dp else 30.dp
val iconSize = if (small) IconSize.m else IconSize.l
Row(
modifier = modifier.padding(
vertical = Spacing.xs,

View File

@ -0,0 +1,20 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.components
data class Option(
val id: OptionId,
val text: String,
)
sealed class OptionId(val value: Int) {
data object Share : OptionId(0)
data object Hide : OptionId(1)
data object SeeRaw : OptionId(2)
data object CrossPost : OptionId(3)
data object Report : OptionId(4)
data object Edit : OptionId(5)
data object Delete : OptionId(6)
data object Info : OptionId(7)
data object InfoInstance : OptionId(8)
data object Block : OptionId(9)
data object BlockInstance : OptionId(10)
}

View File

@ -53,7 +53,7 @@ fun PostCard(
fullHeightImage: Boolean = true,
limitBodyHeight: Boolean = false,
blurNsfw: Boolean = true,
options: List<String> = emptyList(),
options: List<Option> = emptyList(),
onOpenCommunity: ((CommunityModel) -> Unit)? = null,
onOpenCreator: ((UserModel) -> Unit)? = null,
onUpVote: (() -> Unit)? = null,
@ -61,7 +61,7 @@ fun PostCard(
onSave: (() -> Unit)? = null,
onReply: (() -> Unit)? = null,
onImageClick: ((String) -> Unit)? = null,
onOptionSelected: ((Int) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onClick: (() -> Unit)? = null,
) {
Box(
@ -132,7 +132,7 @@ private fun CompactPost(
hideAuthor: Boolean,
blurNsfw: Boolean,
separateUpAndDownVotes: Boolean,
options: List<String> = emptyList(),
options: List<Option> = emptyList(),
onOpenCommunity: ((CommunityModel) -> Unit)? = null,
onOpenCreator: ((UserModel) -> Unit)? = null,
onUpVote: (() -> Unit)? = null,
@ -140,7 +140,7 @@ private fun CompactPost(
onSave: (() -> Unit)? = null,
onReply: (() -> Unit)? = null,
onImageClick: ((String) -> Unit)? = null,
onOptionSelected: ((Int) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onClick: (() -> Unit)? = null,
) {
Column(
@ -215,7 +215,7 @@ private fun ExtendedPost(
fullHeightImage: Boolean = true,
roundedCornerImage: Boolean = true,
backgroundColor: Color = MaterialTheme.colorScheme.background,
options: List<String> = emptyList(),
options: List<Option> = emptyList(),
onOpenCommunity: ((CommunityModel) -> Unit)? = null,
onOpenCreator: ((UserModel) -> Unit)? = null,
onUpVote: (() -> Unit)? = null,
@ -223,7 +223,7 @@ private fun ExtendedPost(
onSave: (() -> Unit)? = null,
onReply: (() -> Unit)? = null,
onImageClick: ((String) -> Unit)? = null,
onOptionSelected: ((Int) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
onClick: (() -> Unit)? = null,
) {
Column(

View File

@ -54,12 +54,12 @@ fun PostCardFooter(
saved: Boolean = false,
upVoted: Boolean = false,
downVoted: Boolean = false,
options: List<String> = emptyList(),
options: List<Option> = emptyList(),
onUpVote: (() -> Unit)? = null,
onDownVote: (() -> Unit)? = null,
onSave: (() -> Unit)? = null,
onReply: (() -> Unit)? = null,
onOptionSelected: ((Int) -> Unit)? = null,
onOptionSelected: ((OptionId) -> Unit)? = null,
) {
var optionsExpanded by remember { mutableStateOf(false) }
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
@ -232,7 +232,7 @@ fun PostCardFooter(
y = optionsOffset.y.toLocalDp(),
),
) {
options.forEachIndexed { idx, text ->
options.forEach { option ->
Text(
modifier = Modifier.padding(
horizontal = Spacing.m,
@ -240,10 +240,10 @@ fun PostCardFooter(
).onClick(
rememberCallback {
optionsExpanded = false
onOptionSelected?.invoke(idx)
onOptionSelected?.invoke(option.id)
},
),
text = text,
text = option.text,
)
}
}

View File

@ -55,8 +55,8 @@ fun UserHeader(
user: UserModel,
modifier: Modifier = Modifier,
autoLoadImages: Boolean = true,
options: List<String> = emptyList(),
onOptionSelected: ((Int) -> Unit)? = null,
options: List<Option> = emptyList(),
onOptionSelected: ((OptionId) -> Unit)? = null,
onOpenImage: ((String) -> Unit)? = null,
) {
Box(
@ -117,7 +117,7 @@ fun UserHeader(
y = optionsOffset.y.toLocalDp(),
),
) {
options.forEachIndexed { idx, option ->
options.forEach { option ->
Text(
modifier = Modifier.padding(
horizontal = Spacing.m,
@ -125,10 +125,10 @@ fun UserHeader(
).onClick(
rememberCallback {
optionsExpanded = false
onOptionSelected?.invoke(idx)
onOptionSelected?.invoke(option.id)
},
),
text = option,
text = option.text,
)
}
}

View File

@ -49,6 +49,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.BottomSheetHandle
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCard
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardBody
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ProgressHud
@ -170,7 +172,12 @@ class CreateCommentScreen(
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
autoLoadImages = uiState.autoLoadImages,
options = buildList {
add(stringResource(MR.strings.post_action_see_raw))
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
},
onOptionSelected = {
rawContent = originalComment
@ -192,7 +199,12 @@ class CreateCommentScreen(
separateUpAndDownVotes = uiState.separateUpAndDownVotes,
autoLoadImages = uiState.autoLoadImages,
options = buildList {
add(stringResource(MR.strings.post_action_see_raw))
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
},
onOptionSelected = {
rawContent = originalPost

View File

@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import dev.icerock.moko.resources.desc.StringDesc
@Stable
@ -12,6 +13,7 @@ interface CreatePostMviModel :
ScreenModel {
sealed interface Intent {
data class SetCommunity(val value: CommunityModel) : Intent
data class SetTitle(val value: String) : Intent
data class SetUrl(val value: String) : Intent
data class ChangeNsfw(val value: Boolean) : Intent
@ -54,6 +56,9 @@ interface CreatePostMviModel :
}
data class UiState(
val communityInfo: String = "",
val communityId: Int? = null,
val communityError: StringDesc? = null,
val title: String = "",
val titleError: StringDesc? = null,
val bodyError: StringDesc? = null,

View File

@ -6,13 +6,13 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Groups
import androidx.compose.material.icons.filled.Image
import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.ExperimentalMaterial3Api
@ -28,6 +28,7 @@ import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@ -38,6 +39,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@ -60,12 +62,16 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Section
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.TextFormattingBar
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getCreatePostViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity.SelectCommunityDialog
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getGalleryHelper
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.shareUrl
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.localized
import dev.icerock.moko.resources.compose.stringResource
@ -75,15 +81,13 @@ import kotlinx.coroutines.flow.onEach
class CreatePostScreen(
private val communityId: Int? = null,
private val editedPost: PostModel? = null,
private val crossPost: PostModel? = null,
) : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
val model = rememberScreenModel {
getCreatePostViewModel(
communityId = communityId,
editedPostId = editedPost?.id,
)
getCreatePostViewModel(editedPostId = editedPost?.id)
}
model.bindToLifecycle(key)
val uiState by model.uiState.collectAsState()
@ -91,17 +95,63 @@ class CreatePostScreen(
val genericError = stringResource(MR.strings.message_generic_error)
val notificationCenter = remember { getNotificationCenter() }
val galleryHelper = remember { getGalleryHelper() }
val crossPostText = stringResource(MR.strings.create_post_cross_post_text)
var bodyTextFieldValue by remember {
mutableStateOf(TextFieldValue(text = editedPost?.text.orEmpty()))
val text = when {
crossPost != null -> buildString {
append(crossPostText)
append(" ")
append(crossPost.shareUrl)
}
editedPost != null -> {
editedPost.text
}
else -> ""
}
mutableStateOf(TextFieldValue(text = text))
}
val bodyFocusRequester = remember { FocusRequester() }
val urlFocusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val navigationCoordinator = remember { getNavigationCoordinator() }
var openImagePicker by remember { mutableStateOf(false) }
var openImagePickerInBody by remember { mutableStateOf(false) }
if (openImagePicker) {
galleryHelper.getImageFromGallery { bytes ->
openImagePicker = false
model.reduce(CreatePostMviModel.Intent.ImageSelected(bytes))
}
}
if (openImagePickerInBody) {
galleryHelper.getImageFromGallery { bytes ->
openImagePickerInBody = false
model.reduce(CreatePostMviModel.Intent.InsertImageInBody(bytes))
}
}
var openSelectCommunity by remember { mutableStateOf(false) }
val keyboardScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset {
focusManager.clearFocus()
return Offset.Zero
}
}
}
LaunchedEffect(model) {
model.reduce(CreatePostMviModel.Intent.SetTitle(editedPost?.title.orEmpty()))
model.reduce(CreatePostMviModel.Intent.SetUrl(editedPost?.url.orEmpty()))
val referencePost = editedPost ?: crossPost
model.reduce(CreatePostMviModel.Intent.SetTitle(referencePost?.title.orEmpty()))
model.reduce(CreatePostMviModel.Intent.SetUrl(referencePost?.url.orEmpty()))
if (communityId != null) {
model.reduce(
CreatePostMviModel.Intent.SetCommunity(CommunityModel(id = communityId))
)
}
model.effects.onEach { effect ->
when (effect) {
@ -123,15 +173,26 @@ class CreatePostScreen(
}
}.launchIn(this)
}
val keyboardScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(
available: Offset,
source: NestedScrollSource,
): Offset {
focusManager.clearFocus()
return Offset.Zero
}
DisposableEffect(key) {
notificationCenter.addObserver(
{
(it as CommunityModel)?.also { community ->
model.reduce(CreatePostMviModel.Intent.SetCommunity(community))
focusManager.clearFocus()
}
}, key, NotificationCenterContractKeys.SelectCommunity
)
notificationCenter.addObserver(
{
if (openSelectCommunity) {
openSelectCommunity = false
}
}, key, NotificationCenterContractKeys.CloseDialog
)
onDispose {
notificationCenter.removeObserver(key)
}
}
@ -147,7 +208,10 @@ class CreatePostScreen(
) {
BottomSheetHandle()
Text(
text = stringResource(MR.strings.create_post_title),
text = when {
else ->
stringResource(MR.strings.create_post_title)
},
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground,
)
@ -164,8 +228,52 @@ class CreatePostScreen(
.padding(padding)
.verticalScroll(rememberScrollState()),
) {
// community
if (crossPost != null) {
TextField(
modifier = Modifier
.fillMaxWidth()
.onFocusChanged(
rememberCallbackArgs {
if (it.hasFocus) {
openSelectCommunity = true
}
},
),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
),
label = {
Text(text = stringResource(MR.strings.create_post_community))
},
trailingIcon = {
Icon(
imageVector = Icons.Default.Groups,
contentDescription = null,
)
},
textStyle = MaterialTheme.typography.bodyMedium,
value = uiState.communityInfo,
readOnly = true,
singleLine = true,
onValueChange = {},
isError = uiState.communityError != null,
supportingText = {
if (uiState.communityError != null) {
Text(
text = uiState.communityError?.localized().orEmpty(),
color = MaterialTheme.colorScheme.error,
)
}
},
)
}
// title
TextField(
modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp),
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
@ -199,21 +307,7 @@ class CreatePostScreen(
},
)
var openImagePicker by remember { mutableStateOf(false) }
var openImagePickerInBody by remember { mutableStateOf(false) }
if (openImagePicker) {
galleryHelper.getImageFromGallery { bytes ->
openImagePicker = false
model.reduce(CreatePostMviModel.Intent.ImageSelected(bytes))
}
}
if (openImagePickerInBody) {
galleryHelper.getImageFromGallery { bytes ->
openImagePickerInBody = false
model.reduce(CreatePostMviModel.Intent.InsertImageInBody(bytes))
}
}
// image
TextField(
modifier = Modifier.fillMaxWidth().focusRequester(urlFocusRequester),
colors = TextFieldDefaults.colors(
@ -262,6 +356,7 @@ class CreatePostScreen(
},
)
// NSFW
Row(
modifier = Modifier.fillMaxWidth().padding(
vertical = Spacing.s, horizontal = Spacing.m
@ -377,5 +472,9 @@ class CreatePostScreen(
if (uiState.loading) {
ProgressHud()
}
if (openSelectCommunity) {
SelectCommunityDialog().Content()
}
}
}

View File

@ -17,7 +17,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class CreatePostViewModel(
private val communityId: Int?,
private val editedPostId: Int?,
private val mvi: DefaultMviModel<CreatePostMviModel.Intent, CreatePostMviModel.UiState, CreatePostMviModel.Effect>,
private val identityRepository: IdentityRepository,
@ -47,6 +46,20 @@ class CreatePostViewModel(
override fun reduce(intent: CreatePostMviModel.Intent) {
when (intent) {
is CreatePostMviModel.Intent.SetCommunity -> {
val community = intent.value
mvi.updateState {
it.copy(
communityId = community.id,
communityInfo = buildString {
append(community.name)
append("@")
append(community.host)
},
)
}
}
is CreatePostMviModel.Intent.SetTitle -> {
mvi.updateState {
it.copy(title = intent.value)
@ -132,6 +145,8 @@ class CreatePostViewModel(
bodyError = null,
)
}
val communityId = uiState.value.communityId
val title = uiState.value.title
val url = uiState.value.url
val nsfw = uiState.value.nsfw
@ -160,6 +175,15 @@ class CreatePostViewModel(
}
valid = false
}
if (communityId == null) {
mvi.updateState {
it.copy(
communityError = message_missing_field.desc(),
)
}
valid = false
}
if (!valid) {
return
}
@ -169,9 +193,9 @@ class CreatePostViewModel(
try {
val auth = identityRepository.authToken.value.orEmpty()
when {
communityId != null -> {
postRepository.create(
communityId = communityId,
editedPostId != null -> {
postRepository.edit(
postId = editedPostId,
title = title,
body = body,
url = url,
@ -180,9 +204,9 @@ class CreatePostViewModel(
)
}
editedPostId != null -> {
postRepository.edit(
postId = editedPostId,
communityId != null -> {
postRepository.create(
communityId = communityId,
title = title,
body = body,
url = url,

View File

@ -29,6 +29,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateRepor
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity.SelectCommunityMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity.SelectCommunityViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.utils.di.utilsModule
@ -134,8 +136,7 @@ val commonUiModule = module {
factory<CreatePostMviModel> { params ->
CreatePostViewModel(
mvi = DefaultMviModel(CreatePostMviModel.UiState()),
communityId = params[0],
editedPostId = params[1],
editedPostId = params[0],
identityRepository = get(),
postRepository = get(),
themeRepository = get(),
@ -199,4 +200,13 @@ val commonUiModule = module {
commentRepository = get(),
)
}
factory<SelectCommunityMviModel> {
SelectCommunityViewModel(
mvi = DefaultMviModel(SelectCommunityMviModel.UiState()),
identityRepository = get(),
communityRepository = get(),
settingsRepository = get(),
notificationCenter = get(),
)
}
}

View File

@ -16,6 +16,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.Navigat
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity.SelectCommunityMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
@ -61,7 +62,6 @@ expect fun getCreateCommentViewModel(
): CreateCommentMviModel
expect fun getCreatePostViewModel(
communityId: Int?,
editedPostId: Int?,
): CreatePostMviModel
@ -81,4 +81,6 @@ expect fun getCreateReportViewModel(
@Composable
expect fun getCustomTextToolbar(
onSearch: () -> Unit,
): TextToolbar
): TextToolbar
expect fun getSelectCommunityViewModel(): SelectCommunityMviModel

View File

@ -81,6 +81,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Comment
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createcomment.CreateCommentScreen
@ -407,37 +409,76 @@ class PostDetailScreen(
}
},
options = buildList {
add(stringResource(MR.strings.post_action_share))
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.Share,
stringResource(MR.strings.post_action_share)
)
)
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.CrossPost,
stringResource(MR.strings.post_action_cross_post)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
}
if (uiState.post.creator?.id == uiState.currentUserId && !isOnOtherInstance) {
add(stringResource(MR.strings.post_action_edit))
add(stringResource(MR.strings.comment_action_delete))
add(
Option(
OptionId.Edit,
stringResource(MR.strings.post_action_edit)
)
)
add(
Option(
OptionId.Delete,
stringResource(MR.strings.comment_action_delete)
)
)
}
},
onOptionSelected = rememberCallbackArgs(model) { idx ->
when (idx) {
4 -> model.reduce(PostDetailMviModel.Intent.DeletePost)
OptionId.Delete -> model.reduce(PostDetailMviModel.Intent.DeletePost)
3 -> {
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()?.show(
CreatePostScreen(
editedPost = uiState.post,
)
CreatePostScreen(editedPost = uiState.post)
)
}
2 -> {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()?.show(
CreateReportScreen(postId = uiState.post.id)
)
}
1 -> {
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()?.show(
CreatePostScreen(crossPost = uiState.post)
)
}
OptionId.SeeRaw -> {
rawContent = uiState.post
}
else -> model.reduce(PostDetailMviModel.Intent.SharePost)
OptionId.Share -> model.reduce(PostDetailMviModel.Intent.SharePost)
else -> Unit
}
},
onImageClick = rememberCallbackArgs { url ->
@ -657,24 +698,44 @@ class PostDetailScreen(
}
},
options = buildList {
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
if (comment.creator?.id == uiState.currentUserId) {
add(stringResource(MR.strings.post_action_edit))
add(stringResource(MR.strings.comment_action_delete))
add(
Option(
OptionId.Edit,
stringResource(MR.strings.post_action_edit)
)
)
add(
Option(
OptionId.Delete,
stringResource(MR.strings.comment_action_delete)
)
)
}
},
onOptionSelected = rememberCallbackArgs(
model
) { optionId ->
when (optionId) {
3 -> model.reduce(
OptionId.Delete -> model.reduce(
PostDetailMviModel.Intent.DeleteComment(
comment.id
)
)
2 -> {
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateCommentScreen(
@ -683,7 +744,7 @@ class PostDetailScreen(
)
}
1 -> {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
@ -692,9 +753,11 @@ class PostDetailScreen(
)
}
else -> {
OptionId.SeeRaw -> {
rawContent = comment
}
else -> Unit
}
},
)
@ -762,22 +825,42 @@ class PostDetailScreen(
}
},
options = buildList {
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
if (comment.creator?.id == uiState.currentUserId) {
add(stringResource(MR.strings.post_action_edit))
add(stringResource(MR.strings.comment_action_delete))
add(
Option(
OptionId.Edit,
stringResource(MR.strings.post_action_edit)
)
)
add(
Option(
OptionId.Delete,
stringResource(MR.strings.comment_action_delete)
)
)
}
},
onOptionSelected = rememberCallbackArgs(model) { optionId ->
when (optionId) {
3 -> model.reduce(
OptionId.Delete -> model.reduce(
PostDetailMviModel.Intent.DeleteComment(
comment.id
)
)
2 -> {
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateCommentScreen(
@ -786,7 +869,7 @@ class PostDetailScreen(
)
}
1 -> {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
@ -795,9 +878,11 @@ class PostDetailScreen(
)
}
else -> {
OptionId.SeeRaw -> {
rawContent = comment
}
else -> Unit
}
},
)

View File

@ -55,6 +55,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.Co
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createcomment.CreateCommentScreen
@ -299,13 +301,28 @@ class SavedItemsScreen : Screen {
)
},
options = buildList {
add(stringResource(MR.strings.post_action_share))
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.Share,
stringResource(MR.strings.post_action_share)
)
)
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
},
onOptionSelected = { optionIndex ->
when (optionIndex) {
2 -> {
OptionId.Report -> {
navigatorCoordinator.getBottomNavigator()?.show(
CreateReportScreen(
postId = post.id
@ -313,17 +330,19 @@ class SavedItemsScreen : Screen {
)
}
1 -> {
OptionId.SeeRaw -> {
rawContent = post
}
else -> {
OptionId.Share -> {
model.reduce(
SavedItemsMviModel.Intent.SharePost(
post.id
)
)
}
else -> Unit
}
},
)
@ -393,12 +412,22 @@ class SavedItemsScreen : Screen {
navigatorCoordinator.getBottomNavigator()?.show(screen)
},
options = buildList {
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
},
onOptionSelected = { optionIndex ->
when (optionIndex) {
1 -> {
OptionId.Report -> {
navigatorCoordinator.getBottomNavigator()?.show(
CreateReportScreen(
commentId = comment.id
@ -406,9 +435,11 @@ class SavedItemsScreen : Screen {
)
}
else -> {
OptionId.SeeRaw -> {
rawContent = comment
}
else -> Unit
}
},
)

View File

@ -0,0 +1,57 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.CornerSize
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.shimmerEffect
@Composable
fun CommunityItemPlaceholder() {
Row(
modifier = Modifier.padding(
vertical = Spacing.xs,
horizontal = Spacing.s,
),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(Spacing.xs),
) {
Box(
modifier = Modifier
.height(IconSize.m)
.fillMaxWidth()
.clip(RoundedCornerShape(CornerSize.s))
.shimmerEffect()
)
Column(
modifier = Modifier.padding(start = Spacing.xs),
) {
Box(
modifier = Modifier
.height(50.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(CornerSize.s))
.shimmerEffect()
)
Box(
modifier = Modifier
.height(30.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(CornerSize.s))
.shimmerEffect()
)
}
}
}

View File

@ -0,0 +1,159 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
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.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommunityItem
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getSelectCommunityViewModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource
class SelectCommunityDialog : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
val model = rememberScreenModel { getSelectCommunityViewModel() }
model.bindToLifecycle(key)
val uiState by model.uiState.collectAsState()
val notificationCenter = remember { getNotificationCenter() }
AlertDialog(
onDismissRequest = rememberCallback {
model.reduce(SelectCommunityMviModel.Intent.SetSearch(""))
notificationCenter.getObserver(NotificationCenterContractKeys.CloseDialog)
?.invoke(Unit)
},
) {
Column(
modifier = Modifier
.background(color = MaterialTheme.colorScheme.surface)
.padding(vertical = Spacing.s),
verticalArrangement = Arrangement.spacedBy(Spacing.s),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResource(MR.strings.dialog_title_select_community),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground,
)
Spacer(modifier = Modifier.height(Spacing.s))
TextField(
modifier = Modifier.fillMaxWidth(),
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
),
label = {
Text(text = stringResource(MR.strings.explore_search_placeholder))
},
singleLine = true,
value = uiState.searchText,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
),
onValueChange = { value ->
model.reduce(SelectCommunityMviModel.Intent.SetSearch(value))
},
trailingIcon = {
Icon(
modifier = Modifier.onClick(
rememberCallback {
if (uiState.searchText.isNotEmpty()) {
model.reduce(SelectCommunityMviModel.Intent.SetSearch(""))
}
},
),
imageVector = if (uiState.searchText.isEmpty()) Icons.Default.Search else Icons.Default.Clear,
contentDescription = null,
)
},
)
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 500.dp, max = 500.dp)
.padding(horizontal = Spacing.m)
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(Spacing.xxs),
) {
if (uiState.communities.isEmpty() && uiState.initial) {
items(5) {
CommunityItemPlaceholder()
}
}
items(uiState.communities, { it.id }) { community ->
CommunityItem(
modifier = Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.background)
.onClick(rememberCallback {
notificationCenter.getObserver(
NotificationCenterContractKeys.SelectCommunity
)
?.invoke(community)
notificationCenter.getObserver(
NotificationCenterContractKeys.CloseDialog
)
?.invoke(Unit)
}),
autoLoadImages = uiState.autoLoadImages,
community = community,
)
}
}
}
Button(
onClick = {
model.reduce(SelectCommunityMviModel.Intent.SetSearch(""))
notificationCenter.getObserver(NotificationCenterContractKeys.CloseDialog)
?.invoke(Unit)
},
) {
Text(text = stringResource(MR.strings.button_close))
}
}
}
}
}

View File

@ -0,0 +1,22 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity
import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
interface SelectCommunityMviModel :
MviModel<SelectCommunityMviModel.Intent, SelectCommunityMviModel.UiState, SelectCommunityMviModel.Effect>,
ScreenModel {
sealed interface Intent {
data class SetSearch(val value: String) : Intent
}
data class UiState(
val initial: Boolean = true,
val communities: List<CommunityModel> = emptyList(),
val searchText: String = "",
val autoLoadImages: Boolean = true,
)
sealed interface Effect
}

View File

@ -0,0 +1,82 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class SelectCommunityViewModel(
private val mvi: DefaultMviModel<SelectCommunityMviModel.Intent, SelectCommunityMviModel.UiState, SelectCommunityMviModel.Effect>,
private val identityRepository: IdentityRepository,
private val communityRepository: CommunityRepository,
private val settingsRepository: SettingsRepository,
private val notificationCenter: NotificationCenter,
) : SelectCommunityMviModel,
MviModel<SelectCommunityMviModel.Intent, SelectCommunityMviModel.UiState, SelectCommunityMviModel.Effect> by mvi {
private var communities: List<CommunityModel> = emptyList()
private var debounceJob: Job? = null
override fun onStarted() {
mvi.onStarted()
mvi.scope?.launch {
settingsRepository.currentSettings.onEach { settings ->
mvi.updateState { it.copy(autoLoadImages = settings.autoLoadImages) }
}.launchIn(this)
}
if (communities.isEmpty()) {
populate()
}
}
override fun reduce(intent: SelectCommunityMviModel.Intent) {
when (intent) {
is SelectCommunityMviModel.Intent.SetSearch -> setSearch(intent.value)
}
}
private fun setSearch(value: String) {
debounceJob?.cancel()
mvi.updateState { it.copy(searchText = value) }
debounceJob = mvi.scope?.launch(Dispatchers.IO) {
delay(1_000)
mvi.updateState {
val filtered = filterCommunities()
it.copy(communities = filtered)
}
}
}
private fun populate() {
mvi.scope?.launch(Dispatchers.IO) {
val auth = identityRepository.authToken.value
communities = communityRepository.getSubscribed(auth).sortedBy { it.name }
mvi.updateState {
it.copy(
initial = false,
communities = communities,
)
}
}
}
private fun filterCommunities(): List<CommunityModel> {
val searchText = uiState.value.searchText
val res = if (searchText.isNotEmpty()) {
communities.filter { it.name.contains(searchText) }
} else {
communities
}
return res
}
}

View File

@ -32,6 +32,7 @@ interface UserDetailMviModel :
}
data class UiState(
val currentUserId: Int? = null,
val section: UserDetailSection = UserDetailSection.Posts,
val sortType: SortType = SortType.Active,
val refreshing: Boolean = false,

View File

@ -69,6 +69,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Comment
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ProgressHud
@ -76,6 +78,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.Section
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserHeader
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createpost.CreatePostScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getDrawerCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getFabNestedScrollConnection
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
@ -317,17 +320,24 @@ class UserDetailScreen(
user = uiState.user,
autoLoadImages = uiState.autoLoadImages,
options = listOf(
stringResource(MR.strings.community_detail_block),
stringResource(MR.strings.community_detail_block_instance),
Option(
OptionId.Block,
stringResource(MR.strings.community_detail_block)
),
Option(
OptionId.BlockInstance,
stringResource(MR.strings.community_detail_block_instance)
),
),
onOpenImage = rememberCallbackArgs { url ->
navigationCoordinator.getRootNavigator()
?.push(ZoomableImageScreen(url))
},
onOptionSelected = rememberCallbackArgs { optionIdx ->
when (optionIdx) {
1 -> model.reduce(UserDetailMviModel.Intent.BlockInstance)
else -> model.reduce(UserDetailMviModel.Intent.Block)
onOptionSelected = rememberCallbackArgs { optionId ->
when (optionId) {
OptionId.BlockInstance -> model.reduce(UserDetailMviModel.Intent.BlockInstance)
OptionId.Block -> model.reduce(UserDetailMviModel.Intent.Block)
else -> Unit
}
},
)
@ -482,13 +492,36 @@ class UserDetailScreen(
)
},
options = buildList {
add(stringResource(MR.strings.post_action_share))
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.Share,
stringResource(MR.strings.post_action_share)
)
)
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.CrossPost,
stringResource(MR.strings.post_action_cross_post)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
}
},
onOptionSelected = rememberCallbackArgs { optionIdx ->
when (optionIdx) {
2 -> {
onOptionSelected = rememberCallbackArgs { optionId ->
when (optionId) {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
@ -497,13 +530,22 @@ class UserDetailScreen(
)
}
1 -> {
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(crossPost = post)
)
}
OptionId.SeeRaw -> {
rawContent = post
}
else -> model.reduce(
OptionId.Share -> model.reduce(
UserDetailMviModel.Intent.SharePost(post.id)
)
else -> Unit
}
})
},
@ -653,12 +695,24 @@ class UserDetailScreen(
?.push(CommunityDetailScreen(community))
},
options = buildList {
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
}
},
onOptionSelected = rememberCallbackArgs { optionId ->
when (optionId) {
1 -> {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(
@ -667,9 +721,11 @@ class UserDetailScreen(
)
}
else -> {
OptionId.SeeRaw -> {
rawContent = comment
}
else -> Unit
}
},
)

View File

@ -86,6 +86,11 @@ class UserDetailViewModel(
)
}
}.launchIn(this)
if (uiState.value.currentUserId == null) {
val auth = identityRepository.authToken.value.orEmpty()
val user = siteRepository.getCurrentUser(auth)
mvi.updateState { it.copy(currentUserId = user?.id ?: 0) }
}
}
if (uiState.value.posts.isEmpty()) {
refresh(initial = true)

View File

@ -19,6 +19,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.Navigat
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.selectcommunity.SelectCommunityMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
@ -71,10 +72,9 @@ actual fun getCreateCommentViewModel(
CommonUiViewModelHelper.getCreateCommentModel(postId, parentId, editedCommentId)
actual fun getCreatePostViewModel(
communityId: Int?,
editedPostId: Int?,
): CreatePostMviModel =
CommonUiViewModelHelper.getCreatePostModel(communityId, editedPostId)
CommonUiViewModelHelper.getCreatePostModel(editedPostId)
actual fun getZoomableImageViewModel(): ZoomableImageMviModel =
CommonUiViewModelHelper.zoomableImageModel
@ -93,6 +93,9 @@ actual fun getCreateReportViewModel(
commentId: Int?,
): CreateReportMviModel = CommonUiViewModelHelper.getCreateReportModel(postId, commentId)
actual fun getSelectCommunityViewModel(): SelectCommunityMviModel =
CommonUiViewModelHelper.selectCommunityViewModel
object CommonUiViewModelHelper : KoinComponent {
val navigationCoordinator: NavigationCoordinator by inject()
@ -101,6 +104,7 @@ object CommonUiViewModelHelper : KoinComponent {
val zoomableImageModel: ZoomableImageMviModel by inject()
val savedItemsViewModel: SavedItemsMviModel by inject()
val modalDrawerViewModel: ModalDrawerMviModel by inject()
val selectCommunityViewModel: SelectCommunityMviModel by inject()
fun getPostDetailModel(
post: PostModel,
@ -155,9 +159,9 @@ object CommonUiViewModelHelper : KoinComponent {
return model
}
fun getCreatePostModel(communityId: Int?, editedPostId: Int?): CreatePostMviModel {
fun getCreatePostModel(editedPostId: Int?): CreatePostMviModel {
val model: CreatePostMviModel by inject(
parameters = { parametersOf(communityId, editedPostId) }
parameters = { parametersOf(editedPostId) }
)
return model
}

View File

@ -22,4 +22,5 @@ object NotificationCenterContractKeys {
const val MultiCommunityCreated = "multiCommunityCreated"
const val ResetContents = "resetContents"
const val CloseDialog = "closeDialog"
const val SelectCommunity = "selectCommunity"
}

View File

@ -60,6 +60,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycl
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
@ -247,16 +249,18 @@ class PostListScreen : Screen {
}
},
)
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ClearAll,
text = stringResource(MR.strings.action_clear_read),
onSelected = rememberCallback {
model.reduce(PostListMviModel.Intent.ClearRead)
scope.launch {
lazyListState.scrollToItem(0)
}
},
)
if (uiState.currentUserId != null) {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ClearAll,
text = stringResource(MR.strings.action_clear_read),
onSelected = rememberCallback {
model.reduce(PostListMviModel.Intent.ClearRead)
scope.launch {
lazyListState.scrollToItem(0)
}
},
)
}
}
)
}
@ -403,44 +407,97 @@ class PostListScreen : Screen {
)
},
options = buildList {
add(stringResource(MR.strings.post_action_share))
add(stringResource(MR.strings.post_action_hide))
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.Share,
stringResource(MR.strings.post_action_share)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.Hide,
stringResource(MR.strings.post_action_hide)
)
)
}
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.CrossPost,
stringResource(MR.strings.post_action_cross_post)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
}
if (post.creator?.id == uiState.currentUserId) {
add(stringResource(MR.strings.post_action_edit))
add(stringResource(MR.strings.comment_action_delete))
add(
Option(
OptionId.Edit,
stringResource(MR.strings.post_action_edit)
)
)
add(
Option(
OptionId.Delete,
stringResource(MR.strings.comment_action_delete)
)
)
}
},
onOptionSelected = rememberCallbackArgs(model) { optionIdx ->
when (optionIdx) {
5 -> model.reduce(
onOptionSelected = rememberCallbackArgs(model) { optinId ->
when (optinId) {
OptionId.Delete -> model.reduce(
PostListMviModel.Intent.DeletePost(post.id)
)
4 -> {
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(editedPost = post)
)
}
3 -> {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreateReportScreen(postId = post.id)
)
}
2 -> {
OptionId.CrossPost -> {
navigationCoordinator.getBottomNavigator()
?.show(
CreatePostScreen(crossPost = post)
)
}
OptionId.SeeRaw -> {
rawContent = post
}
1 -> model.reduce(PostListMviModel.Intent.Hide(post.id))
OptionId.Hide -> model.reduce(
PostListMviModel.Intent.Hide(
post.id
)
)
else -> model.reduce(
OptionId.Share -> model.reduce(
PostListMviModel.Intent.SharePost(post.id)
)
else -> Unit
}
}
)

View File

@ -17,7 +17,7 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
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.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
@ -70,7 +70,7 @@ internal fun PostsTopBar(
}
else -> {
Box(modifier = Modifier.size(24.dp))
Box(modifier = Modifier.size(IconSize.m))
}
}
},

View File

@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextOverflow
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.CustomImage
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PlaceholderImage
@ -118,7 +119,7 @@ internal fun ChatCard(
horizontalArrangement = Arrangement.spacedBy(Spacing.xxs),
verticalAlignment = Alignment.CenterVertically,
) {
val buttonModifier = Modifier.size(24.dp).padding(3.25.dp)
val buttonModifier = Modifier.size(IconSize.m).padding(3.25.dp)
Icon(
modifier = buttonModifier.padding(1.dp),
imageVector = Icons.Default.Schedule,

View File

@ -42,6 +42,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycl
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCardPlaceholder
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
@ -230,18 +232,38 @@ internal object ProfileLoggedScreen : Tab {
)
},
options = buildList {
add(stringResource(MR.strings.post_action_share))
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_edit))
add(stringResource(MR.strings.comment_action_delete))
add(
Option(
OptionId.Share,
stringResource(MR.strings.post_action_share)
)
)
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
add(
Option(
OptionId.Edit,
stringResource(MR.strings.post_action_edit)
)
)
add(
Option(
OptionId.Delete,
stringResource(MR.strings.comment_action_delete)
)
)
},
onOptionSelected = rememberCallbackArgs(model) { optionIdx ->
when (optionIdx) {
3 -> model.reduce(
onOptionSelected = rememberCallbackArgs(model) { optionId ->
when (optionId) {
OptionId.Delete -> model.reduce(
ProfileLoggedMviModel.Intent.DeletePost(post.id)
)
2 -> {
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()?.show(
CreatePostScreen(
editedPost = post,
@ -249,13 +271,15 @@ internal object ProfileLoggedScreen : Tab {
)
}
1 -> {
OptionId.SeeRaw -> {
rawContent = post
}
else -> model.reduce(
OptionId.Share -> model.reduce(
ProfileLoggedMviModel.Intent.SharePost(post.id)
)
else -> Unit
}
},
)
@ -330,13 +354,28 @@ internal object ProfileLoggedScreen : Tab {
)
},
options = buildList {
add(stringResource(MR.strings.post_action_see_raw))
add(stringResource(MR.strings.post_action_edit))
add(stringResource(MR.strings.comment_action_delete))
add(
Option(
OptionId.SeeRaw,
stringResource(MR.strings.post_action_see_raw)
)
)
add(
Option(
OptionId.Edit,
stringResource(MR.strings.post_action_edit)
)
)
add(
Option(
OptionId.Delete,
stringResource(MR.strings.comment_action_delete)
)
)
},
onOptionSelected = rememberCallbackArgs(model) { optionIdx ->
when (optionIdx) {
2 -> {
onOptionSelected = rememberCallbackArgs(model) { optionId ->
when (optionId) {
OptionId.Delete -> {
model.reduce(
ProfileLoggedMviModel.Intent.DeleteComment(
comment.id
@ -344,7 +383,7 @@ internal object ProfileLoggedScreen : Tab {
)
}
1 -> {
OptionId.Edit -> {
navigationCoordinator.getBottomNavigator()?.show(
CreateCommentScreen(
editedComment = comment,
@ -352,9 +391,11 @@ internal object ProfileLoggedScreen : Tab {
)
}
else -> {
OptionId.SeeRaw -> {
rawContent = comment
}
else -> Unit
}
}
)

View File

@ -46,6 +46,7 @@ val exploreTabModule = module {
community = params[0],
postRepository = get(),
identityRepository = get(),
siteRepository = get(),
themeRepository = get(),
shareHelper = get(),
settingsRepository = get(),

View File

@ -17,7 +17,7 @@ import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
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.utils.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.rememberCallback
@ -69,7 +69,7 @@ internal fun ExploreTopBar(
}
else -> {
Box(modifier = Modifier.size(24.dp))
Box(modifier = Modifier.size(IconSize.m))
}
}
},

View File

@ -24,6 +24,7 @@ interface MultiCommunityMviModel :
}
data class UiState(
val currentUserId: Int? = null,
val refreshing: Boolean = false,
val loading: Boolean = false,
val canFetchMore: Boolean = true,

View File

@ -61,6 +61,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycl
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenu
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.FloatingActionButtonMenuItem
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.PostCard
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCardPlaceholder
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SwipeableCard
@ -375,13 +377,30 @@ class MultiCommunityScreen(
)
},
options = buildList {
add(stringResource(MR.strings.post_action_share))
add(stringResource(MR.strings.post_action_hide))
add(stringResource(MR.strings.post_action_report))
add(
Option(
OptionId.Share,
stringResource(MR.strings.post_action_share)
)
)
if (uiState.currentUserId != null) {
add(
Option(
OptionId.Hide,
stringResource(MR.strings.post_action_hide)
)
)
add(
Option(
OptionId.Report,
stringResource(MR.strings.post_action_report)
)
)
}
},
onOptionSelected = { optionIdx ->
when (optionIdx) {
2 -> {
onOptionSelected = { optionId ->
when (optionId) {
OptionId.Report -> {
navigationCoordinator.getBottomNavigator()?.show(
CreateReportScreen(
postId = post.id
@ -389,15 +408,17 @@ class MultiCommunityScreen(
)
}
1 -> model.reduce(
OptionId.Hide -> model.reduce(
MultiCommunityMviModel.Intent.Hide(
post.id
)
)
else -> model.reduce(
OptionId.Share -> model.reduce(
MultiCommunityMviModel.Intent.SharePost(post.id)
)
else -> Unit
}
}
)

View File

@ -17,6 +17,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.imageUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.shareUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import com.github.diegoberaldin.raccoonforlemmy.feature.search.multicommunity.utils.MultiCommunityPaginator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
@ -29,6 +30,7 @@ class MultiCommunityViewModel(
private val community: MultiCommunityModel,
private val postRepository: PostRepository,
private val identityRepository: IdentityRepository,
private val siteRepository: SiteRepository,
private val themeRepository: ThemeRepository,
private val shareHelper: ShareHelper,
private val settingsRepository: SettingsRepository,
@ -72,6 +74,11 @@ class MultiCommunityViewModel(
)
}
}.launchIn(this)
if (uiState.value.currentUserId == null) {
val auth = identityRepository.authToken.value.orEmpty()
val user = siteRepository.getCurrentUser(auth)
mvi.updateState { it.copy(currentUserId = user?.id ?: 0) }
}
}
mvi.scope?.launch(Dispatchers.IO) {

View File

@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.IconSize
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
@Composable
@ -32,7 +32,7 @@ internal fun SettingsHeader(
) {
if (icon != null) {
Icon(
modifier = Modifier.size(24.dp),
modifier = Modifier.size(IconSize.m),
imageVector = icon,
contentDescription = null,
)

View File

@ -26,6 +26,8 @@
<string name="create_comment_body">Comment body</string>
<string name="create_comment_title">New comment</string>
<string name="create_post_body">Post body</string>
<string name="create_post_community">Community</string>
<string name="create_post_cross_post_text">Cross posted from:</string>
<string name="create_post_name">Post title</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Editor</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Change instance</string>
<string name="dialog_title_raw_content">Raw content</string>
<string name="dialog_title_select_community">Select a community</string>
<string name="explore_result_type_all">All</string>
<string name="explore_result_type_comments">Comments</string>
<string name="explore_result_type_communities">Communities</string>
@ -131,7 +134,8 @@
<string name="navigation_profile">Profile</string>
<string name="navigation_search">Explore</string>
<string name="navigation_settings">Settings</string>
<string name="post_action_edit">Edit</string>
<string name="post_action_cross_post">Cross-post…</string>
<string name="post_action_edit">Edit…</string>
<string name="post_action_hide">Hide</string>
<string name="post_action_report">Report…</string>
<string name="post_action_see_raw">View raw…</string>

View File

@ -26,6 +26,8 @@
<string name="create_comment_body">Kommentartext</string>
<string name="create_comment_title">Neuer Kommentar</string>
<string name="create_post_body">Beitragstext</string>
<string name="create_post_community">Community</string>
<string name="create_post_cross_post_text">Cross-gepostet von:</string>
<string name="create_post_name">Beitragstitel</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Redakteur</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Instanz ändern</string>
<string name="dialog_title_raw_content">Rohinhalt</string>
<string name="dialog_title_select_community">Wählen Sie eine Community aus</string>
<string name="explore_result_type_all">Alle</string>
<string name="explore_result_type_comments">Kommentare</string>
<string name="explore_result_type_communities">Communitys</string>
@ -124,7 +127,8 @@
<string name="navigation_profile">Profil</string>
<string name="navigation_search">Erkunden</string>
<string name="navigation_settings">Einstellungen</string>
<string name="post_action_edit">Bearbeiten</string>
<string name="post_action_cross_post">Cross-post</string>
<string name="post_action_edit">Bearbeiten…</string>
<string name="post_action_hide">Verstecken</string>
<string name="post_action_report">Bericht…</string>
<string name="post_action_see_raw">Roh ansehen…</string>

View File

@ -24,6 +24,8 @@
<string name="community_info_subscribers">συνδρομητές</string>
<string name="community_info_weekly_active_users">ενεργοί χρήστες (εβδομάδα)</string>
<string name="create_comment_body">Κείμενο σχολίου</string>
<string name="create_post_community">Κοινότητα</string>
<string name="create_post_cross_post_text">Διασταυρούμενη από:</string>
<string name="create_comment_title">Νέο σχόλιο</string>
<string name="create_post_body">Κείμενο ανάρτησης</string>
<string name="create_post_name">Τίτλος ανάρτησης</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Αλλαγή παραδείγματος</string>
<string name="dialog_title_raw_content">Ακατέργαστο περιεχόμενο</string>
<string name="dialog_title_select_community">Επιλέξε μια κοινότητα</string>
<string name="explore_result_type_all">Όλα</string>
<string name="explore_result_type_comments">Σχόλια</string>
<string name="explore_result_type_communities">Κοινότητες</string>
@ -124,11 +127,12 @@
<string name="navigation_profile">Προφίλ</string>
<string name="navigation_search">Εξερεύνηση</string>
<string name="navigation_settings">Ρυθμίσεις</string>
<string name="post_action_edit">Επεξεργασία</string>
<string name="post_action_hide">Απόκρυψη</string>
<string name="post_action_report">Αναφορά…</string>
<string name="post_action_cross_post">Διασταύρωσε ανάρτηση…</string>
<string name="post_action_edit">Επεξεργασία…</string>
<string name="post_action_hide">Απόκρυψε</string>
<string name="post_action_report">Αναφόρησε…</string>
<string name="post_action_see_raw">Προβολή ακατέργαστου…</string>
<string name="post_action_share">Κοινοποίηση</string>
<string name="post_action_share">Κοινοποίησε</string>
<string name="post_detail_cross_posts">επίσης αναρτήθηκε σε:</string>
<string name="post_detail_load_more_comments">Φόρτωση περισσότερων σχολίων…</string>
<string name="post_hour_short">ώρ</string>

View File

@ -26,6 +26,8 @@
<string name="create_comment_body">Texto comentario</string>
<string name="create_comment_title">Nuevo comentario</string>
<string name="create_post_body">Texto publicación</string>
<string name="create_post_community">Comunidad</string>
<string name="create_post_cross_post_text">Publicación cruzada desde:</string>
<string name="create_post_name">Título publicación</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Editor</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Cambiar de instancia</string>
<string name="dialog_title_raw_content">Contenido raw</string>
<string name="dialog_title_select_community">Seleccionar una comunidad</string>
<string name="explore_result_type_all">Todos</string>
<string name="explore_result_type_comments">Comentarios</string>
<string name="explore_result_type_communities">Comunidades</string>
@ -124,7 +127,8 @@
<string name="navigation_profile">Perfil</string>
<string name="navigation_search">Descubre</string>
<string name="navigation_settings">Ajustes</string>
<string name="post_action_edit">Modificar</string>
<string name="post_action_cross_post">Publicación cruzada…</string>
<string name="post_action_edit">Modificar…</string>
<string name="post_action_hide">Esconder</string>
<string name="post_action_report">Crear informe…</string>
<string name="post_action_see_raw">Ver raw…</string>

View File

@ -26,6 +26,8 @@
<string name="create_comment_body">Texte du commentaire</string>
<string name="create_comment_title">Nouveau commentaire</string>
<string name="create_post_body">Texte de la publication</string>
<string name="create_post_community">Communauté</string>
<string name="create_post_cross_post_text">Publication croisée à partir de:</string>
<string name="create_post_name">Titre de la publication</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Éditeur</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Changer d\'instance</string>
<string name="dialog_title_raw_content">Contenu brut</string>
<string name="dialog_title_select_community">Sélectionner une communauté</string>
<string name="explore_result_type_all">Tous</string>
<string name="explore_result_type_comments">Commentaires</string>
<string name="explore_result_type_communities">Communautés</string>
@ -124,7 +127,8 @@
<string name="navigation_profile">Profil</string>
<string name="navigation_search">Explorer</string>
<string name="navigation_settings">Réglages</string>
<string name="post_action_edit">Éditer</string>
<string name="post_action_cross_post">Publication croisée…</string>
<string name="post_action_edit">Éditer…</string>
<string name="post_action_hide">Cacher</string>
<string name="post_action_report">Signaler</string>
<string name="post_action_see_raw">Voir brut…</string>

View File

@ -26,6 +26,8 @@
<string name="create_comment_body">Testo commento</string>
<string name="create_comment_title">Nuovo commento</string>
<string name="create_post_body">Testo post</string>
<string name="create_post_community">Comunità</string>
<string name="create_post_cross_post_text">Cross-post da:</string>
<string name="create_post_name">Titolo post</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Editor</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Cambia istanza</string>
<string name="dialog_title_raw_content">Contenuto grezzo</string>
<string name="dialog_title_select_community">Seleziona comunità</string>
<string name="explore_result_type_all">Tutti</string>
<string name="explore_result_type_comments">Commenti</string>
<string name="explore_result_type_communities">Comunità</string>
@ -124,7 +127,8 @@
<string name="navigation_profile">Profilo</string>
<string name="navigation_search">Esplora</string>
<string name="navigation_settings">Impostazioni</string>
<string name="post_action_edit">Modifica</string>
<string name="post_action_cross_post">Cross-post…</string>
<string name="post_action_edit">Modifica…</string>
<string name="post_action_hide">Nascondi</string>
<string name="post_action_report">Segnala…</string>
<string name="post_action_see_raw">Vedi raw…</string>

View File

@ -23,8 +23,10 @@
<string name="community_info_posts">berichten</string>
<string name="community_info_subscribers">abonnees</string>
<string name="community_info_weekly_active_users">actieve gebruikers (week)</string>
<string name="create_comment_body">Reactie body</string>
<string name="create_comment_title">Nieuwe opmerking</string>
<string name="create_comment_body">Commentaar body</string>
<string name="create_post_community">Gemeenschap</string>
<string name="create_post_cross_post_text">Crosspost van:</string>
<string name="create_comment_title">Nieuw commentaar</string>
<string name="create_post_body">Bericht body</string>
<string name="create_post_name">Bericht titel</string>
<string name="create_post_nsfw">NSFW</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Instantie wijzigen</string>
<string name="dialog_title_raw_content">Content</string>
<string name="dialog_title_select_community">Selecteer een gemeenschap</string>
<string name="explore_result_type_all">Alle</string>
<string name="explore_result_type_comments">Opmerkingen</string>
<string name="explore_result_type_communities">Samenleving</string>
@ -132,13 +135,14 @@
<string name="navigation_profile">Profiel</string>
<string name="navigation_search">Ontdekken</string>
<string name="navigation_settings">Instellingen</string>
<string name="post_action_edit">Bewerk</string>
<string name="post_action_cross_post">Crosspost…</string>
<string name="post_action_edit">Bewerk…</string>
<string name="post_action_hide">Verbergen</string>
<string name="post_action_report">Verslag</string>
<string name="post_action_see_raw">Toon raw</string>
<string name="post_action_share">Delen</string>
<string name="post_action_report">Verslag</string>
<string name="post_action_see_raw">Toon raw</string>
<string name="post_action_share">Delen</string>
<string name="post_detail_cross_posts">ook geplaatst op:</string>
<string name="post_detail_load_more_comments">Meer reacties laden</string>
<string name="post_detail_load_more_comments">Meer reacties laden</string>
<string name="post_hour_short">u</string>
<string name="post_minute_short">m</string>
<string name="post_second_short">s</string>

View File

@ -26,6 +26,8 @@
<string name="create_comment_body">Texto do comentário</string>
<string name="create_comment_title">Novo comentário</string>
<string name="create_post_body">Texto da publicação</string>
<string name="create_post_community">Comunidade</string>
<string name="create_post_cross_post_text">Publicação cruzada de:</string>
<string name="create_post_name">Título da publicação</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Editor</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Mudar instância</string>
<string name="dialog_title_raw_content">Conteúdo bruto</string>
<string name="dialog_title_select_community">Selecionar uma comunidade</string>
<string name="explore_result_type_all">Todos</string>
<string name="explore_result_type_comments">Comentários</string>
<string name="explore_result_type_communities">Comunidades</string>
@ -122,7 +125,8 @@
<string name="navigation_profile">Perfil</string>
<string name="navigation_search">Explorar</string>
<string name="navigation_settings">Configurações</string>
<string name="post_action_edit">Modificar</string>
<string name="post_action_cross_post">Publicação cruzada…</string>
<string name="post_action_edit">Modificar…</string>
<string name="post_action_hide">Esconder</string>
<string name="post_action_report">Denunciar…</string>
<string name="post_action_see_raw">Ver conteúdo bruto</string>

View File

@ -26,6 +26,8 @@
<string name="create_comment_body">Text comentariului</string>
<string name="create_comment_title">Nou comentariu</string>
<string name="create_post_body">Text postării</string>
<string name="create_post_community">Community</string>
<string name="create_post_cross_post_text">Postare încrucișată din:</string>
<string name="create_post_name">Titlu postării</string>
<string name="create_post_nsfw">NSFW</string>
<string name="create_post_tab_editor">Editor</string>
@ -40,6 +42,7 @@
<string name="dialog_raw_content_url">URL</string>
<string name="dialog_title_change_instance">Schimbă instanță</string>
<string name="dialog_title_raw_content">Conținut brut</string>
<string name="dialog_title_select_community">Selectează comunitate</string>
<string name="explore_result_type_all">Toate</string>
<string name="explore_result_type_comments">Comentarii</string>
<string name="explore_result_type_communities">Comunități</string>
@ -123,10 +126,11 @@
<string name="navigation_profile">Profil</string>
<string name="navigation_search">Exploră</string>
<string name="navigation_settings">Setări</string>
<string name="post_action_edit">Editează</string>
<string name="post_action_cross_post">Postare încrucișată…</string>
<string name="post_action_edit">Editează…</string>
<string name="post_action_hide">Ascunde</string>
<string name="post_action_report">Reportează…</string>
<string name="post_action_see_raw">Vizualizare bruta</string>
<string name="post_action_see_raw">Vizualizare brută</string>
<string name="post_action_share">Partajează…</string>
<string name="post_detail_cross_posts">postat și în:</string>
<string name="post_detail_load_more_comments">Încărcă mai multe comentări…</string>