refactor: screen params (state restoration) (#402)

* chore: add local cache

* chore: remove possibility to disable bottom sheet gestures

* chore: add cache to detail opener and create entry points

* chore: add query by id to multi-community

* chore: update community detail

* chore: update community info

* chore: update create comment

* chore: update create post

* chore: update multi-community screens

* chore: update user detail

* chore: update user info

* chore: update post detail

* chore: update post list

* chore: update modal drawer

* chore: update subscription management

* chore: update profile

* chore: update saved items

* chore: remove JavaSerializable

closes #316
This commit is contained in:
Diego Beraldin 2023-12-30 10:15:22 +01:00 committed by GitHub
parent d28f843a51
commit e206de2e16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 907 additions and 850 deletions

View File

@ -1,6 +1,7 @@
package com.github.diegoberaldin.raccoonforlemmy.core.commonui.detailopener.api package com.github.diegoberaldin.raccoonforlemmy.core.commonui.detailopener.api
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel 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.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
@ -23,4 +24,17 @@ interface DetailOpener {
highlightCommentId: Int? = null, highlightCommentId: Int? = null,
isMod: Boolean = false, isMod: Boolean = false,
) )
fun openReply(
originalPost: PostModel? = null,
originalComment: CommentModel? = null,
editedComment: CommentModel? = null,
initialText: String? = null,
)
fun openCreatePost(
editedPost: PostModel? = null,
crossPost: PostModel? = null,
communityId: Int? = null,
)
} }

View File

@ -44,11 +44,15 @@ kotlin {
implementation(projects.core.navigation) implementation(projects.core.navigation)
implementation(projects.core.commonui.detailopenerApi) implementation(projects.core.commonui.detailopenerApi)
implementation(projects.domain.lemmy.data)
implementation(projects.domain.lemmy.repository)
implementation(projects.unit.postdetail) implementation(projects.unit.postdetail)
implementation(projects.unit.communitydetail) implementation(projects.unit.communitydetail)
implementation(projects.unit.userdetail) implementation(projects.unit.userdetail)
implementation(projects.unit.createpost)
implementation(projects.domain.lemmy.data) implementation(projects.unit.createcomment)
implementation(projects.resources) implementation(projects.resources)
} }

View File

@ -2,33 +2,50 @@ package com.github.diegoberaldin.raccoonforlemmy.core.commonui.detailopener.impl
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.detailopener.api.DetailOpener import com.github.diegoberaldin.raccoonforlemmy.core.commonui.detailopener.api.DetailOpener
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.NavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel 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.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.unit.communitydetail.CommunityDetailScreen import com.github.diegoberaldin.raccoonforlemmy.unit.communitydetail.CommunityDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.CreatePostScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.postdetail.PostDetailScreen import com.github.diegoberaldin.raccoonforlemmy.unit.postdetail.PostDetailScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.unit.userdetail.UserDetailScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class DefaultDetailOpener( class DefaultDetailOpener(
private val navigationCoordinator: NavigationCoordinator, private val navigationCoordinator: NavigationCoordinator,
private val itemCache: LemmyItemCache,
) : DetailOpener { ) : DetailOpener {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
override fun openCommunityDetail(community: CommunityModel, otherInstance: String) { override fun openCommunityDetail(community: CommunityModel, otherInstance: String) {
navigationCoordinator.pushScreen( scope.launch {
CommunityDetailScreen( itemCache.putCommunity(community)
community = community, navigationCoordinator.pushScreen(
otherInstance = otherInstance, CommunityDetailScreen(
), communityId = community.id,
) otherInstance = otherInstance,
),
)
}
} }
override fun openUserDetail(user: UserModel, otherInstance: String) { override fun openUserDetail(user: UserModel, otherInstance: String) {
navigationCoordinator.pushScreen( scope.launch {
UserDetailScreen( itemCache.putUser(user)
user = user, navigationCoordinator.pushScreen(
otherInstance = otherInstance, UserDetailScreen(
), userId = user.id,
) otherInstance = otherInstance,
),
)
}
} }
override fun openPostDetail( override fun openPostDetail(
@ -37,13 +54,63 @@ class DefaultDetailOpener(
highlightCommentId: Int?, highlightCommentId: Int?,
isMod: Boolean, isMod: Boolean,
) { ) {
navigationCoordinator.pushScreen( scope.launch {
PostDetailScreen( itemCache.putPost(post)
post = post, navigationCoordinator.pushScreen(
highlightCommentId = highlightCommentId, PostDetailScreen(
otherInstance = otherInstance, postId = post.id,
isMod = isMod, highlightCommentId = highlightCommentId,
), otherInstance = otherInstance,
) isMod = isMod,
),
)
}
}
override fun openReply(
originalPost: PostModel?,
originalComment: CommentModel?,
editedComment: CommentModel?,
initialText: String?,
) {
scope.launch {
if (originalPost != null) {
itemCache.putPost(originalPost)
}
if (originalComment != null) {
itemCache.putComment(originalComment)
}
if (editedComment != null) {
itemCache.putComment(editedComment)
}
val screen = CreateCommentScreen(
originalPostId = originalPost?.id,
originalCommentId = originalComment?.id,
editedCommentId = editedComment?.id,
initialText = initialText,
)
navigationCoordinator.pushScreen(screen)
}
}
override fun openCreatePost(
editedPost: PostModel?,
crossPost: PostModel?,
communityId: Int?,
) {
scope.launch {
if (editedPost != null) {
itemCache.putPost(editedPost)
}
if (crossPost != null) {
itemCache.putPost(crossPost)
}
val screen = CreatePostScreen(
editedPostId = editedPost?.id,
crossPostId = crossPost?.id,
communityId = communityId,
)
navigationCoordinator.pushScreen(screen)
}
} }
} }

View File

@ -40,7 +40,6 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
override val inboxUnread = MutableStateFlow(0) override val inboxUnread = MutableStateFlow(0)
override val canPop = MutableStateFlow(false) override val canPop = MutableStateFlow(false)
override val exitMessageVisible = MutableStateFlow(false) override val exitMessageVisible = MutableStateFlow(false)
override val bottomSheetGesturesEnabled = MutableStateFlow(true)
private var connection: NestedScrollConnection? = null private var connection: NestedScrollConnection? = null
private var navigator: Navigator? = null private var navigator: Navigator? = null
@ -72,7 +71,6 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
NavigationEvent.Hide -> { NavigationEvent.Hide -> {
bottomNavigator?.hide() bottomNavigator?.hide()
setBottomSheetGesturesEnabled(true)
} }
} }
}.launchIn(this) }.launchIn(this)
@ -175,10 +173,6 @@ internal class DefaultNavigationCoordinator : NavigationCoordinator {
exitMessageVisible.value = value exitMessageVisible.value = value
} }
override fun setBottomSheetGesturesEnabled(value: Boolean) {
bottomSheetGesturesEnabled.value = value
}
override fun setTabNavigator(value: TabNavigator) { override fun setTabNavigator(value: TabNavigator) {
tabNavigator = value tabNavigator = value
} }

View File

@ -27,7 +27,6 @@ interface NavigationCoordinator {
val deepLinkUrl: Flow<String?> val deepLinkUrl: Flow<String?>
val canPop: StateFlow<Boolean> val canPop: StateFlow<Boolean>
val exitMessageVisible: StateFlow<Boolean> val exitMessageVisible: StateFlow<Boolean>
val bottomSheetGesturesEnabled: StateFlow<Boolean>
fun setCurrentSection(section: TabNavigationSection) fun setCurrentSection(section: TabNavigationSection)
fun submitDeeplink(url: String) fun submitDeeplink(url: String)
@ -43,7 +42,6 @@ interface NavigationCoordinator {
fun pushScreen(screen: Screen) fun pushScreen(screen: Screen)
fun popScreen() fun popScreen()
fun setExitMessageVisible(value: Boolean) fun setExitMessageVisible(value: Boolean)
fun setBottomSheetGesturesEnabled(value: Boolean)
fun setTabNavigator(value: TabNavigator) fun setTabNavigator(value: TabNavigator)
fun changeTab(value: Tab) fun changeTab(value: Tab)
} }

View File

@ -1,6 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class AccountModel( data class AccountModel(
val id: Long? = null, val id: Long? = null,
@ -9,4 +8,4 @@ data class AccountModel(
val instance: String, val instance: String,
val jwt: String, val jwt: String,
val active: Boolean = false, val active: Boolean = false,
) : JavaSerializable )

View File

@ -1,8 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class FavoriteCommunityModel( data class FavoriteCommunityModel(
val id: Long? = null, val id: Long? = null,
val communityId: Int? = null, val communityId: Int? = null,
) : JavaSerializable )

View File

@ -1,10 +1,8 @@
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class MultiCommunityModel( data class MultiCommunityModel(
val id: Long? = null, val id: Long? = null,
val name: String = "", val name: String = "",
val communityIds: List<Int> = emptyList(), val communityIds: List<Int> = emptyList(),
val icon: String? = null, val icon: String? = null,
) : JavaSerializable )

View File

@ -1,7 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data package com.github.diegoberaldin.raccoonforlemmy.core.persistence.data
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -40,4 +39,4 @@ data class SettingsModel(
val replyColor: Int? = null, val replyColor: Int? = null,
val searchPostTitleOnly: Boolean = false, val searchPostTitleOnly: Boolean = false,
val edgeToEdge: Boolean = true, val edgeToEdge: Boolean = true,
) : JavaSerializable )

View File

@ -13,12 +13,17 @@ internal class DefaultMultiCommunityRepository(
private val db = provider.getDatabase() private val db = provider.getDatabase()
override suspend fun getAll(accountId: Long?): List<MultiCommunityModel> = override suspend fun getAll(accountId: Long): List<MultiCommunityModel> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.multicommunitiesQueries.getAll(accountId) db.multicommunitiesQueries.getAll(accountId)
.executeAsList().map { it.toModel() } .executeAsList().map { it.toModel() }
} }
override suspend fun getById(id: Long): MultiCommunityModel? =
withContext(Dispatchers.IO) {
db.multicommunitiesQueries.getById(id).executeAsOneOrNull()?.toModel()
}
override suspend fun create(model: MultiCommunityModel, accountId: Long): Long = override suspend fun create(model: MultiCommunityModel, accountId: Long): Long =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
db.multicommunitiesQueries.create( db.multicommunitiesQueries.create(

View File

@ -3,7 +3,8 @@ package com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
interface MultiCommunityRepository { interface MultiCommunityRepository {
suspend fun getAll(accountId: Long?): List<MultiCommunityModel> suspend fun getById(id: Long): MultiCommunityModel?
suspend fun getAll(accountId: Long): List<MultiCommunityModel>
suspend fun create(model: MultiCommunityModel, accountId: Long): Long suspend fun create(model: MultiCommunityModel, accountId: Long): Long

View File

@ -18,6 +18,11 @@ SELECT *
FROM MultiCommunityEntity FROM MultiCommunityEntity
WHERE name = ? AND account_id = ?; WHERE name = ? AND account_id = ?;
getById:
SELECT *
FROM MultiCommunityEntity
WHERE id = ?;
create: create:
INSERT OR IGNORE INTO MultiCommunityEntity ( INSERT OR IGNORE INTO MultiCommunityEntity (
name, name,

View File

@ -1,3 +0,0 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils
actual typealias JavaSerializable = java.io.Serializable

View File

@ -1,3 +0,0 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils
expect interface JavaSerializable

View File

@ -0,0 +1,51 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils.cache
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
internal class DefaultLruCache<T>(val size: Int) : LruCache<T> {
private val values: MutableMap<Int, T> = mutableMapOf()
private var lastUsedIds: List<Int> = listOf()
private val mutex = Mutex()
override suspend fun get(key: Int): T? = mutex.withLock {
val res = values[key]
if (res != null) {
moveAtTheBeginning(key)
}
return res
}
override suspend fun put(value: T, key: Int) = mutex.withLock {
val old = values[key]
if (old != null) {
// already existing element
moveAtTheBeginning(key)
} else {
// new element
values[key] = value
lastUsedIds = buildList {
this += key
this += lastUsedIds
}
if (lastUsedIds.size > size) {
dropOldest()
}
}
}
private fun dropOldest() {
lastUsedIds.lastOrNull()?.also { oldest ->
values.remove(oldest)
lastUsedIds = lastUsedIds - oldest
}
}
private fun moveAtTheBeginning(key: Int) {
lastUsedIds = buildList {
this += key
this += (lastUsedIds - key)
}
}
}

View File

@ -0,0 +1,11 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils.cache
interface LruCache<T> {
suspend fun get(key: Int): T?
suspend fun put(value: T, key: Int)
companion object {
fun <T> factory(size: Int): LruCache<T> = DefaultLruCache(size)
}
}

View File

@ -1,3 +0,0 @@
package com.github.diegoberaldin.raccoonforlemmy.core.utils
actual interface JavaSerializable

View File

@ -1,9 +1,7 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class AccountBansModel( data class AccountBansModel(
val users: List<UserModel> = emptyList(), val users: List<UserModel> = emptyList(),
val communities: List<CommunityModel> = emptyList(), val communities: List<CommunityModel> = emptyList(),
val instances: List<InstanceModel> = emptyList(), val instances: List<InstanceModel> = emptyList(),
) : JavaSerializable )

View File

@ -1,7 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class AccountSettingsModel( data class AccountSettingsModel(
val avatar: String? = null, val avatar: String? = null,
val banner: String? = null, val banner: String? = null,
@ -16,4 +14,4 @@ data class AccountSettingsModel(
val showNsfw: Boolean? = null, val showNsfw: Boolean? = null,
val defaultListingType: ListingType? = null, val defaultListingType: ListingType? = null,
val defaultSortType: SortType? = null, val defaultSortType: SortType? = null,
) : JavaSerializable )

View File

@ -1,6 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import kotlin.jvm.Transient import kotlin.jvm.Transient
data class CommentModel( data class CommentModel(
@ -27,7 +26,7 @@ data class CommentModel(
@Transient @Transient
val loadMoreButtonVisible: Boolean = false, val loadMoreButtonVisible: Boolean = false,
val languageId: Int = 0, val languageId: Int = 0,
) : JavaSerializable { ) {
val depth: Int get() = (path.split(".").size - 2).coerceAtLeast(0) val depth: Int get() = (path.split(".").size - 2).coerceAtLeast(0)
val parentId: String? val parentId: String?
get() = path.split(".") get() = path.split(".")

View File

@ -1,7 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class CommentReportModel( data class CommentReportModel(
val id: Int = 0, val id: Int = 0,
val creator: UserModel? = null, val creator: UserModel? = null,
@ -13,4 +11,4 @@ data class CommentReportModel(
val resolver: UserModel? = null, val resolver: UserModel? = null,
val publishDate: String? = null, val publishDate: String? = null,
val updateDate: String? = null, val updateDate: String? = null,
) : JavaSerializable )

View File

@ -1,6 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import kotlin.jvm.Transient import kotlin.jvm.Transient
data class CommunityModel( data class CommunityModel(
@ -23,4 +22,4 @@ data class CommunityModel(
val comments: Int = 0, val comments: Int = 0,
val creationDate: String? = null, val creationDate: String? = null,
@Transient val favorite: Boolean = false, @Transient val favorite: Boolean = false,
) : JavaSerializable )

View File

@ -1,8 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class InstanceModel( data class InstanceModel(
val id: Int = 0, val id: Int = 0,
val domain: String = "", val domain: String = "",
) : JavaSerializable )

View File

@ -5,11 +5,10 @@ import androidx.compose.material.icons.filled.Book
import androidx.compose.material.icons.filled.Cottage import androidx.compose.material.icons.filled.Cottage
import androidx.compose.material.icons.filled.Public import androidx.compose.material.icons.filled.Public
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
sealed interface ListingType : JavaSerializable { sealed interface ListingType {
data object All : ListingType data object All : ListingType
data object Subscribed : ListingType data object Subscribed : ListingType
data object Local : ListingType data object Local : ListingType

View File

@ -1,8 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class MetadataModel( data class MetadataModel(
val title: String = "", val title: String = "",
val description: String = "", val description: String = "",
) : JavaSerializable )

View File

@ -1,10 +1,8 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
sealed class ModlogItem( sealed class ModlogItem(
val type: ModlogItemType, val type: ModlogItemType,
) : JavaSerializable { ) {
abstract val id: Int abstract val id: Int
abstract val date: String? abstract val date: String?

View File

@ -1,8 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable sealed interface ModlogItemType {
sealed interface ModlogItemType : JavaSerializable {
data object All : ModlogItemType data object All : ModlogItemType
data object ModRemovePost : ModlogItemType data object ModRemovePost : ModlogItemType
data object ModLockPost : ModlogItemType data object ModLockPost : ModlogItemType

View File

@ -1,7 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class PersonMentionModel( data class PersonMentionModel(
val id: Int = 0, val id: Int = 0,
val post: PostModel, val post: PostModel,
@ -16,4 +14,4 @@ data class PersonMentionModel(
val isOwnPost: Boolean = false, val isOwnPost: Boolean = false,
val publishDate: String? = null, val publishDate: String? = null,
val read: Boolean = false, val read: Boolean = false,
) : JavaSerializable )

View File

@ -1,6 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage
data class PostModel( data class PostModel(
@ -28,7 +27,7 @@ data class PostModel(
val removed: Boolean = false, val removed: Boolean = false,
val locked: Boolean = false, val locked: Boolean = false,
val languageId: Int = 0, val languageId: Int = 0,
) : JavaSerializable )
val PostModel.imageUrl: String val PostModel.imageUrl: String
get() = url?.takeIf { it.looksLikeAnImage }?.takeIf { it.isNotEmpty() } ?: run { get() = url?.takeIf { it.looksLikeAnImage }?.takeIf { it.isNotEmpty() } ?: run {

View File

@ -1,6 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage import com.github.diegoberaldin.raccoonforlemmy.core.utils.looksLikeAnImage
data class PostReportModel( data class PostReportModel(
@ -16,7 +15,7 @@ data class PostReportModel(
val resolver: UserModel? = null, val resolver: UserModel? = null,
val publishDate: String? = null, val publishDate: String? = null,
val updateDate: String? = null, val updateDate: String? = null,
) : JavaSerializable )
val PostReportModel.imageUrl: String val PostReportModel.imageUrl: String
get() = originalUrl?.takeIf { it.looksLikeAnImage }?.takeIf { it.isNotEmpty() } ?: run { get() = originalUrl?.takeIf { it.looksLikeAnImage }?.takeIf { it.isNotEmpty() } ?: run {

View File

@ -1,7 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class PrivateMessageModel( data class PrivateMessageModel(
val id: Int = 0, val id: Int = 0,
val content: String? = null, val content: String? = null,
@ -10,7 +8,7 @@ data class PrivateMessageModel(
val publishDate: String? = null, val publishDate: String? = null,
val updateDate: String? = null, val updateDate: String? = null,
val read: Boolean = false, val read: Boolean = false,
) : JavaSerializable )
fun PrivateMessageModel.otherUser(currentUserId: Int): UserModel? = when (currentUserId) { fun PrivateMessageModel.otherUser(currentUserId: Int): UserModel? = when (currentUserId) {
creator?.id -> recipient creator?.id -> recipient

View File

@ -1,8 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable sealed interface SearchResult {
sealed interface SearchResult : JavaSerializable {
data class Post(val model: PostModel) : SearchResult data class Post(val model: PostModel) : SearchResult
data class Comment(val model: CommentModel) : SearchResult data class Comment(val model: CommentModel) : SearchResult
data class User(val model: UserModel) : SearchResult data class User(val model: UserModel) : SearchResult

View File

@ -1,8 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable sealed interface SearchResultType {
sealed interface SearchResultType : JavaSerializable {
data object All : SearchResultType data object All : SearchResultType
data object Posts : SearchResultType data object Posts : SearchResultType
data object Comments : SearchResultType data object Comments : SearchResultType

View File

@ -12,11 +12,10 @@ import androidx.compose.material.icons.filled.Thunderstorm
import androidx.compose.material.icons.filled.TrendingUp import androidx.compose.material.icons.filled.TrendingUp
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
sealed interface SortType : JavaSerializable { sealed interface SortType {
data object Active : SortType data object Active : SortType
data object Hot : SortType data object Hot : SortType
data object New : SortType data object New : SortType

View File

@ -1,7 +1,5 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class UserModel( data class UserModel(
val id: Int = 0, val id: Int = 0,
val instanceId: Int = 0, val instanceId: Int = 0,
@ -17,6 +15,6 @@ data class UserModel(
val banned: Boolean = false, val banned: Boolean = false,
val updateDate: String? = null, val updateDate: String? = null,
val admin: Boolean = false, val admin: Boolean = false,
) : JavaSerializable )
fun List<UserModel>.containsId(value: Int?): Boolean = any { it.id == value } fun List<UserModel>.containsId(value: Int?): Boolean = any { it.id == value }

View File

@ -1,8 +1,6 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data
import com.github.diegoberaldin.raccoonforlemmy.core.utils.JavaSerializable
data class UserScoreModel( data class UserScoreModel(
val postScore: Int = 0, val postScore: Int = 0,
val commentScore: Int = 0, val commentScore: Int = 0,
) : JavaSerializable )

View File

@ -29,7 +29,10 @@ kotlin {
dependencies { dependencies {
implementation(libs.koin.core) implementation(libs.koin.core)
implementation(libs.ktorfit.lib) implementation(libs.ktorfit.lib)
implementation(projects.core.api) implementation(projects.core.api)
implementation(projects.core.utils)
implementation(projects.domain.lemmy.data) implementation(projects.domain.lemmy.data)
} }
} }

View File

@ -0,0 +1,39 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.cache.LruCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
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.UserModel
internal class DefaultLemmyItemCache(
private val postCache: LruCache<PostModel>,
private val communityCache: LruCache<CommunityModel>,
private val commentCache: LruCache<CommentModel>,
private val userCache: LruCache<UserModel>,
) : LemmyItemCache {
override suspend fun putPost(value: PostModel) {
postCache.put(value = value, key = value.id)
}
override suspend fun getPost(id: Int): PostModel? = postCache.get(id)
override suspend fun putComment(value: CommentModel) {
commentCache.put(value = value, key = value.id)
}
override suspend fun getComment(id: Int): CommentModel? = commentCache.get(id)
override suspend fun putCommunity(value: CommunityModel) {
communityCache.put(value = value, key = value.id)
}
override suspend fun getCommunity(id: Int): CommunityModel? = communityCache.get(id)
override suspend fun putUser(value: UserModel) {
userCache.put(value = value, key = value.id)
}
override suspend fun getUser(id: Int): UserModel? = userCache.get(id)
}

View File

@ -0,0 +1,20 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
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.UserModel
interface LemmyItemCache {
suspend fun putPost(value: PostModel)
suspend fun getPost(id: Int): PostModel?
suspend fun putComment(value: CommentModel)
suspend fun getComment(id: Int): CommentModel?
suspend fun putCommunity(value: CommunityModel)
suspend fun getCommunity(id: Int): CommunityModel?
suspend fun putUser(value: UserModel)
suspend fun getUser(id: Int): UserModel?
}

View File

@ -1,16 +1,19 @@
package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.di package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.di
import com.github.diegoberaldin.raccoonforlemmy.core.utils.cache.LruCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultCommentRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultCommentRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultCommunityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultCommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultGetSortTypesUseCase import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultGetSortTypesUseCase
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultLemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultModlogRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultModlogRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultPostRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultPostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultPrivateMessageRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultPrivateMessageRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultSiteRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultSiteRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultUserRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.DefaultUserRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.ModlogRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.ModlogRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PrivateMessageRepository
@ -65,4 +68,12 @@ val repositoryModule = module {
services = get(named("default")), services = get(named("default")),
) )
} }
single<LemmyItemCache> {
DefaultLemmyItemCache(
postCache = LruCache.factory(5),
commentCache = LruCache.factory(5),
communityCache = LruCache.factory(5),
userCache = LruCache.factory(5),
)
}
} }

View File

@ -90,7 +90,6 @@ fun App(onLoadingFinished: () -> Unit = {}) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val drawerCoordinator = remember { getDrawerCoordinator() } val drawerCoordinator = remember { getDrawerCoordinator() }
val drawerGesturesEnabled by drawerCoordinator.gesturesEnabled.collectAsState() val drawerGesturesEnabled by drawerCoordinator.gesturesEnabled.collectAsState()
val bottomSheetGesturesEnabled by navigationCoordinator.bottomSheetGesturesEnabled.collectAsState()
val detailOpener = remember { getDetailOpener() } val detailOpener = remember { getDetailOpener() }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@ -203,7 +202,9 @@ fun App(onLoadingFinished: () -> Unit = {}) {
} }
is DrawerEvent.OpenMultiCommunity -> { is DrawerEvent.OpenMultiCommunity -> {
navigationCoordinator.pushScreen(MultiCommunityScreen(evt.community)) evt.community.id?.toInt()?.also {
navigationCoordinator.pushScreen(MultiCommunityScreen(it))
}
} }
DrawerEvent.ManageSubscriptions -> { DrawerEvent.ManageSubscriptions -> {
@ -240,7 +241,6 @@ fun App(onLoadingFinished: () -> Unit = {}) {
topEnd = CornerSize.xl topEnd = CornerSize.xl
), ),
sheetBackgroundColor = MaterialTheme.colorScheme.background, sheetBackgroundColor = MaterialTheme.colorScheme.background,
sheetGesturesEnabled = bottomSheetGesturesEnabled,
) { bottomNavigator -> ) { bottomNavigator ->
navigationCoordinator.setBottomNavigator(bottomNavigator) navigationCoordinator.setBottomNavigator(bottomNavigator)

View File

@ -17,6 +17,7 @@ internal val internalSharedModule = module {
single<DetailOpener> { single<DetailOpener> {
DefaultDetailOpener( DefaultDetailOpener(
navigationCoordinator = get(), navigationCoordinator = get(),
itemCache = get(),
) )
} }
} }

View File

@ -101,15 +101,12 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.keepscreenon.rememberKeepScreenOn import com.github.diegoberaldin.raccoonforlemmy.core.utils.keepscreenon.rememberKeepScreenOn
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp 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.CommentModel
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.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.containsId import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.containsId
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.ban.BanUserScreen import com.github.diegoberaldin.raccoonforlemmy.unit.ban.BanUserScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.CommunityInfoScreen import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.CommunityInfoScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.CreatePostScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.instanceinfo.InstanceInfoScreen import com.github.diegoberaldin.raccoonforlemmy.unit.instanceinfo.InstanceInfoScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.modlog.ModlogScreen import com.github.diegoberaldin.raccoonforlemmy.unit.modlog.ModlogScreen
@ -125,21 +122,21 @@ import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class CommunityDetailScreen( class CommunityDetailScreen(
private val community: CommunityModel, private val communityId: Int,
private val otherInstance: String = "", private val otherInstance: String = "",
) : Screen { ) : Screen {
override val key: ScreenKey override val key: ScreenKey
get() = super.key + community.id.toString() get() = super.key + communityId.toString()
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<CommunityDetailMviModel>( val model = getScreenModel<CommunityDetailMviModel>(
tag = community.id.toString() + community.name, tag = communityId.toString(),
parameters = { parametersOf(community, otherInstance) }, parameters = { parametersOf(communityId, otherInstance) },
) )
model.bindToLifecycle(key + community.id.toString()) model.bindToLifecycle(key + communityId.toString())
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -366,7 +363,7 @@ class CommunityDetailScreen(
OptionId.Info -> { OptionId.Info -> {
navigationCoordinator.showBottomSheet( navigationCoordinator.showBottomSheet(
CommunityInfoScreen(uiState.community), CommunityInfoScreen(uiState.community.id),
) )
} }
@ -475,13 +472,9 @@ class CommunityDetailScreen(
icon = Icons.Default.Create, icon = Icons.Default.Create,
text = stringResource(MR.strings.action_create_post), text = stringResource(MR.strings.action_create_post),
onSelected = rememberCallback { onSelected = rememberCallback {
with(navigationCoordinator) { detailOpener.openCreatePost(
setBottomSheetGesturesEnabled(false) communityId = uiState.community.id,
val screen = CreatePostScreen( )
communityId = uiState.community.id,
)
showBottomSheet(screen = screen)
}
}, },
) )
} }
@ -612,13 +605,9 @@ class CommunityDetailScreen(
) )
}, },
onSecondDismissToStart = rememberCallback(model) { onSecondDismissToStart = rememberCallback(model) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = post,
val screen = CreateCommentScreen( )
originalPost = post,
)
showBottomSheet(screen)
}
}, },
onDismissToEnd = rememberCallback(model) { onDismissToEnd = rememberCallback(model) {
model.reduce( model.reduce(
@ -808,12 +797,7 @@ class CommunityDetailScreen(
) )
OptionId.Edit -> { OptionId.Edit -> {
with(navigationCoordinator) { detailOpener.openCreatePost(editedPost = post)
setBottomSheetGesturesEnabled(false)
showBottomSheet(
CreatePostScreen(editedPost = post),
)
}
} }
OptionId.Report -> { OptionId.Report -> {
@ -823,12 +807,7 @@ class CommunityDetailScreen(
} }
OptionId.CrossPost -> { OptionId.CrossPost -> {
with(navigationCoordinator) { detailOpener.openCreatePost(crossPost = post)
setBottomSheetGesturesEnabled(false)
showBottomSheet(
CreatePostScreen(crossPost = post),
)
}
} }
OptionId.SeeRaw -> { OptionId.SeeRaw -> {
@ -943,7 +922,8 @@ class CommunityDetailScreen(
if (rawContent != null) { if (rawContent != null) {
when (val content = rawContent) { when (val content = rawContent) {
is PostModel -> { is PostModel -> {
RawContentDialog(title = content.title, RawContentDialog(
title = content.title,
publishDate = content.publishDate, publishDate = content.publishDate,
updateDate = content.updateDate, updateDate = content.updateDate,
url = content.url, url = content.url,
@ -954,20 +934,17 @@ class CommunityDetailScreen(
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = content,
val screen = CreateCommentScreen( initialText = buildString {
originalPost = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
},
)
showBottomSheet(screen)
}
} }
}) },
)
} }
is CommentModel -> { is CommentModel -> {
@ -981,16 +958,14 @@ class CommunityDetailScreen(
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalComment = content,
val screen = CreateCommentScreen(originalComment = content, initialText = buildString {
initialText = buildString { append("> ")
append("> ") append(quotation)
append(quotation) append("\n\n")
append("\n\n") },
}) )
showBottomSheet(screen)
}
} }
}, },
) )

View File

@ -23,6 +23,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.imageUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -33,7 +34,7 @@ import kotlinx.coroutines.launch
class CommunityDetailViewModel( class CommunityDetailViewModel(
private val mvi: DefaultMviModel<CommunityDetailMviModel.Intent, CommunityDetailMviModel.UiState, CommunityDetailMviModel.Effect>, private val mvi: DefaultMviModel<CommunityDetailMviModel.Intent, CommunityDetailMviModel.UiState, CommunityDetailMviModel.Effect>,
private val community: CommunityModel, private val communityId: Int,
private val otherInstance: String, private val otherInstance: String,
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val apiConfigurationRepository: ApiConfigurationRepository, private val apiConfigurationRepository: ApiConfigurationRepository,
@ -50,6 +51,7 @@ class CommunityDetailViewModel(
private val imagePreloadManager: ImagePreloadManager, private val imagePreloadManager: ImagePreloadManager,
private val getSortTypesUseCase: GetSortTypesUseCase, private val getSortTypesUseCase: GetSortTypesUseCase,
private val notificationCenter: NotificationCenter, private val notificationCenter: NotificationCenter,
private val itemCache: LemmyItemCache,
) : CommunityDetailMviModel, ) : CommunityDetailMviModel,
MviModel<CommunityDetailMviModel.Intent, CommunityDetailMviModel.UiState, CommunityDetailMviModel.Effect> by mvi { MviModel<CommunityDetailMviModel.Intent, CommunityDetailMviModel.UiState, CommunityDetailMviModel.Effect> by mvi {
@ -59,16 +61,18 @@ class CommunityDetailViewModel(
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
mvi.updateState {
it.copy(
community = it.community.takeIf { c -> c.id != 0 } ?: community,
instance = otherInstance.takeIf { n -> n.isNotEmpty() }
?: apiConfigurationRepository.instance.value,
)
}
mvi.scope?.launch { mvi.scope?.launch {
if (uiState.value.community.id == 0) {
val community = itemCache.getCommunity(communityId) ?: CommunityModel()
mvi.updateState {
it.copy(
community = community,
instance = otherInstance.takeIf { n -> n.isNotEmpty() }
?: apiConfigurationRepository.instance.value,
)
}
}
themeRepository.postLayout.onEach { layout -> themeRepository.postLayout.onEach { layout ->
mvi.updateState { it.copy(postLayout = layout) } mvi.updateState { it.copy(postLayout = layout) }
}.launchIn(this) }.launchIn(this)
@ -252,6 +256,7 @@ class CommunityDetailViewModel(
pageCursor = null pageCursor = null
hideReadPosts = false hideReadPosts = false
mvi.updateState { it.copy(canFetchMore = true, refreshing = true) } mvi.updateState { it.copy(canFetchMore = true, refreshing = true) }
val community = uiState.value.community
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
val accountId = accountRepository.getActive()?.id val accountId = accountRepository.getActive()?.id
val isFavorite = favoriteCommunityRepository.getBy(accountId, community.id) != null val isFavorite = favoriteCommunityRepository.getBy(accountId, community.id) != null
@ -298,11 +303,11 @@ class CommunityDetailViewModel(
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
val refreshing = currentState.refreshing val refreshing = currentState.refreshing
val sort = currentState.sortType val sort = currentState.sortType
val communityId = currentState.community.id val community = currentState.community
val (itemList, nextPage) = postRepository.getAll( val (itemList, nextPage) = postRepository.getAll(
auth = auth, auth = auth,
otherInstance = otherInstance, otherInstance = otherInstance,
communityId = communityId, communityId = community.id,
communityName = community.name, communityName = community.name,
page = currentPage, page = currentPage,
pageCursor = pageCursor, pageCursor = pageCursor,
@ -459,7 +464,7 @@ class CommunityDetailViewModel(
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
communityRepository.subscribe( communityRepository.subscribe(
auth = identityRepository.authToken.value, auth = identityRepository.authToken.value,
id = community.id, id = communityId,
) )
// the first response isn't immediately true, simulate here // the first response isn't immediately true, simulate here
mvi.updateState { it.copy(community = it.community.copy(subscribed = true)) } mvi.updateState { it.copy(community = it.community.copy(subscribed = true)) }
@ -471,7 +476,7 @@ class CommunityDetailViewModel(
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
val community = communityRepository.unsubscribe( val community = communityRepository.unsubscribe(
auth = identityRepository.authToken.value, auth = identityRepository.authToken.value,
id = community.id, id = communityId,
) )
if (community != null) { if (community != null) {
mvi.updateState { it.copy(community = community) } mvi.updateState { it.copy(community = community) }
@ -501,7 +506,6 @@ class CommunityDetailViewModel(
mvi.updateState { it.copy(asyncInProgress = true) } mvi.updateState { it.copy(asyncInProgress = true) }
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
try { try {
val communityId = community.id
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
communityRepository.block(communityId, true, auth).getOrThrow() communityRepository.block(communityId, true, auth).getOrThrow()
mvi.emitEffect(CommunityDetailMviModel.Effect.BlockSuccess) mvi.emitEffect(CommunityDetailMviModel.Effect.BlockSuccess)
@ -517,6 +521,7 @@ class CommunityDetailViewModel(
mvi.updateState { it.copy(asyncInProgress = true) } mvi.updateState { it.copy(asyncInProgress = true) }
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
try { try {
val community = uiState.value.community
val instanceId = community.instanceId val instanceId = community.instanceId
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
siteRepository.block(instanceId, true, auth).getOrThrow() siteRepository.block(instanceId, true, auth).getOrThrow()
@ -583,7 +588,7 @@ class CommunityDetailViewModel(
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
val newModerators = communityRepository.addModerator( val newModerators = communityRepository.addModerator(
auth = auth, auth = auth,
communityId = community.id, communityId = communityId,
added = !isModerator, added = !isModerator,
userId = userId, userId = userId,
) )
@ -594,10 +599,9 @@ class CommunityDetailViewModel(
} }
private fun toggleFavorite() { private fun toggleFavorite() {
val communityId = community.id
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
val accountId = accountRepository.getActive()?.id ?: 0L val accountId = accountRepository.getActive()?.id ?: 0L
val newValue = !community.favorite val newValue = !uiState.value.community.favorite
if (newValue) { if (newValue) {
val model = FavoriteCommunityModel(communityId = communityId) val model = FavoriteCommunityModel(communityId = communityId)
favoriteCommunityRepository.create(model, accountId) favoriteCommunityRepository.create(model, accountId)

View File

@ -9,7 +9,7 @@ val communityDetailModule = module {
factory<CommunityDetailMviModel> { params -> factory<CommunityDetailMviModel> { params ->
CommunityDetailViewModel( CommunityDetailViewModel(
mvi = DefaultMviModel(CommunityDetailMviModel.UiState()), mvi = DefaultMviModel(CommunityDetailMviModel.UiState()),
community = params[0], communityId = params[0],
otherInstance = params[1], otherInstance = params[1],
identityRepository = get(), identityRepository = get(),
apiConfigurationRepository = get(), apiConfigurationRepository = get(),
@ -26,6 +26,7 @@ val communityDetailModule = module {
getSortTypesUseCase = get(), getSortTypesUseCase = get(),
accountRepository = get(), accountRepository = get(),
favoriteCommunityRepository = get(), favoriteCommunityRepository = get(),
itemCache = get(),
) )
} }
} }

View File

@ -32,7 +32,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.getScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle 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.BottomSheetHandle
@ -41,10 +40,10 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.detailopener.api.g
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.DetailInfoItem import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.DetailInfoItem
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.PostCardBody import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.PostCardBody
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.getScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.components.ModeratorCell import com.github.diegoberaldin.raccoonforlemmy.unit.communityinfo.components.ModeratorCell
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
@ -55,14 +54,14 @@ import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class CommunityInfoScreen( class CommunityInfoScreen(
private val community: CommunityModel, private val communityId: Int,
) : Screen { ) : Screen {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<CommunityInfoMviModel>( val model = getScreenModel<CommunityInfoMviModel>(
tag = community.id.toString() + community.name, tag = communityId.toString(),
parameters = { parametersOf(community) }, parameters = { parametersOf(communityId) },
) )
model.bindToLifecycle(key) model.bindToLifecycle(key)
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()

View File

@ -5,30 +5,35 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class CommunityInfoViewModel( class CommunityInfoViewModel(
private val mvi: DefaultMviModel<CommunityInfoMviModel.Intent, CommunityInfoMviModel.UiState, CommunityInfoMviModel.Effect>, private val mvi: DefaultMviModel<CommunityInfoMviModel.Intent, CommunityInfoMviModel.UiState, CommunityInfoMviModel.Effect>,
private val community: CommunityModel, private val communityId: Int,
private val communityRepository: CommunityRepository, private val communityRepository: CommunityRepository,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val itemCache: LemmyItemCache,
) : CommunityInfoMviModel, ) : CommunityInfoMviModel,
MviModel<CommunityInfoMviModel.Intent, CommunityInfoMviModel.UiState, CommunityInfoMviModel.Effect> by mvi { MviModel<CommunityInfoMviModel.Intent, CommunityInfoMviModel.UiState, CommunityInfoMviModel.Effect> by mvi {
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
mvi.updateState { it.copy(community = community) }
mvi.scope?.launch { mvi.scope?.launch {
if (uiState.value.community.id == 0) {
val community = itemCache.getCommunity(communityId) ?: CommunityModel()
mvi.updateState { it.copy(community = community) }
}
settingsRepository.currentSettings.onEach { settingsRepository.currentSettings.onEach {
mvi.updateState { it.copy(autoLoadImages = it.autoLoadImages) } mvi.updateState { it.copy(autoLoadImages = it.autoLoadImages) }
}.launchIn(this) }.launchIn(this)
if (uiState.value.moderators.isEmpty()) { if (uiState.value.moderators.isEmpty()) {
val moderators = communityRepository.getModerators( val moderators = communityRepository.getModerators(
id = community.id id = communityId
) )
mvi.updateState { it.copy(moderators = moderators) } mvi.updateState { it.copy(moderators = moderators) }
} }

View File

@ -9,9 +9,10 @@ val communityInfoModule = module {
factory<CommunityInfoMviModel> { params -> factory<CommunityInfoMviModel> { params ->
CommunityInfoViewModel( CommunityInfoViewModel(
mvi = DefaultMviModel(CommunityInfoMviModel.UiState()), mvi = DefaultMviModel(CommunityInfoMviModel.UiState()),
community = params[0], communityId = params[0],
communityRepository = get(), communityRepository = get(),
settingsRepository = get(), settingsRepository = get(),
itemCache = get(),
) )
} }
} }

View File

@ -6,7 +6,9 @@ import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CreatePostSection import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CreatePostSection
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.LanguageModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.LanguageModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import dev.icerock.moko.resources.desc.StringDesc import dev.icerock.moko.resources.desc.StringDesc
@Stable @Stable
@ -37,6 +39,9 @@ interface CreateCommentMviModel :
} }
data class UiState( data class UiState(
val originalPost: PostModel? = null,
val originalComment: CommentModel? = null,
val editedComment: CommentModel? = null,
val postLayout: PostLayout = PostLayout.Card, val postLayout: PostLayout = PostLayout.Card,
val fullHeightImages: Boolean = true, val fullHeightImages: Boolean = true,
val voteFormat: VoteFormat = VoteFormat.Aggregated, val voteFormat: VoteFormat = VoteFormat.Aggregated,

View File

@ -82,9 +82,9 @@ import kotlinx.coroutines.flow.onEach
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class CreateCommentScreen( class CreateCommentScreen(
private val originalPost: PostModel? = null, private val originalPostId: Int? = null,
private val originalComment: CommentModel? = null, private val originalCommentId: Int? = null,
private val editedComment: CommentModel? = null, private val editedCommentId: Int? = null,
private val initialText: String? = null, private val initialText: String? = null,
) : Screen { ) : Screen {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@ -92,9 +92,9 @@ class CreateCommentScreen(
override fun Content() { override fun Content() {
val model = getScreenModel<CreateCommentMviModel> { val model = getScreenModel<CreateCommentMviModel> {
parametersOf( parametersOf(
originalPost?.id, originalPostId,
originalComment?.id, originalCommentId,
editedComment?.id, editedCommentId,
) )
} }
model.bindToLifecycle(key) model.bindToLifecycle(key)
@ -109,7 +109,7 @@ class CreateCommentScreen(
var textFieldValue by remember { var textFieldValue by remember {
mutableStateOf( mutableStateOf(
TextFieldValue( TextFieldValue(
text = (initialText ?: editedComment?.text).orEmpty() text = (initialText ?: uiState.editedComment?.text).orEmpty()
) )
) )
} }
@ -122,7 +122,7 @@ class CreateCommentScreen(
var selectLanguageDialogOpen by remember { mutableStateOf(false) } var selectLanguageDialogOpen by remember { mutableStateOf(false) }
LaunchedEffect(model) { LaunchedEffect(model) {
if (editedComment != null) { uiState.editedComment?.also { editedComment ->
model.reduce(CreateCommentMviModel.Intent.ChangeLanguage(editedComment.languageId)) model.reduce(CreateCommentMviModel.Intent.ChangeLanguage(editedComment.languageId))
} }
model.effects.onEach { effect -> model.effects.onEach { effect ->
@ -133,7 +133,7 @@ class CreateCommentScreen(
is CreateCommentMviModel.Effect.Success -> { is CreateCommentMviModel.Effect.Success -> {
notificationCenter.send(event = NotificationCenterEvent.CommentCreated) notificationCenter.send(event = NotificationCenterEvent.CommentCreated)
if (originalPost != null) { uiState.originalPost?.also { originalPost ->
notificationCenter.send( notificationCenter.send(
event = NotificationCenterEvent.PostUpdated( event = NotificationCenterEvent.PostUpdated(
originalPost.copy( originalPost.copy(
@ -146,7 +146,7 @@ class CreateCommentScreen(
), ),
) )
} }
navigationCoordinator.hideBottomSheet() navigationCoordinator.popScreen()
} }
is CreateCommentMviModel.Effect.AddImageToText -> { is CreateCommentMviModel.Effect.AddImageToText -> {
@ -167,7 +167,7 @@ class CreateCommentScreen(
Image( Image(
modifier = Modifier.padding(start = Spacing.s).onClick( modifier = Modifier.padding(start = Spacing.s).onClick(
onClick = rememberCallback { onClick = rememberCallback {
navigationCoordinator.hideBottomSheet() navigationCoordinator.popScreen()
}, },
), ),
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
@ -189,7 +189,7 @@ class CreateCommentScreen(
BottomSheetHandle() BottomSheetHandle()
Text( Text(
text = when { text = when {
editedComment != null -> { uiState.editedComment != null -> {
stringResource(MR.strings.edit_comment_title) stringResource(MR.strings.edit_comment_title)
} }
@ -345,57 +345,55 @@ class CreateCommentScreen(
modifier = Modifier.padding(padding), modifier = Modifier.padding(padding),
) { ) {
item { item {
when { val originalComment = uiState.originalComment
originalComment != null -> { val originalPost = uiState.originalPost
CommentCard( if (originalComment != null) {
modifier = referenceModifier, CommentCard(
comment = originalComment, modifier = referenceModifier,
hideIndent = true, comment = originalComment,
voteFormat = uiState.voteFormat, hideIndent = true,
autoLoadImages = uiState.autoLoadImages, voteFormat = uiState.voteFormat,
options = buildList { autoLoadImages = uiState.autoLoadImages,
add( options = buildList {
Option( add(
OptionId.SeeRaw, Option(
stringResource(MR.strings.post_action_see_raw) OptionId.SeeRaw,
) stringResource(MR.strings.post_action_see_raw)
) )
}, )
onOptionSelected = { },
rawContent = originalComment onOptionSelected = {
}, rawContent = originalComment
) },
Divider() )
} Divider()
} else if (originalPost != null) {
originalPost != null -> { PostCard(
PostCard( modifier = referenceModifier,
modifier = referenceModifier, postLayout = if (uiState.postLayout == PostLayout.Card) {
postLayout = if (uiState.postLayout == PostLayout.Card) { uiState.postLayout
uiState.postLayout } else {
} else { PostLayout.Full
PostLayout.Full },
}, fullHeightImage = uiState.fullHeightImages,
fullHeightImage = uiState.fullHeightImages, post = originalPost,
post = originalPost, limitBodyHeight = true,
limitBodyHeight = true, blurNsfw = false,
blurNsfw = false, includeFullBody = true,
includeFullBody = true, voteFormat = uiState.voteFormat,
voteFormat = uiState.voteFormat, autoLoadImages = uiState.autoLoadImages,
autoLoadImages = uiState.autoLoadImages, options = buildList {
options = buildList { add(
add( Option(
Option( OptionId.SeeRaw,
OptionId.SeeRaw, stringResource(MR.strings.post_action_see_raw)
stringResource(MR.strings.post_action_see_raw)
)
) )
}, )
onOptionSelected = { },
rawContent = originalPost onOptionSelected = {
}, rawContent = originalPost
) },
} )
} }
} }
} }

View File

@ -8,6 +8,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationC
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import com.github.diegoberaldin.raccoonforlemmy.resources.MR.strings.message_missing_field import com.github.diegoberaldin.raccoonforlemmy.resources.MR.strings.message_missing_field
@ -30,15 +31,28 @@ class CreateCommentViewModel(
private val themeRepository: ThemeRepository, private val themeRepository: ThemeRepository,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val notificationCenter: NotificationCenter, private val notificationCenter: NotificationCenter,
private val itemCache: LemmyItemCache,
) : CreateCommentMviModel, ) : CreateCommentMviModel,
MviModel<CreateCommentMviModel.Intent, CreateCommentMviModel.UiState, CreateCommentMviModel.Effect> by mvi { MviModel<CreateCommentMviModel.Intent, CreateCommentMviModel.UiState, CreateCommentMviModel.Effect> by mvi {
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
mvi.scope?.launch { mvi.scope?.launch {
val originalPost = postId?.let { itemCache.getPost(it) }
val originalComment = parentId?.let { itemCache.getComment(it) }
val editedComment = editedCommentId?.let { itemCache.getComment(it) }
mvi.updateState {
it.copy(
originalPost = originalPost,
originalComment = originalComment,
editedComment = editedComment,
)
}
themeRepository.postLayout.onEach { layout -> themeRepository.postLayout.onEach { layout ->
mvi.updateState { it.copy(postLayout = layout) } mvi.updateState { it.copy(postLayout = layout) }
}.launchIn(this) }.launchIn(this)
if (uiState.value.currentUser.isEmpty()) { if (uiState.value.currentUser.isEmpty()) {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
val currentUser = siteRepository.getCurrentUser(auth) val currentUser = siteRepository.getCurrentUser(auth)

View File

@ -19,6 +19,7 @@ val createCommentModule = module {
themeRepository = get(), themeRepository = get(),
settingsRepository = get(), settingsRepository = get(),
notificationCenter = get(), notificationCenter = get(),
itemCache = get(),
) )
} }
} }

View File

@ -8,6 +8,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CreatePostSection import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CreatePostSection
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.LanguageModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.LanguageModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import dev.icerock.moko.resources.desc.StringDesc import dev.icerock.moko.resources.desc.StringDesc
@Stable @Stable
@ -57,6 +58,8 @@ interface CreatePostMviModel :
} }
data class UiState( data class UiState(
val editedPost: PostModel? = null,
val crossPost: PostModel? = null,
val communityInfo: String = "", val communityInfo: String = "",
val communityId: Int? = null, val communityId: Int? = null,
val communityError: StringDesc? = null, val communityError: StringDesc? = null,

View File

@ -87,14 +87,14 @@ import org.koin.core.parameter.parametersOf
class CreatePostScreen( class CreatePostScreen(
private val communityId: Int? = null, private val communityId: Int? = null,
private val editedPost: PostModel? = null, private val editedPostId: Int? = null,
private val crossPost: PostModel? = null, private val crossPostId: Int? = null,
) : Screen { ) : Screen {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<CreatePostMviModel> { val model = getScreenModel<CreatePostMviModel> {
parametersOf(editedPost?.id) parametersOf(editedPostId, crossPostId)
} }
model.bindToLifecycle(key) model.bindToLifecycle(key)
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()
@ -103,22 +103,21 @@ class CreatePostScreen(
val notificationCenter = remember { getNotificationCenter() } val notificationCenter = remember { getNotificationCenter() }
val galleryHelper = remember { getGalleryHelper() } val galleryHelper = remember { getGalleryHelper() }
val crossPostText = stringResource(MR.strings.create_post_cross_post_text) val crossPostText = stringResource(MR.strings.create_post_cross_post_text)
val crossPost = uiState.crossPost
val editedPost = uiState.editedPost
var bodyTextFieldValue by remember { var bodyTextFieldValue by remember {
val text = when { val text = buildString {
crossPost != null -> buildString { if (crossPost != null) {
append(crossPostText) append(crossPostText)
append(" ") append(" ")
append(crossPost.originalUrl) append(crossPost.originalUrl)
} else if (editedPost != null) {
append(editedPost.text)
} }
editedPost != null -> {
editedPost.text
}
else -> ""
} }
mutableStateOf(TextFieldValue(text = text)) mutableStateOf(TextFieldValue(text = text))
} }
val bodyFocusRequester = remember { FocusRequester() } val bodyFocusRequester = remember { FocusRequester() }
val urlFocusRequester = remember { FocusRequester() } val urlFocusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
@ -137,6 +136,7 @@ class CreatePostScreen(
model.reduce(CreatePostMviModel.Intent.InsertImageInBody(bytes)) model.reduce(CreatePostMviModel.Intent.InsertImageInBody(bytes))
} }
} }
var openSelectCommunity by remember { mutableStateOf(false) } var openSelectCommunity by remember { mutableStateOf(false) }
val topAppBarState = rememberTopAppBarState() val topAppBarState = rememberTopAppBarState()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarState) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(topAppBarState)
@ -146,7 +146,7 @@ class CreatePostScreen(
var selectLanguageDialogOpen by remember { mutableStateOf(false) } var selectLanguageDialogOpen by remember { mutableStateOf(false) }
LaunchedEffect(model) { LaunchedEffect(model) {
val referencePost = editedPost ?: crossPost val referencePost = uiState.editedPost ?: uiState.crossPost
model.reduce(CreatePostMviModel.Intent.SetTitle(referencePost?.title.orEmpty())) model.reduce(CreatePostMviModel.Intent.SetTitle(referencePost?.title.orEmpty()))
model.reduce(CreatePostMviModel.Intent.SetUrl(referencePost?.url.orEmpty())) model.reduce(CreatePostMviModel.Intent.SetUrl(referencePost?.url.orEmpty()))
if (editedPost != null) { if (editedPost != null) {
@ -178,7 +178,7 @@ class CreatePostScreen(
notificationCenter.send( notificationCenter.send(
event = NotificationCenterEvent.PostCreated, event = NotificationCenterEvent.PostCreated,
) )
navigationCoordinator.hideBottomSheet() navigationCoordinator.popScreen()
} }
is CreatePostMviModel.Effect.AddImageToBody -> { is CreatePostMviModel.Effect.AddImageToBody -> {
@ -189,7 +189,8 @@ class CreatePostScreen(
} }
}.launchIn(this) }.launchIn(this)
} }
LaunchedEffect(notificationCenter) { LaunchedEffect(notificationCenter)
{
notificationCenter.subscribe(NotificationCenterEvent.SelectCommunity::class) notificationCenter.subscribe(NotificationCenterEvent.SelectCommunity::class)
.onEach { evt -> .onEach { evt ->
model.reduce(CreatePostMviModel.Intent.SetCommunity(evt.model)) model.reduce(CreatePostMviModel.Intent.SetCommunity(evt.model))
@ -211,7 +212,7 @@ class CreatePostScreen(
Image( Image(
modifier = Modifier.padding(start = Spacing.s).onClick( modifier = Modifier.padding(start = Spacing.s).onClick(
onClick = rememberCallback { onClick = rememberCallback {
navigationCoordinator.hideBottomSheet() navigationCoordinator.popScreen()
}, },
), ),
imageVector = Icons.Default.Close, imageVector = Icons.Default.Close,
@ -232,7 +233,6 @@ class CreatePostScreen(
Text( Text(
text = when { text = when {
editedPost != null -> stringResource(MR.strings.edit_post_title) editedPost != null -> stringResource(MR.strings.edit_post_title)
else -> stringResource(MR.strings.create_post_title) else -> stringResource(MR.strings.create_post_title)
}, },
style = MaterialTheme.typography.titleLarge, style = MaterialTheme.typography.titleLarge,
@ -254,7 +254,8 @@ class CreatePostScreen(
) )
}, },
) )
}, snackbarHost = { }, snackbarHost =
{
SnackbarHost(snackbarHostState) { data -> SnackbarHost(snackbarHostState) { data ->
Snackbar( Snackbar(
containerColor = MaterialTheme.colorScheme.surfaceVariant, containerColor = MaterialTheme.colorScheme.surfaceVariant,
@ -262,7 +263,8 @@ class CreatePostScreen(
snackbarData = data, snackbarData = data,
) )
} }
}) { padding -> })
{ padding ->
Column( Column(
modifier = Modifier.padding(padding).verticalScroll(rememberScrollState()), modifier = Modifier.padding(padding).verticalScroll(rememberScrollState()),
) { ) {

View File

@ -6,6 +6,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.StringUtils.isValidUrl import com.github.diegoberaldin.raccoonforlemmy.core.utils.StringUtils.isValidUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import com.github.diegoberaldin.raccoonforlemmy.resources.MR.strings.message_invalid_field import com.github.diegoberaldin.raccoonforlemmy.resources.MR.strings.message_invalid_field
@ -18,19 +19,29 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class CreatePostViewModel( class CreatePostViewModel(
private val editedPostId: Int?,
private val mvi: DefaultMviModel<CreatePostMviModel.Intent, CreatePostMviModel.UiState, CreatePostMviModel.Effect>, private val mvi: DefaultMviModel<CreatePostMviModel.Intent, CreatePostMviModel.UiState, CreatePostMviModel.Effect>,
private val editedPostId: Int?,
private val crossPostId: Int?,
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val siteRepository: SiteRepository, private val siteRepository: SiteRepository,
private val themeRepository: ThemeRepository, private val themeRepository: ThemeRepository,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val itemCache: LemmyItemCache,
) : CreatePostMviModel, ) : CreatePostMviModel,
MviModel<CreatePostMviModel.Intent, CreatePostMviModel.UiState, CreatePostMviModel.Effect> by mvi { MviModel<CreatePostMviModel.Intent, CreatePostMviModel.UiState, CreatePostMviModel.Effect> by mvi {
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
mvi.scope?.launch { mvi.scope?.launch {
val editedPost = editedPostId?.let {
itemCache.getPost(it)
}
val crossPost = crossPostId?.let {
itemCache.getPost(it)
}
mvi.updateState { it.copy(editedPost = editedPost, crossPost = crossPost) }
themeRepository.postLayout.onEach { layout -> themeRepository.postLayout.onEach { layout ->
mvi.updateState { it.copy(postLayout = layout) } mvi.updateState { it.copy(postLayout = layout) }
}.launchIn(this) }.launchIn(this)

View File

@ -10,11 +10,13 @@ val createPostModule = module {
CreatePostViewModel( CreatePostViewModel(
mvi = DefaultMviModel(CreatePostMviModel.UiState()), mvi = DefaultMviModel(CreatePostMviModel.UiState()),
editedPostId = params[0], editedPostId = params[0],
crossPostId = params[1],
identityRepository = get(), identityRepository = get(),
postRepository = get(), postRepository = get(),
siteRepository = get(), siteRepository = get(),
themeRepository = get(), themeRepository = get(),
settingsRepository = get(), settingsRepository = get(),
itemCache = get(),
) )
} }
} }

View File

@ -152,8 +152,9 @@ class ModalDrawerViewModel(
val res = it - favorites.toSet() val res = it - favorites.toSet()
favorites + res favorites + res
} }
val multiCommunitites = multiCommunityRepository.getAll(accountId).sortedBy { it.name } val multiCommunitites = accountId?.let {
multiCommunityRepository.getAll(it).sortedBy { e -> e.name }
}.orEmpty()
mvi.updateState { mvi.updateState {
it.copy( it.copy(
refreshing = false, refreshing = false,

View File

@ -190,9 +190,11 @@ class ManageSubscriptionsScreen : Screen {
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.background(MaterialTheme.colorScheme.background).onClick( .background(MaterialTheme.colorScheme.background).onClick(
onClick = rememberCallback { onClick = rememberCallback {
navigatorCoordinator.pushScreen( community.id?.toInt()?.also {
MultiCommunityScreen(community), navigatorCoordinator.pushScreen(
) MultiCommunityScreen(it),
)
}
}, },
), ),
community = community, community = community,
@ -211,7 +213,7 @@ class ManageSubscriptionsScreen : Screen {
when (optionId) { when (optionId) {
OptionId.Edit -> { OptionId.Edit -> {
navigatorCoordinator.pushScreen( navigatorCoordinator.pushScreen(
MultiCommunityEditorScreen(community), MultiCommunityEditorScreen(community.id?.toInt()),
) )
} }

View File

@ -4,6 +4,7 @@ import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.PostLayout
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat import com.github.diegoberaldin.raccoonforlemmy.core.appearance.data.VoteFormat
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
@ -32,6 +33,7 @@ interface MultiCommunityMviModel :
val instance: String = "", val instance: String = "",
val isLogged: Boolean = false, val isLogged: Boolean = false,
val sortType: SortType? = null, val sortType: SortType? = null,
val community: MultiCommunityModel = MultiCommunityModel(),
val posts: List<PostModel> = emptyList(), val posts: List<PostModel> = emptyList(),
val blurNsfw: Boolean = true, val blurNsfw: Boolean = true,
val swipeActionsEnabled: Boolean = true, val swipeActionsEnabled: Boolean = true,

View File

@ -75,7 +75,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.di.getFabN
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ShareBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ShareBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.di.getSettingsRepository 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.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallback
@ -83,7 +82,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.getAdditionalLabel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.getAdditionalLabel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen
@ -91,15 +89,18 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf
class MultiCommunityScreen( class MultiCommunityScreen(
private val community: MultiCommunityModel, private val communityId: Int,
) : Screen { ) : Screen {
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<MultiCommunityMviModel>() val model = getScreenModel<MultiCommunityMviModel>(parameters = {
parametersOf(communityId)
})
model.bindToLifecycle(key) model.bindToLifecycle(key)
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()
val topAppBarState = rememberTopAppBarState() val topAppBarState = rememberTopAppBarState()
@ -136,7 +137,7 @@ class MultiCommunityScreen(
}, },
title = { title = {
Text( Text(
text = community.name, text = uiState.community.name,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
@ -302,13 +303,9 @@ class MultiCommunityScreen(
model.reduce(MultiCommunityMviModel.Intent.UpVotePost(post.id)) model.reduce(MultiCommunityMviModel.Intent.UpVotePost(post.id))
}, },
onSecondDismissToStart = rememberCallback(model) { onSecondDismissToStart = rememberCallback(model) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = post,
val screen = CreateCommentScreen( )
originalPost = post,
)
showBottomSheet(screen)
}
}, },
onDismissToEnd = { onDismissToEnd = {
model.reduce(MultiCommunityMviModel.Intent.DownVotePost(post.id)) model.reduce(MultiCommunityMviModel.Intent.DownVotePost(post.id))

View File

@ -6,11 +6,11 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterEvent
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.MultiCommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.core.utils.imagepreload.ImagePreloadManager import com.github.diegoberaldin.raccoonforlemmy.core.utils.imagepreload.ImagePreloadManager
import com.github.diegoberaldin.raccoonforlemmy.core.utils.share.ShareHelper import com.github.diegoberaldin.raccoonforlemmy.core.utils.share.ShareHelper
import com.github.diegoberaldin.raccoonforlemmy.core.utils.vibrate.HapticFeedback import com.github.diegoberaldin.raccoonforlemmy.core.utils.vibrate.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
@ -25,13 +25,14 @@ import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MultiCommunityViewModel( class MultiCommunityViewModel(
private val mvi: DefaultMviModel<MultiCommunityMviModel.Intent, MultiCommunityMviModel.UiState, MultiCommunityMviModel.Effect>, private val mvi: DefaultMviModel<MultiCommunityMviModel.Intent, MultiCommunityMviModel.UiState, MultiCommunityMviModel.Effect>,
private val community: MultiCommunityModel, private val communityId: Int,
private val postRepository: PostRepository, private val postRepository: PostRepository,
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val apiConfigurationRepository: ApiConfigurationRepository, private val multiCommunityRepository: MultiCommunityRepository,
private val siteRepository: SiteRepository, private val siteRepository: SiteRepository,
private val themeRepository: ThemeRepository, private val themeRepository: ThemeRepository,
private val shareHelper: ShareHelper, private val shareHelper: ShareHelper,
@ -49,6 +50,11 @@ class MultiCommunityViewModel(
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
mvi.scope?.launch { mvi.scope?.launch {
if ((uiState.value.community.id ?: 0) == 0L) {
val community =
multiCommunityRepository.getById(communityId.toLong()) ?: MultiCommunityModel()
mvi.updateState { it.copy(community = community) }
}
themeRepository.postLayout.onEach { layout -> themeRepository.postLayout.onEach { layout ->
mvi.updateState { it.copy(postLayout = layout) } mvi.updateState { it.copy(postLayout = layout) }
}.launchIn(this) }.launchIn(this)
@ -80,20 +86,19 @@ class MultiCommunityViewModel(
val user = siteRepository.getCurrentUser(auth) val user = siteRepository.getCurrentUser(auth)
mvi.updateState { it.copy(currentUserId = user?.id ?: 0) } mvi.updateState { it.copy(currentUserId = user?.id ?: 0) }
} }
} withContext(Dispatchers.IO) {
if (uiState.value.posts.isEmpty()) {
mvi.scope?.launch(Dispatchers.IO) { val settings = settingsRepository.currentSettings.value
if (uiState.value.posts.isEmpty()) { val sortTypes = getSortTypesUseCase.getTypesForPosts()
val settings = settingsRepository.currentSettings.value mvi.updateState {
val sortTypes = getSortTypesUseCase.getTypesForPosts() it.copy(
mvi.updateState { sortType = settings.defaultPostSortType.toSortType(),
it.copy( availableSortTypes = sortTypes,
sortType = settings.defaultPostSortType.toSortType(), )
availableSortTypes = sortTypes, }
) paginator.setCommunities(uiState.value.community.communityIds)
refresh()
} }
paginator.setCommunities(community.communityIds)
refresh()
} }
} }
} }

View File

@ -13,10 +13,9 @@ val multiCommunityModule = module {
factory<MultiCommunityMviModel> { params -> factory<MultiCommunityMviModel> { params ->
MultiCommunityViewModel( MultiCommunityViewModel(
mvi = DefaultMviModel(MultiCommunityMviModel.UiState()), mvi = DefaultMviModel(MultiCommunityMviModel.UiState()),
community = params[0], communityId = params[0],
postRepository = get(), postRepository = get(),
identityRepository = get(), identityRepository = get(),
apiConfigurationRepository = get(),
siteRepository = get(), siteRepository = get(),
themeRepository = get(), themeRepository = get(),
shareHelper = get(), shareHelper = get(),
@ -26,6 +25,7 @@ val multiCommunityModule = module {
paginator = get(), paginator = get(),
imagePreloadManager = get(), imagePreloadManager = get(),
getSortTypesUseCase = get(), getSortTypesUseCase = get(),
multiCommunityRepository = get(),
) )
} }
factory<MultiCommunityPaginator> { factory<MultiCommunityPaginator> {
@ -36,7 +36,7 @@ val multiCommunityModule = module {
factory<MultiCommunityEditorMviModel> { params -> factory<MultiCommunityEditorMviModel> { params ->
MultiCommunityEditorViewModel( MultiCommunityEditorViewModel(
mvi = DefaultMviModel(MultiCommunityEditorMviModel.UiState()), mvi = DefaultMviModel(MultiCommunityEditorMviModel.UiState()),
editedCommunity = params[0], communityId = params[0],
identityRepository = get(), identityRepository = get(),
communityRepository = get(), communityRepository = get(),
accountRepository = get(), accountRepository = get(),

View File

@ -60,7 +60,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycl
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomImage import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CustomImage
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CommunityItem import com.github.diegoberaldin.raccoonforlemmy.core.commonui.lemmyui.CommunityItem
import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigationCoordinator
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.data.MultiCommunityModel
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick 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.rememberCallback
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
@ -71,13 +70,13 @@ import kotlinx.coroutines.flow.onEach
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class MultiCommunityEditorScreen( class MultiCommunityEditorScreen(
private val editedCommunity: MultiCommunityModel? = null, private val communityId: Int? = null,
) : Screen { ) : Screen {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<MultiCommunityEditorMviModel> { parametersOf(editedCommunity) } val model = getScreenModel<MultiCommunityEditorMviModel> { parametersOf(communityId) }
model.bindToLifecycle(key) model.bindToLifecycle(key)
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()
val navigationCoordinator = remember { getNavigationCoordinator() } val navigationCoordinator = remember { getNavigationCoordinator() }

View File

@ -23,7 +23,7 @@ import kotlinx.coroutines.launch
class MultiCommunityEditorViewModel( class MultiCommunityEditorViewModel(
private val mvi: DefaultMviModel<MultiCommunityEditorMviModel.Intent, MultiCommunityEditorMviModel.UiState, MultiCommunityEditorMviModel.Effect>, private val mvi: DefaultMviModel<MultiCommunityEditorMviModel.Intent, MultiCommunityEditorMviModel.UiState, MultiCommunityEditorMviModel.Effect>,
private val editedCommunity: MultiCommunityModel? = null, private val communityId: Int?,
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val communityRepository: CommunityRepository, private val communityRepository: CommunityRepository,
private val multiCommunityRepository: MultiCommunityRepository, private val multiCommunityRepository: MultiCommunityRepository,
@ -60,6 +60,9 @@ class MultiCommunityEditorViewModel(
private fun populate() { private fun populate() {
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
val editedCommunity = communityId?.toLong()?.let {
multiCommunityRepository.getById(it)
}
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
communities = communityRepository.getSubscribed(auth).sortedBy { it.name }.map { c -> communities = communityRepository.getSubscribed(auth).sortedBy { it.name }.map { c ->
c to (editedCommunity?.communityIds?.contains(c.id) == true) c to (editedCommunity?.communityIds?.contains(c.id) == true)
@ -145,19 +148,21 @@ class MultiCommunityEditorViewModel(
return return
} }
val icon = currentState.icon
val communityIds = currentState.communities.filter { it.second }.map { it.first.id }
val multiCommunity = editedCommunity?.copy(
name = name,
icon = icon,
communityIds = communityIds,
) ?: MultiCommunityModel(
name = name,
icon = icon,
communityIds = communityIds,
)
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
val icon = currentState.icon
val communityIds = currentState.communities.filter { it.second }.map { it.first.id }
val editedCommunity = communityId?.toLong()?.let {
multiCommunityRepository.getById(it)
}
val multiCommunity = editedCommunity?.copy(
name = name,
icon = icon,
communityIds = communityIds,
) ?: MultiCommunityModel(
name = name,
icon = icon,
communityIds = communityIds,
)
val accountId = accountRepository.getActive()?.id ?: return@launch val accountId = accountRepository.getActive()?.id ?: return@launch
if (multiCommunity.id == null) { if (multiCommunity.id == null) {
val id = multiCommunityRepository.create(multiCommunity, accountId) val id = multiCommunityRepository.create(multiCommunity, accountId)

View File

@ -58,8 +58,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.CreatePostScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen import com.github.diegoberaldin.raccoonforlemmy.unit.zoomableimage.ZoomableImageScreen
@ -259,14 +257,9 @@ object ProfileLoggedScreen : Tab {
) )
OptionId.Edit -> { OptionId.Edit -> {
with(navigationCoordinator) { detailOpener.openCreatePost(
setBottomSheetGesturesEnabled(false) editedPost = post,
showBottomSheet( )
CreatePostScreen(
editedPost = post,
)
)
}
} }
OptionId.SeeRaw -> { OptionId.SeeRaw -> {
@ -327,8 +320,7 @@ object ProfileLoggedScreen : Tab {
items = uiState.comments, items = uiState.comments,
key = { it.id.toString() + it.updateDate }, key = { it.id.toString() + it.updateDate },
) { comment -> ) { comment ->
CommentCard( CommentCard(modifier = Modifier.background(MaterialTheme.colorScheme.background),
modifier = Modifier.background(MaterialTheme.colorScheme.background),
comment = comment, comment = comment,
voteFormat = uiState.voteFormat, voteFormat = uiState.voteFormat,
autoLoadImages = uiState.autoLoadImages, autoLoadImages = uiState.autoLoadImages,
@ -396,12 +388,9 @@ object ProfileLoggedScreen : Tab {
} }
OptionId.Edit -> { OptionId.Edit -> {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) editedComment = comment,
showBottomSheet( )
CreateCommentScreen(editedComment = comment)
)
}
} }
OptionId.SeeRaw -> { OptionId.SeeRaw -> {
@ -410,8 +399,7 @@ object ProfileLoggedScreen : Tab {
else -> Unit else -> Unit
} }
} })
)
Divider( Divider(
modifier = Modifier.padding(vertical = Spacing.xxxs), modifier = Modifier.padding(vertical = Spacing.xxxs),
thickness = 0.25.dp thickness = 0.25.dp
@ -466,8 +454,7 @@ object ProfileLoggedScreen : Tab {
if (rawContent != null) { if (rawContent != null) {
when (val content = rawContent) { when (val content = rawContent) {
is PostModel -> { is PostModel -> {
RawContentDialog( RawContentDialog(title = content.title,
title = content.title,
publishDate = content.publishDate, publishDate = content.publishDate,
updateDate = content.updateDate, updateDate = content.updateDate,
url = content.url, url = content.url,
@ -478,27 +465,20 @@ object ProfileLoggedScreen : Tab {
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = content,
val screen = initialText = buildString {
CreateCommentScreen( append("> ")
originalPost = content, append(quotation)
initialText = buildString { append("\n\n")
append("> ") },
append(quotation) )
append("\n\n")
}
)
showBottomSheet(screen)
}
} }
} })
)
} }
is CommentModel -> { is CommentModel -> {
RawContentDialog( RawContentDialog(text = content.text,
text = content.text,
publishDate = content.publishDate, publishDate = content.publishDate,
updateDate = content.updateDate, updateDate = content.updateDate,
onDismiss = { onDismiss = {
@ -507,21 +487,16 @@ object ProfileLoggedScreen : Tab {
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalComment = content,
val screen = CreateCommentScreen( initialText = buildString {
originalComment = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
}
)
showBottomSheet(screen)
}
} }
} })
)
} }
} }
} }

View File

@ -100,8 +100,6 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.containsId
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.ban.BanUserScreen import com.github.diegoberaldin.raccoonforlemmy.unit.ban.BanUserScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.CreatePostScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog
import com.github.diegoberaldin.raccoonforlemmy.unit.remove.RemoveScreen import com.github.diegoberaldin.raccoonforlemmy.unit.remove.RemoveScreen
@ -114,14 +112,14 @@ import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class PostDetailScreen( class PostDetailScreen(
private val post: PostModel, private val postId: Int,
private val otherInstance: String = "", private val otherInstance: String = "",
private val highlightCommentId: Int? = null, private val highlightCommentId: Int? = null,
private val isMod: Boolean = false, private val isMod: Boolean = false,
) : Screen { ) : Screen {
override val key: ScreenKey override val key: ScreenKey
get() = super.key + post.id.toString() get() = super.key + postId.toString()
@OptIn( @OptIn(
ExperimentalMaterial3Api::class, ExperimentalMaterial3Api::class,
@ -131,17 +129,16 @@ class PostDetailScreen(
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<PostDetailMviModel>( val model = getScreenModel<PostDetailMviModel>(
tag = post.id.toString() + highlightCommentId.toString(), tag = postId.toString() + highlightCommentId.toString(),
parameters = { parameters = {
parametersOf( parametersOf(
post, postId,
otherInstance, otherInstance,
highlightCommentId, highlightCommentId,
isMod, isMod,
) )
} })
) model.bindToLifecycle(key + postId.toString())
model.bindToLifecycle(key + post.id.toString())
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()
val isOnOtherInstance = remember { otherInstance.isNotEmpty() } val isOnOtherInstance = remember { otherInstance.isNotEmpty() }
val otherInstanceName = remember { otherInstance } val otherInstanceName = remember { otherInstance }
@ -169,9 +166,6 @@ class PostDetailScreen(
LaunchedEffect(notificationCenter) { LaunchedEffect(notificationCenter) {
notificationCenter.resetCache() notificationCenter.resetCache()
} }
LaunchedEffect(navigationCoordinator) {
navigationCoordinator.setBottomSheetGesturesEnabled(true)
}
LaunchedEffect(model) { LaunchedEffect(model) {
model.effects.onEach { evt -> model.effects.onEach { evt ->
when (evt) { when (evt) {
@ -193,8 +187,7 @@ class PostDetailScreen(
} }
Scaffold( Scaffold(
modifier = Modifier modifier = Modifier.background(MaterialTheme.colorScheme.background)
.background(MaterialTheme.colorScheme.background)
.padding(Spacing.xs), .padding(Spacing.xs),
topBar = { topBar = {
TopAppBar( TopAppBar(
@ -268,13 +261,9 @@ class PostDetailScreen(
icon = Icons.Default.Reply, icon = Icons.Default.Reply,
text = stringResource(MR.strings.action_reply), text = stringResource(MR.strings.action_reply),
onSelected = rememberCallback { onSelected = rememberCallback {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = uiState.post,
val screen = CreateCommentScreen( )
originalPost = uiState.post,
)
showBottomSheet(screen)
}
}, },
) )
} }
@ -297,8 +286,7 @@ class PostDetailScreen(
} else { } else {
it it
} }
}.nestedScroll(fabNestedScrollConnection) }.nestedScroll(fabNestedScrollConnection).pullRefresh(pullRefreshState),
.pullRefresh(pullRefreshState),
) { ) {
LazyColumn( LazyColumn(
state = lazyListState state = lazyListState
@ -357,13 +345,9 @@ class PostDetailScreen(
}, },
onReply = rememberCallback { onReply = rememberCallback {
if (uiState.isLogged && !isOnOtherInstance) { if (uiState.isLogged && !isOnOtherInstance) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = uiState.post,
val screen = CreateCommentScreen( )
originalPost = uiState.post,
)
showBottomSheet(screen)
}
} }
}, },
options = buildList { options = buildList {
@ -441,12 +425,7 @@ class PostDetailScreen(
OptionId.Delete -> model.reduce(PostDetailMviModel.Intent.DeletePost) OptionId.Delete -> model.reduce(PostDetailMviModel.Intent.DeletePost)
OptionId.Edit -> { OptionId.Edit -> {
with(navigationCoordinator) { detailOpener.openCreatePost(editedPost = uiState.post)
setBottomSheetGesturesEnabled(false)
showBottomSheet(
CreatePostScreen(editedPost = uiState.post),
)
}
} }
OptionId.Report -> { OptionId.Report -> {
@ -456,12 +435,7 @@ class PostDetailScreen(
} }
OptionId.CrossPost -> { OptionId.CrossPost -> {
with(navigationCoordinator) { detailOpener.openCreatePost(crossPost = uiState.post)
setBottomSheetGesturesEnabled(false)
showBottomSheet(
CreatePostScreen(crossPost = uiState.post),
)
}
} }
OptionId.SeeRaw -> { OptionId.SeeRaw -> {
@ -649,14 +623,10 @@ class PostDetailScreen(
) )
}, },
onSecondDismissToStart = rememberCallback(model) { onSecondDismissToStart = rememberCallback(model) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = uiState.post,
val screen = CreateCommentScreen( originalComment = comment,
originalPost = uiState.post, )
originalComment = comment,
)
showBottomSheet(screen)
}
}, },
onDismissToEnd = rememberCallback(model) { onDismissToEnd = rememberCallback(model) {
model.reduce( model.reduce(
@ -762,14 +732,10 @@ class PostDetailScreen(
}, },
onReply = rememberCallback { onReply = rememberCallback {
if (uiState.isLogged && !isOnOtherInstance) { if (uiState.isLogged && !isOnOtherInstance) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = uiState.post,
val screen = CreateCommentScreen( originalComment = comment,
originalPost = uiState.post, )
originalComment = comment,
)
showBottomSheet(screen)
}
} }
}, },
onOpenCreator = rememberCallbackArgs { user, instance -> onOpenCreator = rememberCallbackArgs { user, instance ->
@ -777,8 +743,7 @@ class PostDetailScreen(
}, },
onOpenCommunity = rememberCallbackArgs { community, instance -> onOpenCommunity = rememberCallbackArgs { community, instance ->
detailOpener.openCommunityDetail( detailOpener.openCommunityDetail(
community, community, instance
instance
) )
}, },
onOpenPost = rememberCallbackArgs { p, instance -> onOpenPost = rememberCallbackArgs { p, instance ->
@ -862,16 +827,9 @@ class PostDetailScreen(
) )
OptionId.Edit -> { OptionId.Edit -> {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled( editedComment = comment,
false, )
)
showBottomSheet(
CreateCommentScreen(
editedComment = comment,
),
)
}
} }
OptionId.Report -> { OptionId.Report -> {
@ -979,14 +937,10 @@ class PostDetailScreen(
}, },
onReply = rememberCallback(model) { onReply = rememberCallback(model) {
if (uiState.isLogged && !isOnOtherInstance) { if (uiState.isLogged && !isOnOtherInstance) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = uiState.post,
val screen = CreateCommentScreen( originalComment = comment,
originalPost = uiState.post, )
originalComment = comment,
)
showBottomSheet(screen)
}
} }
}, },
onOpenCreator = rememberCallbackArgs { user -> onOpenCreator = rememberCallbackArgs { user ->
@ -1058,14 +1012,9 @@ class PostDetailScreen(
) )
OptionId.Edit -> { OptionId.Edit -> {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) editedComment = comment,
showBottomSheet( )
CreateCommentScreen(
editedComment = comment,
),
)
}
} }
OptionId.Report -> { OptionId.Report -> {
@ -1174,8 +1123,7 @@ class PostDetailScreen(
Column { Column {
if (uiState.post.comments == 0) { if (uiState.post.comments == 0) {
Text( Text(
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
.padding(top = Spacing.xs), .padding(top = Spacing.xs),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
text = stringResource(MR.strings.message_empty_comments), text = stringResource(MR.strings.message_empty_comments),
@ -1184,8 +1132,7 @@ class PostDetailScreen(
) )
} else { } else {
Text( Text(
modifier = Modifier modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
.padding(top = Spacing.xs), .padding(top = Spacing.xs),
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
text = stringResource(MR.strings.message_error_loading_comments), text = stringResource(MR.strings.message_error_loading_comments),
@ -1240,18 +1187,14 @@ class PostDetailScreen(
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = content,
val screen = CreateCommentScreen( initialText = buildString {
originalPost = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
},
)
showBottomSheet(screen)
}
} }
}, },
) )
@ -1268,19 +1211,15 @@ class PostDetailScreen(
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = uiState.post,
val screen = CreateCommentScreen( originalComment = content,
originalPost = uiState.post, initialText = buildString {
originalComment = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
},
)
showBottomSheet(screen)
}
} }
}, },
) )

View File

@ -18,6 +18,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommunityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -28,7 +29,7 @@ import kotlinx.coroutines.launch
class PostDetailViewModel( class PostDetailViewModel(
private val mvi: DefaultMviModel<PostDetailMviModel.Intent, PostDetailMviModel.UiState, PostDetailMviModel.Effect>, private val mvi: DefaultMviModel<PostDetailMviModel.Intent, PostDetailMviModel.UiState, PostDetailMviModel.Effect>,
private val post: PostModel, private val postId: Int,
private val otherInstance: String, private val otherInstance: String,
private val highlightCommentId: Int?, private val highlightCommentId: Int?,
private val isModerator: Boolean, private val isModerator: Boolean,
@ -44,6 +45,7 @@ class PostDetailViewModel(
private val notificationCenter: NotificationCenter, private val notificationCenter: NotificationCenter,
private val hapticFeedback: HapticFeedback, private val hapticFeedback: HapticFeedback,
private val getSortTypesUseCase: GetSortTypesUseCase, private val getSortTypesUseCase: GetSortTypesUseCase,
private val itemCache: LemmyItemCache,
) : PostDetailMviModel, ) : PostDetailMviModel,
MviModel<PostDetailMviModel.Intent, PostDetailMviModel.UiState, PostDetailMviModel.Effect> by mvi { MviModel<PostDetailMviModel.Intent, PostDetailMviModel.UiState, PostDetailMviModel.Effect> by mvi {
@ -60,6 +62,15 @@ class PostDetailViewModel(
) )
} }
mvi.scope?.launch { mvi.scope?.launch {
if (uiState.value.post.id == 0) {
val post = itemCache.getPost(postId) ?: PostModel()
mvi.updateState {
it.copy(
post = post,
isModerator = isModerator,
)
}
}
notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class).onEach { evt -> notificationCenter.subscribe(NotificationCenterEvent.PostUpdated::class).onEach { evt ->
handlePostUpdate(evt.model) handlePostUpdate(evt.model)
}.launchIn(this) }.launchIn(this)
@ -121,12 +132,11 @@ class PostDetailViewModel(
notificationCenter.subscribe(NotificationCenterEvent.Share::class).onEach { evt -> notificationCenter.subscribe(NotificationCenterEvent.Share::class).onEach { evt ->
shareHelper.share(evt.url) shareHelper.share(evt.url)
}.launchIn(this) }.launchIn(this)
}
mvi.scope?.launch(Dispatchers.IO) {
identityRepository.isLogged.onEach { logged -> identityRepository.isLogged.onEach { logged ->
mvi.updateState { it.copy(isLogged = logged ?: false) } mvi.updateState { it.copy(isLogged = logged ?: false) }
}.launchIn(this) }.launchIn(this)
if (uiState.value.currentUserId == null) { if (uiState.value.currentUserId == null) {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
val user = siteRepository.getCurrentUser(auth) val user = siteRepository.getCurrentUser(auth)
@ -135,16 +145,9 @@ class PostDetailViewModel(
} }
} }
mvi.updateState {
it.copy(
post = post,
isModerator = isModerator,
)
}
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
val updatedPost = postRepository.get( val updatedPost = postRepository.get(
id = post.id, id = postId,
auth = auth, auth = auth,
instance = otherInstance, instance = otherInstance,
) )
@ -163,7 +166,7 @@ class PostDetailViewModel(
highlightCommentPath = comment?.path highlightCommentPath = comment?.path
} }
if (isModerator) { if (isModerator) {
post.community?.id?.also { communityId -> uiState.value.post.community?.id?.also { communityId ->
val moderators = communityRepository.getModerators( val moderators = communityRepository.getModerators(
auth = auth, auth = auth,
id = communityId id = communityId
@ -174,7 +177,7 @@ class PostDetailViewModel(
} }
} }
if (post.text.isEmpty() && post.title.isEmpty()) { if (uiState.value.post.text.isEmpty() && uiState.value.post.title.isEmpty()) {
refreshPost() refreshPost()
} }
if (mvi.uiState.value.comments.isEmpty()) { if (mvi.uiState.value.comments.isEmpty()) {
@ -315,10 +318,10 @@ class PostDetailViewModel(
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
val updatedPost = postRepository.get( val updatedPost = postRepository.get(
id = post.id, id = postId,
auth = auth, auth = auth,
instance = otherInstance, instance = otherInstance,
) ?: post ) ?: uiState.value.post
mvi.updateState { mvi.updateState {
it.copy(post = updatedPost) it.copy(post = updatedPost)
} }
@ -345,7 +348,7 @@ class PostDetailViewModel(
val sort = currentState.sortType val sort = currentState.sortType
val itemList = commentRepository.getAll( val itemList = commentRepository.getAll(
auth = auth, auth = auth,
postId = post.id, postId = postId,
instance = otherInstance, instance = otherInstance,
page = currentPage, page = currentPage,
sort = sort, sort = sort,
@ -633,9 +636,9 @@ class PostDetailViewModel(
private fun deletePost() { private fun deletePost() {
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
postRepository.delete(id = post.id, auth = auth) postRepository.delete(id = postId, auth = auth)
notificationCenter.send( notificationCenter.send(
event = NotificationCenterEvent.PostDeleted(post), event = NotificationCenterEvent.PostDeleted(uiState.value.post),
) )
mvi.emitEffect(PostDetailMviModel.Effect.Close) mvi.emitEffect(PostDetailMviModel.Effect.Close)
} }
@ -719,6 +722,7 @@ class PostDetailViewModel(
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
val isModerator = uiState.value.moderators.containsId(userId) val isModerator = uiState.value.moderators.containsId(userId)
val auth = identityRepository.authToken.value.orEmpty() val auth = identityRepository.authToken.value.orEmpty()
val post = uiState.value.post
val communityId = post.community?.id val communityId = post.community?.id
if (communityId != null) { if (communityId != null) {
val newModerators = communityRepository.addModerator( val newModerators = communityRepository.addModerator(

View File

@ -9,7 +9,7 @@ val postDetailModule = module {
factory<PostDetailMviModel> { params -> factory<PostDetailMviModel> { params ->
PostDetailViewModel( PostDetailViewModel(
mvi = DefaultMviModel(PostDetailMviModel.UiState()), mvi = DefaultMviModel(PostDetailMviModel.UiState()),
post = params[0], postId = params[0],
otherInstance = params[1], otherInstance = params[1],
highlightCommentId = params[2], highlightCommentId = params[2],
isModerator = params[3], isModerator = params[3],
@ -25,6 +25,7 @@ val postDetailModule = module {
notificationCenter = get(), notificationCenter = get(),
hapticFeedback = get(), hapticFeedback = get(),
getSortTypesUseCase = get(), getSortTypesUseCase = get(),
itemCache = get(),
) )
} }
} }

View File

@ -80,8 +80,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.keepscreenon.rememberKeepScreenOn import com.github.diegoberaldin.raccoonforlemmy.core.utils.keepscreenon.rememberKeepScreenOn
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.CreatePostScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
@ -345,13 +343,9 @@ class PostListScreen : Screen {
model.reduce(PostListMviModel.Intent.UpVotePost(post.id)) model.reduce(PostListMviModel.Intent.UpVotePost(post.id))
}, },
onSecondDismissToStart = rememberCallback(model) { onSecondDismissToStart = rememberCallback(model) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = post,
val screen = CreateCommentScreen( )
originalPost = post,
)
showBottomSheet(screen)
}
}, },
onDismissToEnd = rememberCallback(model) { onDismissToEnd = rememberCallback(model) {
model.reduce(PostListMviModel.Intent.DownVotePost(post.id)) model.reduce(PostListMviModel.Intent.DownVotePost(post.id))
@ -518,12 +512,7 @@ class PostListScreen : Screen {
) )
OptionId.Edit -> { OptionId.Edit -> {
with(navigationCoordinator) { detailOpener.openCreatePost(editedPost = post)
setBottomSheetGesturesEnabled(false)
showBottomSheet(
CreatePostScreen(editedPost = post),
)
}
} }
OptionId.Report -> { OptionId.Report -> {
@ -533,12 +522,7 @@ class PostListScreen : Screen {
} }
OptionId.CrossPost -> { OptionId.CrossPost -> {
with(navigationCoordinator) { detailOpener.openCreatePost(crossPost = post)
setBottomSheetGesturesEnabled(false)
showBottomSheet(
CreatePostScreen(crossPost = post),
)
}
} }
OptionId.SeeRaw -> { OptionId.SeeRaw -> {
@ -668,19 +652,14 @@ class PostListScreen : Screen {
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = content,
val screen = initialText = buildString {
CreateCommentScreen( append("> ")
originalPost = content, append(quotation)
initialText = buildString { append("\n\n")
append("> ") },
append(quotation) )
append("\n\n")
},
)
showBottomSheet(screen)
}
} }
} }
) )

View File

@ -69,7 +69,6 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
@ -373,14 +372,11 @@ class SavedItemsScreen : Screen {
) )
}, },
onReply = { onReply = {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = PostModel(id = comment.postId),
val screen = CreateCommentScreen( originalComment = comment,
originalPost = PostModel(id = comment.postId), )
originalComment = comment,
)
showBottomSheet(screen)
}
}, },
options = buildList { options = buildList {
add( add(
@ -475,18 +471,14 @@ class SavedItemsScreen : Screen {
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = content,
val screen = CreateCommentScreen( initialText = buildString {
originalPost = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
},
)
showBottomSheet(screen)
}
} }
}, },
) )
@ -503,18 +495,14 @@ class SavedItemsScreen : Screen {
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalComment = content,
val screen = CreateCommentScreen( initialText = buildString {
originalComment = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
},
)
showBottomSheet(screen)
}
} }
} }
) )

View File

@ -100,12 +100,9 @@ import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallb
import com.github.diegoberaldin.raccoonforlemmy.core.utils.toLocalDp 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.CommentModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toIcon
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.chat.InboxChatScreen import com.github.diegoberaldin.raccoonforlemmy.unit.chat.InboxChatScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createcomment.CreateCommentScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createpost.CreatePostScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.unit.createreport.CreateReportScreen
import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog import com.github.diegoberaldin.raccoonforlemmy.unit.rawcontent.RawContentDialog
import com.github.diegoberaldin.raccoonforlemmy.unit.userinfo.UserInfoScreen import com.github.diegoberaldin.raccoonforlemmy.unit.userinfo.UserInfoScreen
@ -118,21 +115,19 @@ import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class UserDetailScreen( class UserDetailScreen(
private val user: UserModel, private val userId: Int,
private val otherInstance: String = "", private val otherInstance: String = "",
) : Screen { ) : Screen {
override val key: ScreenKey override val key: ScreenKey
get() = super.key + user.id.toString() get() = super.key + userId.toString()
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<UserDetailMviModel>( val model = getScreenModel<UserDetailMviModel>(tag = userId.toString(),
tag = user.id.toString(), parameters = { parametersOf(userId, otherInstance) })
parameters = { parametersOf(user, otherInstance) } model.bindToLifecycle(key + userId.toString())
)
model.bindToLifecycle(key + user.id.toString())
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -181,194 +176,185 @@ class UserDetailScreen(
}.launchIn(this) }.launchIn(this)
} }
Scaffold( Scaffold(modifier = Modifier.background(MaterialTheme.colorScheme.background)
modifier = Modifier .padding(Spacing.xs), topBar = {
.background(MaterialTheme.colorScheme.background) val userName = uiState.user.name
.padding(Spacing.xs), val userHost = uiState.user.host
topBar = { val maxTopInset = Dimensions.topBarHeight.value.toInt()
val userName = user.name var topInset by remember { mutableStateOf(maxTopInset) }
val userHost = user.host snapshotFlow { topAppBarState.collapsedFraction }.onEach {
val maxTopInset = Dimensions.topBarHeight.value.toInt() topInset = (maxTopInset * (1 - it)).toInt()
var topInset by remember { mutableStateOf(maxTopInset) } }.launchIn(scope)
snapshotFlow { topAppBarState.collapsedFraction }.onEach {
topInset = (maxTopInset * (1 - it)).toInt()
}.launchIn(scope)
TopAppBar( TopAppBar(
windowInsets = if (settings.edgeToEdge) { windowInsets = if (settings.edgeToEdge) {
WindowInsets(0, topInset, 0, 0) WindowInsets(0, topInset, 0, 0)
} else { } else {
TopAppBarDefaults.windowInsets TopAppBarDefaults.windowInsets
}, },
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
title = { title = {
Text( Text(
modifier = Modifier.padding(horizontal = Spacing.s), modifier = Modifier.padding(horizontal = Spacing.s),
text = buildString { text = buildString {
append(userName) append(userName)
if (userHost.isNotEmpty()) { if (userHost.isNotEmpty()) {
append("@$userHost") append("@$userHost")
} }
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
actions = {
// sort button
Image(
modifier = Modifier.onClick(
onClick = rememberCallback {
val sheet = SortBottomSheet(
sheetKey = key,
values = uiState.availableSortTypes,
comments = false,
expandTop = true,
)
navigationCoordinator.showBottomSheet(sheet)
}, },
maxLines = 1, ),
overflow = TextOverflow.Ellipsis, imageVector = uiState.sortType.toIcon(),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
)
// options menu
Box {
val options = listOf(
Option(
OptionId.Info, stringResource(MR.strings.user_detail_info)
),
Option(
OptionId.Block,
stringResource(MR.strings.community_detail_block)
),
Option(
OptionId.BlockInstance,
stringResource(MR.strings.community_detail_block_instance)
),
) )
}, var optionsExpanded by remember { mutableStateOf(false) }
actions = { var optionsOffset by remember { mutableStateOf(Offset.Zero) }
// sort button
Image( Image(
modifier = Modifier.onClick( modifier = Modifier.onGloballyPositioned {
optionsOffset = it.positionInParent()
}.padding(start = Spacing.s).onClick(
onClick = rememberCallback { onClick = rememberCallback {
val sheet = SortBottomSheet( optionsExpanded = true
sheetKey = key,
values = uiState.availableSortTypes,
comments = false,
expandTop = true,
)
navigationCoordinator.showBottomSheet(sheet)
}, },
), ),
imageVector = uiState.sortType.toIcon(), imageVector = Icons.Default.MoreVert,
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground), colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
) )
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
when (option.id) {
OptionId.BlockInstance -> model.reduce(
UserDetailMviModel.Intent.BlockInstance
)
// options menu OptionId.Block -> model.reduce(
Box { UserDetailMviModel.Intent.Block
val options = listOf( )
Option(
OptionId.Info, OptionId.Info -> {
stringResource(MR.strings.user_detail_info) navigationCoordinator.showBottomSheet(
), UserInfoScreen(uiState.user.id),
Option(
OptionId.Block,
stringResource(MR.strings.community_detail_block)
),
Option(
OptionId.BlockInstance,
stringResource(MR.strings.community_detail_block_instance)
),
)
var optionsExpanded by remember { mutableStateOf(false) }
var optionsOffset by remember { mutableStateOf(Offset.Zero) }
Image(
modifier = Modifier.onGloballyPositioned {
optionsOffset = it.positionInParent()
}.padding(start = Spacing.s).onClick(
onClick = rememberCallback {
optionsExpanded = true
},
),
imageVector = Icons.Default.MoreVert,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
)
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
when (option.id) {
OptionId.BlockInstance -> model.reduce(
UserDetailMviModel.Intent.BlockInstance
) )
OptionId.Block -> model.reduce(
UserDetailMviModel.Intent.Block
)
OptionId.Info -> {
navigationCoordinator.showBottomSheet(
UserInfoScreen(uiState.user),
)
}
else -> Unit
} }
},
), else -> Unit
text = option.text, }
) },
} ),
} text = option.text,
}
},
navigationIcon = {
if (navigationCoordinator.canPop.value) {
Image(
modifier = Modifier.onClick(
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
)
}
},
)
},
floatingActionButton = {
AnimatedVisibility(
visible = isFabVisible,
enter = slideInVertically(
initialOffsetY = { it * 2 },
),
exit = slideOutVertically(
targetOffsetY = { it * 2 },
),
) {
FloatingActionButtonMenu(
items = buildList {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ExpandLess,
text = stringResource(MR.strings.action_back_to_top),
onSelected = rememberCallback {
scope.launch {
lazyListState.scrollToItem(0)
topAppBarState.heightOffset = 0f
topAppBarState.contentOffset = 0f
}
},
)
if (uiState.isLogged && !isOnOtherInstance) {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.Chat,
text = stringResource(MR.strings.action_chat),
onSelected = rememberCallback {
val screen = InboxChatScreen(otherUserId = user.id)
navigationCoordinator.pushScreen(screen)
},
) )
} }
} }
}
},
navigationIcon = {
if (navigationCoordinator.canPop.value) {
Image(
modifier = Modifier.onClick(
onClick = rememberCallback {
navigationCoordinator.popScreen()
},
),
imageVector = Icons.Default.ArrowBack,
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onBackground),
)
}
},
)
}, floatingActionButton = {
AnimatedVisibility(
visible = isFabVisible,
enter = slideInVertically(
initialOffsetY = { it * 2 },
),
exit = slideOutVertically(
targetOffsetY = { it * 2 },
),
) {
FloatingActionButtonMenu(items = buildList {
this += FloatingActionButtonMenuItem(
icon = Icons.Default.ExpandLess,
text = stringResource(MR.strings.action_back_to_top),
onSelected = rememberCallback {
scope.launch {
lazyListState.scrollToItem(0)
topAppBarState.heightOffset = 0f
topAppBarState.contentOffset = 0f
}
},
) )
} if (uiState.isLogged && !isOnOtherInstance) {
}, this += FloatingActionButtonMenuItem(
snackbarHost = { icon = Icons.Default.Chat,
SnackbarHost(snackbarHostState) { data -> text = stringResource(MR.strings.action_chat),
Snackbar( onSelected = rememberCallback {
containerColor = MaterialTheme.colorScheme.surfaceVariant, val screen = InboxChatScreen(otherUserId = userId)
contentColor = MaterialTheme.colorScheme.onSurfaceVariant, navigationCoordinator.pushScreen(screen)
snackbarData = data, },
) )
} }
})
} }
) { padding -> }, snackbarHost = {
SnackbarHost(snackbarHostState) { data ->
Snackbar(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant,
snackbarData = data,
)
}
}) { padding ->
val pullRefreshState = rememberPullRefreshState( val pullRefreshState = rememberPullRefreshState(
refreshing = uiState.refreshing, refreshing = uiState.refreshing,
onRefresh = rememberCallback(model) { onRefresh = rememberCallback(model) {
@ -376,17 +362,13 @@ class UserDetailScreen(
}, },
) )
Box( Box(
modifier = Modifier modifier = Modifier.padding(padding).let {
.padding(padding) if (settings.hideNavigationBarWhileScrolling) {
.let { it.nestedScroll(scrollBehavior.nestedScrollConnection)
if (settings.hideNavigationBarWhileScrolling) { } else {
it.nestedScroll(scrollBehavior.nestedScrollConnection) it
} else {
it
}
} }
.nestedScroll(fabNestedScrollConnection) }.nestedScroll(fabNestedScrollConnection).pullRefresh(pullRefreshState),
.pullRefresh(pullRefreshState),
) { ) {
LazyColumn( LazyColumn(
state = lazyListState, state = lazyListState,
@ -506,13 +488,9 @@ class UserDetailScreen(
) )
}, },
onSecondDismissToStart = rememberCallback(model) { onSecondDismissToStart = rememberCallback(model) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = post,
val screen = CreateCommentScreen( )
originalPost = post,
)
showBottomSheet(screen)
}
}, },
onDismissToEnd = rememberCallback(model) { onDismissToEnd = rememberCallback(model) {
model.reduce( model.reduce(
@ -642,12 +620,7 @@ class UserDetailScreen(
} }
OptionId.CrossPost -> { OptionId.CrossPost -> {
with(navigationCoordinator) { detailOpener.openCreatePost(crossPost = post)
setBottomSheetGesturesEnabled(false)
showBottomSheet(
CreatePostScreen(crossPost = post),
)
}
} }
OptionId.SeeRaw -> { OptionId.SeeRaw -> {
@ -773,14 +746,10 @@ class UserDetailScreen(
) )
}, },
onSecondDismissToStart = rememberCallback(model) { onSecondDismissToStart = rememberCallback(model) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = PostModel(id = comment.postId),
val screen = CreateCommentScreen( originalComment = comment,
originalPost = PostModel(id = comment.postId), )
originalComment = comment,
)
showBottomSheet(screen)
}
}, },
onDismissToEnd = rememberCallback(model) { onDismissToEnd = rememberCallback(model) {
model.reduce( model.reduce(
@ -855,14 +824,10 @@ class UserDetailScreen(
null null
} else { } else {
rememberCallback { rememberCallback {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = PostModel(id = comment.postId),
val screen = CreateCommentScreen( originalComment = comment,
originalPost = PostModel(id = comment.postId), )
originalComment = comment,
)
showBottomSheet(screen)
}
} }
}, },
onOpenCommunity = rememberCallbackArgs { community, instance -> onOpenCommunity = rememberCallbackArgs { community, instance ->
@ -982,26 +947,21 @@ class UserDetailScreen(
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalPost = content,
val screen = CreateCommentScreen( initialText = buildString {
originalPost = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
},
)
showBottomSheet(screen)
}
} }
} },
) )
} }
is CommentModel -> { is CommentModel -> {
RawContentDialog( RawContentDialog(text = content.text,
text = content.text,
publishDate = content.publishDate, publishDate = content.publishDate,
updateDate = content.updateDate, updateDate = content.updateDate,
onDismiss = { onDismiss = {
@ -1010,21 +970,16 @@ class UserDetailScreen(
onQuote = rememberCallbackArgs { quotation -> onQuote = rememberCallbackArgs { quotation ->
rawContent = null rawContent = null
if (quotation != null) { if (quotation != null) {
with(navigationCoordinator) { detailOpener.openReply(
setBottomSheetGesturesEnabled(false) originalComment = content,
val screen = CreateCommentScreen( initialText = buildString {
originalComment = content, append("> ")
initialText = buildString { append(quotation)
append("> ") append("\n\n")
append(quotation) },
append("\n\n") )
},
)
showBottomSheet(screen)
}
} }
} })
)
} }
} }
} }

View File

@ -20,6 +20,7 @@ import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.imageUrl
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.toSortType
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.GetSortTypesUseCase
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
@ -34,7 +35,7 @@ import kotlinx.coroutines.withContext
class UserDetailViewModel( class UserDetailViewModel(
private val mvi: DefaultMviModel<UserDetailMviModel.Intent, UserDetailMviModel.UiState, UserDetailMviModel.Effect>, private val mvi: DefaultMviModel<UserDetailMviModel.Intent, UserDetailMviModel.UiState, UserDetailMviModel.Effect>,
private val user: UserModel, private val userId: Int,
private val otherInstance: String = "", private val otherInstance: String = "",
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val apiConfigurationRepository: ApiConfigurationRepository, private val apiConfigurationRepository: ApiConfigurationRepository,
@ -49,6 +50,7 @@ class UserDetailViewModel(
private val notificationCenter: NotificationCenter, private val notificationCenter: NotificationCenter,
private val imagePreloadManager: ImagePreloadManager, private val imagePreloadManager: ImagePreloadManager,
private val getSortTypesUseCase: GetSortTypesUseCase, private val getSortTypesUseCase: GetSortTypesUseCase,
private val itemCache: LemmyItemCache,
) : UserDetailMviModel, ) : UserDetailMviModel,
MviModel<UserDetailMviModel.Intent, UserDetailMviModel.UiState, UserDetailMviModel.Effect> by mvi { MviModel<UserDetailMviModel.Intent, UserDetailMviModel.UiState, UserDetailMviModel.Effect> by mvi {
@ -63,6 +65,12 @@ class UserDetailViewModel(
) )
} }
mvi.scope?.launch { mvi.scope?.launch {
if (uiState.value.user.id == 0) {
val user = itemCache.getUser(userId) ?: UserModel()
mvi.updateState {
it.copy(user = user)
}
}
themeRepository.postLayout.onEach { layout -> themeRepository.postLayout.onEach { layout ->
mvi.updateState { it.copy(postLayout = layout) } mvi.updateState { it.copy(postLayout = layout) }
}.launchIn(this) }.launchIn(this)
@ -77,11 +85,7 @@ class UserDetailViewModel(
shareHelper.share(evt.url) shareHelper.share(evt.url)
}.launchIn(this) }.launchIn(this)
} }
mvi.updateState {
it.copy(
user = it.user.takeIf { u -> u.id != 0 } ?: user,
)
}
mvi.scope?.launch { mvi.scope?.launch {
settingsRepository.currentSettings.onEach { settings -> settingsRepository.currentSettings.onEach { settings ->
mvi.updateState { mvi.updateState {
@ -236,10 +240,10 @@ class UserDetailViewModel(
} }
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
val refreshedUser = userRepository.get( val refreshedUser = userRepository.get(
id = user.id, id = userId,
auth = auth, auth = auth,
otherInstance = otherInstance, otherInstance = otherInstance,
username = user.name, username = uiState.value.user.name,
) )
if (refreshedUser != null) { if (refreshedUser != null) {
mvi.updateState { it.copy(user = refreshedUser) } mvi.updateState { it.copy(user = refreshedUser) }
@ -266,7 +270,7 @@ class UserDetailViewModel(
id = userId, id = userId,
page = currentPage, page = currentPage,
sort = currentState.sortType, sort = currentState.sortType,
username = user.name, username = uiState.value.user.name,
otherInstance = otherInstance, otherInstance = otherInstance,
) )
}.await() }.await()
@ -279,7 +283,7 @@ class UserDetailViewModel(
id = userId, id = userId,
page = currentPage, page = currentPage,
sort = currentState.sortType, sort = currentState.sortType,
username = user.name, username = uiState.value.user.name,
otherInstance = otherInstance, otherInstance = otherInstance,
).orEmpty() ).orEmpty()
} else { } else {
@ -502,7 +506,6 @@ class UserDetailViewModel(
mvi.updateState { it.copy(asyncInProgress = true) } mvi.updateState { it.copy(asyncInProgress = true) }
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
try { try {
val userId = user.id
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
userRepository.block(userId, true, auth).getOrThrow() userRepository.block(userId, true, auth).getOrThrow()
mvi.emitEffect(UserDetailMviModel.Effect.BlockSuccess) mvi.emitEffect(UserDetailMviModel.Effect.BlockSuccess)
@ -518,6 +521,7 @@ class UserDetailViewModel(
mvi.updateState { it.copy(asyncInProgress = true) } mvi.updateState { it.copy(asyncInProgress = true) }
mvi.scope?.launch(Dispatchers.IO) { mvi.scope?.launch(Dispatchers.IO) {
try { try {
val user = uiState.value.user
val instanceId = user.instanceId val instanceId = user.instanceId
val auth = identityRepository.authToken.value val auth = identityRepository.authToken.value
siteRepository.block(instanceId, true, auth).getOrThrow() siteRepository.block(instanceId, true, auth).getOrThrow()

View File

@ -9,7 +9,7 @@ val userDetailModule = module {
factory<UserDetailMviModel> { params -> factory<UserDetailMviModel> { params ->
UserDetailViewModel( UserDetailViewModel(
mvi = DefaultMviModel(UserDetailMviModel.UiState()), mvi = DefaultMviModel(UserDetailMviModel.UiState()),
user = params[0], userId = params[0],
otherInstance = params[1], otherInstance = params[1],
identityRepository = get(), identityRepository = get(),
apiConfigurationRepository = get(), apiConfigurationRepository = get(),
@ -24,6 +24,7 @@ val userDetailModule = module {
notificationCenter = get(), notificationCenter = get(),
imagePreloadManager = get(), imagePreloadManager = get(),
getSortTypesUseCase = get(), getSortTypesUseCase = get(),
itemCache = get(),
) )
} }
} }

View File

@ -46,7 +46,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.navigation.di.getNavigation
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.rememberCallbackArgs
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.prettifyDate
import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber import com.github.diegoberaldin.raccoonforlemmy.core.utils.getPrettyNumber
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.resources.MR import com.github.diegoberaldin.raccoonforlemmy.resources.MR
import com.github.diegoberaldin.raccoonforlemmy.unit.userinfo.components.ModeratedCommunityCell import com.github.diegoberaldin.raccoonforlemmy.unit.userinfo.components.ModeratedCommunityCell
import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen import com.github.diegoberaldin.raccoonforlemmy.unit.web.WebViewScreen
@ -57,12 +56,12 @@ import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
class UserInfoScreen( class UserInfoScreen(
private val user: UserModel, private val userId: Int,
) : Screen { ) : Screen {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
override fun Content() { override fun Content() {
val model = getScreenModel<UserInfoMviModel> { parametersOf(user) } val model = getScreenModel<UserInfoMviModel> { parametersOf(userId) }
model.bindToLifecycle(key) model.bindToLifecycle(key)
val uiState by model.uiState.collectAsState() val uiState by model.uiState.collectAsState()
val navigationCoordinator = remember { getNavigationCoordinator() } val navigationCoordinator = remember { getNavigationCoordinator() }

View File

@ -4,27 +4,29 @@ import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviMode
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository import com.github.diegoberaldin.raccoonforlemmy.core.persistence.repository.SettingsRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.LemmyItemCache
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class UserInfoViewModel( class UserInfoViewModel(
private val mvi: DefaultMviModel<UserInfoMviModel.Intent, UserInfoMviModel.UiState, UserInfoMviModel.Effect>, private val mvi: DefaultMviModel<UserInfoMviModel.Intent, UserInfoMviModel.UiState, UserInfoMviModel.Effect>,
private val user: UserModel, private val userId: Int,
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val itemCache: LemmyItemCache,
) : UserInfoMviModel, ) : UserInfoMviModel,
MviModel<UserInfoMviModel.Intent, UserInfoMviModel.UiState, UserInfoMviModel.Effect> by mvi { MviModel<UserInfoMviModel.Intent, UserInfoMviModel.UiState, UserInfoMviModel.Effect> by mvi {
override fun onStarted() { override fun onStarted() {
mvi.onStarted() mvi.onStarted()
mvi.updateState {
it.copy(user = user) mvi.scope?.launch {
} val user = itemCache.getUser(userId) ?: UserModel()
mvi.scope?.launch(Dispatchers.IO) { mvi.updateState {
it.copy(user = user)
}
settingsRepository.currentSettings.onEach { settingsRepository.currentSettings.onEach {
mvi.updateState { it.copy(autoLoadImages = it.autoLoadImages) } mvi.updateState { it.copy(autoLoadImages = it.autoLoadImages) }
}.launchIn(this) }.launchIn(this)

View File

@ -9,9 +9,10 @@ val userInfoModule = module {
factory<UserInfoMviModel> { params -> factory<UserInfoMviModel> { params ->
UserInfoViewModel( UserInfoViewModel(
mvi = DefaultMviModel(UserInfoMviModel.UiState()), mvi = DefaultMviModel(UserInfoMviModel.UiState()),
user = params[0], userId = params[0],
userRepository = get(), userRepository = get(),
settingsRepository = get(), settingsRepository = get(),
itemCache = get(),
) )
} }
} }