mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 12:58:43 +01:00
refactor(profile): move saved items to separate screen and restructure post and comments section (#23)
* feat(profile): move saved items to separate screen and introduce saved comments * feat(profile): unifies posts and comments
This commit is contained in:
parent
0eda8a341a
commit
2614e31b9b
@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Bookmarks
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -18,6 +21,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.onClick
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.toLocalPixel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
@ -27,6 +31,7 @@ import io.kamel.image.asyncPainterResource
|
||||
@Composable
|
||||
fun UserHeader(
|
||||
user: UserModel,
|
||||
onOpenBookmarks: (() -> Unit)? = null,
|
||||
) {
|
||||
val userAvatar = user.avatar.orEmpty()
|
||||
val userDisplayName = user.name
|
||||
@ -97,5 +102,17 @@ fun UserHeader(
|
||||
)
|
||||
}
|
||||
}
|
||||
if (onOpenBookmarks != null) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(Spacing.s)
|
||||
.onClick {
|
||||
onOpenBookmarks.invoke()
|
||||
},
|
||||
imageVector = Icons.Outlined.Bookmarks,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,6 @@ class UserRepository(
|
||||
page: Int,
|
||||
limit: Int = PostsRepository.DEFAULT_PAGE_SIZE,
|
||||
sort: SortType = SortType.Active,
|
||||
savedOnly: Boolean = false,
|
||||
): List<PostModel> = runCatching {
|
||||
val response = serviceProvider.user.getDetails(
|
||||
auth = auth,
|
||||
@ -51,7 +50,25 @@ class UserRepository(
|
||||
page = page,
|
||||
limit = limit,
|
||||
sort = sort.toCommentDto(),
|
||||
savedOnly = savedOnly,
|
||||
)
|
||||
val dto = response.body() ?: return@runCatching emptyList()
|
||||
dto.posts.map { it.toModel() }
|
||||
}.getOrElse { emptyList() }
|
||||
|
||||
suspend fun getSavedPosts(
|
||||
id: Int,
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
limit: Int = PostsRepository.DEFAULT_PAGE_SIZE,
|
||||
sort: SortType = SortType.Active,
|
||||
): List<PostModel> = runCatching {
|
||||
val response = serviceProvider.user.getDetails(
|
||||
auth = auth,
|
||||
personId = id,
|
||||
page = page,
|
||||
limit = limit,
|
||||
sort = sort.toCommentDto(),
|
||||
savedOnly = true,
|
||||
)
|
||||
val dto = response.body() ?: return@runCatching emptyList()
|
||||
dto.posts.map { it.toModel() }
|
||||
@ -75,6 +92,25 @@ class UserRepository(
|
||||
dto.comments.map { it.toModel() }
|
||||
}.getOrElse { emptyList() }
|
||||
|
||||
suspend fun getSavedComments(
|
||||
id: Int,
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
limit: Int = PostsRepository.DEFAULT_PAGE_SIZE,
|
||||
sort: SortType = SortType.Active,
|
||||
): List<CommentModel> = runCatching {
|
||||
val response = serviceProvider.user.getDetails(
|
||||
auth = auth,
|
||||
personId = id,
|
||||
page = page,
|
||||
limit = limit,
|
||||
sort = sort.toCommentDto(),
|
||||
savedOnly = true,
|
||||
)
|
||||
val dto = response.body() ?: return@runCatching emptyList()
|
||||
dto.comments.map { it.toModel() }
|
||||
}.getOrElse { emptyList() }
|
||||
|
||||
suspend fun getMentions(
|
||||
auth: String? = null,
|
||||
page: Int,
|
||||
|
@ -3,8 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.di
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.ProfileContentViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments.ProfileCommentsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved.ProfileSavedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.login.LoginBottomSheetViewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
@ -24,20 +23,9 @@ actual fun getProfileLoggedViewModel(): ProfileLoggedViewModel {
|
||||
return res
|
||||
}
|
||||
|
||||
actual fun getProfilePostsViewModel(
|
||||
user: UserModel,
|
||||
savedOnly: Boolean,
|
||||
): ProfilePostsViewModel {
|
||||
val res: ProfilePostsViewModel by inject(
|
||||
clazz = ProfilePostsViewModel::class.java,
|
||||
parameters = { parametersOf(user, savedOnly) },
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
actual fun getProfileCommentsViewModel(user: UserModel): ProfileCommentsViewModel {
|
||||
val res: ProfileCommentsViewModel by inject(
|
||||
clazz = ProfileCommentsViewModel::class.java,
|
||||
actual fun getProfileSavedViewModel(user: UserModel): ProfileSavedViewModel {
|
||||
val res: ProfileSavedViewModel by inject(
|
||||
clazz = ProfileSavedViewModel::class.java,
|
||||
parameters = { parametersOf(user) },
|
||||
)
|
||||
return res
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
@ -1,18 +1,29 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
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.UserModel
|
||||
|
||||
interface ProfileLoggedMviModel :
|
||||
MviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect> {
|
||||
|
||||
sealed interface Intent {
|
||||
data class SelectTab(val value: ProfileLoggedSection) : Intent
|
||||
data class ChangeSection(val section: ProfileLoggedSection) : Intent
|
||||
object Refresh : Intent
|
||||
object LoadNextPage : Intent
|
||||
data class DeletePost(val id: Int) : Intent
|
||||
data class DeleteComment(val id: Int) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
val user: UserModel? = null,
|
||||
val currentTab: ProfileLoggedSection = ProfileLoggedSection.POSTS,
|
||||
val section: ProfileLoggedSection = ProfileLoggedSection.Posts,
|
||||
val refreshing: Boolean = false,
|
||||
val loading: Boolean = false,
|
||||
val canFetchMore: Boolean = true,
|
||||
val posts: List<PostModel> = emptyList(),
|
||||
val comments: List<CommentModel> = emptyList(),
|
||||
)
|
||||
|
||||
sealed interface Effect
|
||||
|
@ -1,34 +1,50 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.CurrentScreen
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.onClick
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.toLocalPixel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserCounters
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserHeader
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments.ProfileCommentsScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostsScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.saved.ProfileSavedScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved.ProfileSavedScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.getProfileLoggedViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
internal object ProfileLoggedScreen : Tab {
|
||||
|
||||
@ -37,6 +53,7 @@ internal object ProfileLoggedScreen : Tab {
|
||||
return TabOptions(0u, "")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Column(
|
||||
@ -49,6 +66,7 @@ internal object ProfileLoggedScreen : Tab {
|
||||
val uiState by model.uiState.collectAsState()
|
||||
val user = uiState.user
|
||||
val notificationCenter = remember { getNotificationCenter() }
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
DisposableEffect(key) {
|
||||
onDispose {
|
||||
notificationCenter.removeObserver(key)
|
||||
@ -56,47 +74,133 @@ internal object ProfileLoggedScreen : Tab {
|
||||
}
|
||||
|
||||
if (user != null) {
|
||||
val screens = remember {
|
||||
val postsScreen = ProfilePostsScreen(user)
|
||||
val commentsScreen = ProfileCommentsScreen(user)
|
||||
val savedScreen = ProfileSavedScreen(user)
|
||||
|
||||
notificationCenter.addObserver({
|
||||
(it as? ProfileLoggedSection)?.also { value ->
|
||||
model.reduce(ProfileLoggedMviModel.Intent.SelectTab(value))
|
||||
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||
model.reduce(ProfileLoggedMviModel.Intent.Refresh)
|
||||
})
|
||||
Box(
|
||||
modifier = Modifier.pullRefresh(pullRefreshState),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
item {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
UserHeader(
|
||||
user = user,
|
||||
onOpenBookmarks = {
|
||||
navigator?.push(
|
||||
ProfileSavedScreen(user = user),
|
||||
)
|
||||
},
|
||||
)
|
||||
UserCounters(
|
||||
modifier = Modifier.graphicsLayer(translationY = -Spacing.m.toLocalPixel()),
|
||||
user = user,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.xs))
|
||||
SectionSelector(
|
||||
titles = listOf(
|
||||
stringResource(MR.strings.profile_section_posts),
|
||||
stringResource(MR.strings.profile_section_comments),
|
||||
),
|
||||
currentSection = when (uiState.section) {
|
||||
ProfileLoggedSection.Comments -> 1
|
||||
else -> 0
|
||||
},
|
||||
onSectionSelected = {
|
||||
val section = when (it) {
|
||||
1 -> ProfileLoggedSection.Comments
|
||||
else -> ProfileLoggedSection.Posts
|
||||
}
|
||||
model.reduce(
|
||||
ProfileLoggedMviModel.Intent.ChangeSection(
|
||||
section
|
||||
)
|
||||
)
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.m))
|
||||
}
|
||||
}
|
||||
}, key, NotificationCenterContractKeys.SectionChanged)
|
||||
notificationCenter.addObserver({
|
||||
(it as? ProfileLoggedSection)?.also { value ->
|
||||
model.reduce(ProfileLoggedMviModel.Intent.SelectTab(value))
|
||||
if (uiState.section == ProfileLoggedSection.Posts) {
|
||||
items(uiState.posts) { post ->
|
||||
ProfilePostCard(
|
||||
modifier = Modifier.onClick {
|
||||
navigator?.push(
|
||||
PostDetailScreen(post),
|
||||
)
|
||||
},
|
||||
post = post,
|
||||
options = listOf(stringResource(MR.strings.comment_action_delete)),
|
||||
onOpenCommunity = { community ->
|
||||
navigator?.push(
|
||||
CommunityDetailScreen(community),
|
||||
)
|
||||
},
|
||||
onImageClick = { url ->
|
||||
navigator?.push(
|
||||
ZoomableImageScreen(url),
|
||||
)
|
||||
},
|
||||
onOptionSelected = { idx ->
|
||||
when (idx) {
|
||||
else -> model.reduce(
|
||||
ProfileLoggedMviModel.Intent.DeletePost(post.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
items(uiState.comments) { comment ->
|
||||
ProfileCommentCard(
|
||||
comment = comment,
|
||||
options = listOf(stringResource(MR.strings.comment_action_delete)),
|
||||
onOptionSelected = { idx ->
|
||||
when (idx) {
|
||||
else ->
|
||||
model.reduce(
|
||||
ProfileLoggedMviModel.Intent.DeleteComment(
|
||||
comment.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}, key, NotificationCenterContractKeys.SectionChanged)
|
||||
notificationCenter.addObserver({
|
||||
(it as? ProfileLoggedSection)?.also { value ->
|
||||
model.reduce(ProfileLoggedMviModel.Intent.SelectTab(value))
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(Spacing.xxxl))
|
||||
}
|
||||
}, key, NotificationCenterContractKeys.SectionChanged)
|
||||
|
||||
listOf(
|
||||
postsScreen,
|
||||
commentsScreen,
|
||||
savedScreen,
|
||||
)
|
||||
}
|
||||
TabNavigator(screens.first()) {
|
||||
CurrentScreen()
|
||||
val navigator = LocalTabNavigator.current
|
||||
LaunchedEffect(model) {
|
||||
model.uiState.map { it.currentTab }
|
||||
.onEach { section ->
|
||||
val index = when (section) {
|
||||
ProfileLoggedSection.POSTS -> 0
|
||||
ProfileLoggedSection.COMMENTS -> 1
|
||||
ProfileLoggedSection.SAVED -> 2
|
||||
item {
|
||||
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||
model.reduce(ProfileLoggedMviModel.Intent.LoadNextPage)
|
||||
}
|
||||
if (uiState.loading && !uiState.refreshing) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(25.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
navigator.current = screens[index]
|
||||
}.launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = uiState.refreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged
|
||||
|
||||
enum class ProfileLoggedSection {
|
||||
POSTS,
|
||||
COMMENTS,
|
||||
SAVED,
|
||||
sealed interface ProfileLoggedSection {
|
||||
object Posts : ProfileLoggedSection
|
||||
object Comments : ProfileLoggedSection
|
||||
}
|
||||
|
@ -3,8 +3,15 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||
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.SortType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.SiteRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
@ -13,8 +20,32 @@ class ProfileLoggedViewModel(
|
||||
private val mvi: DefaultMviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect>,
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val siteRepository: SiteRepository,
|
||||
private val postsRepository: PostsRepository,
|
||||
private val commentRepository: CommentRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val notificationCenter: NotificationCenter,
|
||||
) : ScreenModel,
|
||||
MviModel<ProfileLoggedMviModel.Intent, ProfileLoggedMviModel.UiState, ProfileLoggedMviModel.Effect> by mvi {
|
||||
|
||||
private var currentPage = 1
|
||||
|
||||
init {
|
||||
notificationCenter.addObserver({
|
||||
(it as? PostModel)?.also { post ->
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostUpdated)
|
||||
notificationCenter.addObserver({
|
||||
(it as? PostModel)?.also { post ->
|
||||
handlePostDelete(post.id)
|
||||
}
|
||||
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostDeleted)
|
||||
}
|
||||
|
||||
fun finalize() {
|
||||
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
|
||||
}
|
||||
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
@ -26,9 +57,123 @@ class ProfileLoggedViewModel(
|
||||
|
||||
override fun reduce(intent: ProfileLoggedMviModel.Intent) {
|
||||
when (intent) {
|
||||
is ProfileLoggedMviModel.Intent.SelectTab -> mvi.updateState {
|
||||
it.copy(currentTab = intent.value)
|
||||
is ProfileLoggedMviModel.Intent.ChangeSection -> changeSection(intent.section)
|
||||
is ProfileLoggedMviModel.Intent.DeleteComment -> deleteComment(intent.id)
|
||||
is ProfileLoggedMviModel.Intent.DeletePost -> deletePost(intent.id)
|
||||
ProfileLoggedMviModel.Intent.LoadNextPage -> loadNextPage()
|
||||
ProfileLoggedMviModel.Intent.Refresh -> refresh()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
currentPage = 1
|
||||
mvi.updateState { it.copy(canFetchMore = true, refreshing = true) }
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
private fun changeSection(section: ProfileLoggedSection) {
|
||||
currentPage = 1
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
section = section,
|
||||
canFetchMore = true,
|
||||
refreshing = true,
|
||||
)
|
||||
}
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
private fun loadNextPage() {
|
||||
val currentState = mvi.uiState.value
|
||||
if (!currentState.canFetchMore || currentState.loading) {
|
||||
mvi.updateState { it.copy(refreshing = false) }
|
||||
return
|
||||
}
|
||||
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val userId = currentState.user?.id ?: 0
|
||||
val section = currentState.section
|
||||
if (section == ProfileLoggedSection.Posts) {
|
||||
val postList = userRepository.getPosts(
|
||||
auth = auth,
|
||||
id = userId,
|
||||
page = currentPage,
|
||||
sort = SortType.New,
|
||||
)
|
||||
val canFetchMore = postList.size >= PostsRepository.DEFAULT_PAGE_SIZE
|
||||
mvi.updateState {
|
||||
val newPosts = if (refreshing) {
|
||||
postList
|
||||
} else {
|
||||
it.posts + postList
|
||||
}
|
||||
it.copy(
|
||||
posts = newPosts,
|
||||
loading = false,
|
||||
canFetchMore = canFetchMore,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val commentList = userRepository.getComments(
|
||||
auth = auth,
|
||||
id = userId,
|
||||
page = currentPage,
|
||||
sort = SortType.New,
|
||||
)
|
||||
val canFetchMore = commentList.size >= PostsRepository.DEFAULT_PAGE_SIZE
|
||||
mvi.updateState {
|
||||
val newcomments = if (refreshing) {
|
||||
commentList
|
||||
} else {
|
||||
it.comments + commentList
|
||||
}
|
||||
it.copy(
|
||||
comments = newcomments,
|
||||
loading = false,
|
||||
canFetchMore = canFetchMore,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
currentPage++
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostUpdate(post: PostModel) {
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
post
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostDelete(id: Int) {
|
||||
mvi.updateState { it.copy(posts = it.posts.filter { post -> post.id != id }) }
|
||||
}
|
||||
|
||||
private fun deletePost(id: Int) {
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postsRepository.delete(id = id, auth = auth)
|
||||
handlePostDelete(id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteComment(id: Int) {
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
commentRepository.delete(id, auth)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
@ -1,23 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
|
||||
|
||||
interface ProfileCommentsMviModel :
|
||||
MviModel<ProfileCommentsMviModel.Intent, ProfileCommentsMviModel.UiState, ProfileCommentsMviModel.Effect> {
|
||||
|
||||
sealed interface Intent {
|
||||
object Refresh : Intent
|
||||
object LoadNextPage : Intent
|
||||
data class DeleteComment(val id: Int) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
val refreshing: Boolean = false,
|
||||
val loading: Boolean = false,
|
||||
val canFetchMore: Boolean = true,
|
||||
val comments: List<CommentModel> = emptyList(),
|
||||
)
|
||||
|
||||
sealed interface Effect
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.toLocalPixel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserCounters
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserHeader
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedSection
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.getProfileCommentsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
internal class ProfileCommentsScreen(
|
||||
private val user: UserModel,
|
||||
) : Tab {
|
||||
|
||||
override val options: TabOptions
|
||||
@Composable get() {
|
||||
return TabOptions(1u, "")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getProfileCommentsViewModel(user) }
|
||||
model.bindToLifecycle(key)
|
||||
val uiState by model.uiState.collectAsState()
|
||||
val notificationCenter = remember { getNotificationCenter() }
|
||||
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||
model.reduce(ProfileCommentsMviModel.Intent.Refresh)
|
||||
})
|
||||
Box(
|
||||
modifier = Modifier.pullRefresh(pullRefreshState),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
item {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
UserHeader(user = user)
|
||||
UserCounters(
|
||||
modifier = Modifier.graphicsLayer(translationY = -Spacing.m.toLocalPixel()),
|
||||
user = user,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.s))
|
||||
SectionSelector(
|
||||
titles = listOf(
|
||||
stringResource(MR.strings.profile_section_posts),
|
||||
stringResource(MR.strings.profile_section_comments),
|
||||
stringResource(MR.strings.profile_section_saved),
|
||||
),
|
||||
currentSection = 1,
|
||||
onSectionSelected = {
|
||||
val section = when (it) {
|
||||
0 -> ProfileLoggedSection.POSTS
|
||||
1 -> ProfileLoggedSection.COMMENTS
|
||||
else -> ProfileLoggedSection.SAVED
|
||||
}
|
||||
notificationCenter.getObserver(NotificationCenterContractKeys.SectionChanged)?.also { observer ->
|
||||
observer.invoke(section)
|
||||
}
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.m))
|
||||
}
|
||||
}
|
||||
items(uiState.comments) { comment ->
|
||||
ProfileCommentCard(
|
||||
comment = comment,
|
||||
options = listOf(stringResource(MR.strings.comment_action_delete)),
|
||||
onOptionSelected = { idx ->
|
||||
when (idx) {
|
||||
else ->
|
||||
model.reduce(
|
||||
ProfileCommentsMviModel.Intent.DeleteComment(
|
||||
comment.id
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(Spacing.xxxl))
|
||||
}
|
||||
item {
|
||||
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||
model.reduce(ProfileCommentsMviModel.Intent.LoadNextPage)
|
||||
}
|
||||
if (uiState.loading && !uiState.refreshing) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(25.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = uiState.refreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ProfileCommentsViewModel(
|
||||
private val mvi: DefaultMviModel<ProfileCommentsMviModel.Intent, ProfileCommentsMviModel.UiState, ProfileCommentsMviModel.Effect>,
|
||||
private val user: UserModel,
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val commentRepository: CommentRepository,
|
||||
) : ScreenModel,
|
||||
MviModel<ProfileCommentsMviModel.Intent, ProfileCommentsMviModel.UiState, ProfileCommentsMviModel.Effect> by mvi {
|
||||
|
||||
private var currentPage: Int = 1
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
|
||||
if (mvi.uiState.value.comments.isEmpty()) {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
override fun reduce(intent: ProfileCommentsMviModel.Intent) {
|
||||
when (intent) {
|
||||
ProfileCommentsMviModel.Intent.LoadNextPage -> loadNextPage()
|
||||
ProfileCommentsMviModel.Intent.Refresh -> refresh()
|
||||
is ProfileCommentsMviModel.Intent.DeleteComment -> deleteComment(intent.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
currentPage = 1
|
||||
mvi.updateState { it.copy(canFetchMore = true, refreshing = true) }
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
private fun loadNextPage() {
|
||||
val currentState = mvi.uiState.value
|
||||
if (!currentState.canFetchMore || currentState.loading) {
|
||||
mvi.updateState { it.copy(refreshing = false) }
|
||||
return
|
||||
}
|
||||
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val commentList = userRepository.getComments(
|
||||
auth = auth,
|
||||
id = user.id,
|
||||
page = currentPage,
|
||||
sort = SortType.New,
|
||||
)
|
||||
currentPage++
|
||||
val canFetchMore = commentList.size >= PostsRepository.DEFAULT_PAGE_SIZE
|
||||
mvi.updateState {
|
||||
val newcomments = if (refreshing) {
|
||||
commentList
|
||||
} else {
|
||||
it.comments + commentList
|
||||
}
|
||||
it.copy(
|
||||
comments = newcomments,
|
||||
loading = false,
|
||||
canFetchMore = canFetchMore,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteComment(id: Int) {
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
commentRepository.delete(id, auth)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||
|
||||
interface ProfilePostsMviModel :
|
||||
MviModel<ProfilePostsMviModel.Intent, ProfilePostsMviModel.UiState, ProfilePostsMviModel.Effect> {
|
||||
|
||||
sealed interface Intent {
|
||||
object Refresh : Intent
|
||||
object LoadNextPage : Intent
|
||||
data class DeletePost(val id: Int) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
val refreshing: Boolean = false,
|
||||
val loading: Boolean = false,
|
||||
val canFetchMore: Boolean = true,
|
||||
val posts: List<PostModel> = emptyList(),
|
||||
)
|
||||
|
||||
sealed interface Effect
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.onClick
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.toLocalPixel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserCounters
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserHeader
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedSection
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.getProfilePostsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
internal class ProfilePostsScreen(
|
||||
private val user: UserModel,
|
||||
) : Tab {
|
||||
override val options: TabOptions
|
||||
@Composable get() {
|
||||
return TabOptions(0u, "")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel {
|
||||
getProfilePostsViewModel(
|
||||
user = user,
|
||||
)
|
||||
}
|
||||
model.bindToLifecycle(key)
|
||||
val uiState by model.uiState.collectAsState()
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
val notificationCenter = remember { getNotificationCenter() }
|
||||
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||
model.reduce(ProfilePostsMviModel.Intent.Refresh)
|
||||
})
|
||||
Box(
|
||||
modifier = Modifier.pullRefresh(pullRefreshState),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
item {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
UserHeader(user = user)
|
||||
UserCounters(
|
||||
modifier = Modifier.graphicsLayer(translationY = -Spacing.m.toLocalPixel()),
|
||||
user = user,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.s))
|
||||
SectionSelector(
|
||||
titles = listOf(
|
||||
stringResource(MR.strings.profile_section_posts),
|
||||
stringResource(MR.strings.profile_section_comments),
|
||||
stringResource(MR.strings.profile_section_saved),
|
||||
),
|
||||
currentSection = 0,
|
||||
onSectionSelected = {
|
||||
val section = when (it) {
|
||||
0 -> ProfileLoggedSection.POSTS
|
||||
1 -> ProfileLoggedSection.COMMENTS
|
||||
else -> ProfileLoggedSection.SAVED
|
||||
}
|
||||
notificationCenter.getObserver(NotificationCenterContractKeys.SectionChanged)
|
||||
?.also { observer ->
|
||||
observer.invoke(section)
|
||||
}
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.m))
|
||||
}
|
||||
}
|
||||
items(uiState.posts) { post ->
|
||||
ProfilePostCard(
|
||||
modifier = Modifier.onClick {
|
||||
navigator?.push(
|
||||
PostDetailScreen(post),
|
||||
)
|
||||
},
|
||||
post = post,
|
||||
options = listOf(stringResource(MR.strings.comment_action_delete)),
|
||||
onOpenCommunity = { community ->
|
||||
navigator?.push(
|
||||
CommunityDetailScreen(community),
|
||||
)
|
||||
},
|
||||
onImageClick = { url ->
|
||||
navigator?.push(
|
||||
ZoomableImageScreen(url),
|
||||
)
|
||||
},
|
||||
onOptionSelected = { idx ->
|
||||
when (idx) {
|
||||
else -> model.reduce(
|
||||
ProfilePostsMviModel.Intent.DeletePost(post.id)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(Spacing.xxxl))
|
||||
}
|
||||
item {
|
||||
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||
model.reduce(ProfilePostsMviModel.Intent.LoadNextPage)
|
||||
}
|
||||
if (uiState.loading && !uiState.refreshing) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(25.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = uiState.refreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||
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.SortType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ProfilePostsViewModel(
|
||||
private val mvi: DefaultMviModel<ProfilePostsMviModel.Intent, ProfilePostsMviModel.UiState, ProfilePostsMviModel.Effect>,
|
||||
private val user: UserModel,
|
||||
private val savedOnly: Boolean = false,
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val postsRepository: PostsRepository,
|
||||
private val notificationCenter: NotificationCenter,
|
||||
) : ScreenModel,
|
||||
MviModel<ProfilePostsMviModel.Intent, ProfilePostsMviModel.UiState, ProfilePostsMviModel.Effect> by mvi {
|
||||
|
||||
private var currentPage: Int = 1
|
||||
|
||||
init {
|
||||
notificationCenter.addObserver({
|
||||
(it as? PostModel)?.also { post ->
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostUpdated)
|
||||
notificationCenter.addObserver({
|
||||
(it as? PostModel)?.also { post ->
|
||||
handlePostDelete(post.id)
|
||||
}
|
||||
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostDeleted)
|
||||
}
|
||||
|
||||
fun finalize() {
|
||||
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
|
||||
}
|
||||
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
|
||||
if (mvi.uiState.value.posts.isEmpty()) {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
override fun reduce(intent: ProfilePostsMviModel.Intent) {
|
||||
when (intent) {
|
||||
ProfilePostsMviModel.Intent.LoadNextPage -> loadNextPage()
|
||||
ProfilePostsMviModel.Intent.Refresh -> refresh()
|
||||
is ProfilePostsMviModel.Intent.DeletePost -> deletePost(intent.id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
currentPage = 1
|
||||
mvi.updateState { it.copy(canFetchMore = true, refreshing = true) }
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
private fun loadNextPage() {
|
||||
val currentState = mvi.uiState.value
|
||||
if (!currentState.canFetchMore || currentState.loading) {
|
||||
mvi.updateState { it.copy(refreshing = false) }
|
||||
return
|
||||
}
|
||||
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val postList = userRepository.getPosts(
|
||||
auth = auth,
|
||||
id = user.id,
|
||||
savedOnly = savedOnly,
|
||||
page = currentPage,
|
||||
sort = SortType.New,
|
||||
)
|
||||
currentPage++
|
||||
val canFetchMore = postList.size >= PostsRepository.DEFAULT_PAGE_SIZE
|
||||
mvi.updateState {
|
||||
val newPosts = if (refreshing) {
|
||||
postList
|
||||
} else {
|
||||
it.posts + postList
|
||||
}
|
||||
it.copy(
|
||||
posts = newPosts,
|
||||
loading = false,
|
||||
canFetchMore = canFetchMore,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostUpdate(post: PostModel) {
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
post
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostDelete(id: Int) {
|
||||
mvi.updateState { it.copy(posts = it.posts.filter { post -> post.id != id }) }
|
||||
}
|
||||
|
||||
private fun deletePost(id: Int) {
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postsRepository.delete(id = id, auth = auth)
|
||||
handlePostDelete(id)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.saved
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.toLocalPixel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserCounters
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.UserHeader
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedSection
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostCard
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostsMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.getProfilePostsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
internal class ProfileSavedScreen(
|
||||
private val user: UserModel,
|
||||
) : Tab {
|
||||
|
||||
override val options: TabOptions
|
||||
@Composable get() {
|
||||
return TabOptions(0u, "")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel {
|
||||
getProfilePostsViewModel(
|
||||
user = user,
|
||||
savedOnly = true,
|
||||
)
|
||||
}
|
||||
model.bindToLifecycle(key)
|
||||
val uiState by model.uiState.collectAsState()
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
val notificationCenter = remember { getNotificationCenter() }
|
||||
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||
model.reduce(ProfilePostsMviModel.Intent.Refresh)
|
||||
})
|
||||
Box(
|
||||
modifier = Modifier.pullRefresh(pullRefreshState),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
item {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
UserHeader(user = user)
|
||||
UserCounters(
|
||||
modifier = Modifier.graphicsLayer(translationY = -Spacing.m.toLocalPixel()),
|
||||
user = user,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.s))
|
||||
SectionSelector(
|
||||
titles = listOf(
|
||||
stringResource(MR.strings.profile_section_posts),
|
||||
stringResource(MR.strings.profile_section_comments),
|
||||
stringResource(MR.strings.profile_section_saved),
|
||||
),
|
||||
currentSection = 2,
|
||||
onSectionSelected = {
|
||||
val section = when (it) {
|
||||
0 -> ProfileLoggedSection.POSTS
|
||||
1 -> ProfileLoggedSection.COMMENTS
|
||||
else -> ProfileLoggedSection.SAVED
|
||||
}
|
||||
notificationCenter.getObserver(NotificationCenterContractKeys.SectionChanged)
|
||||
?.also { observer ->
|
||||
observer.invoke(section)
|
||||
}
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.height(Spacing.m))
|
||||
}
|
||||
}
|
||||
items(uiState.posts) { post ->
|
||||
ProfilePostCard(
|
||||
post = post,
|
||||
onOpenCommunity = { community ->
|
||||
navigator?.push(
|
||||
CommunityDetailScreen(community),
|
||||
)
|
||||
},
|
||||
onImageClick = { url ->
|
||||
navigator?.push(
|
||||
ZoomableImageScreen(url),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(Spacing.xxxl))
|
||||
}
|
||||
item {
|
||||
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||
model.reduce(ProfilePostsMviModel.Intent.LoadNextPage)
|
||||
}
|
||||
if (uiState.loading && !uiState.refreshing) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(25.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = uiState.refreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved
|
||||
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommentModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||
|
||||
interface ProfileSavedMviModel :
|
||||
MviModel<ProfileSavedMviModel.Intent, ProfileSavedMviModel.UiState, ProfileSavedMviModel.Effect> {
|
||||
|
||||
sealed interface Intent {
|
||||
object Refresh : Intent
|
||||
object LoadNextPage : Intent
|
||||
data class ChangeSection(val section: ProfileSavedSection) : Intent
|
||||
data class UpVotePost(val index: Int, val feedback: Boolean = false) : Intent
|
||||
data class DownVotePost(val index: Int, val feedback: Boolean = false) : Intent
|
||||
data class SavePost(val index: Int, val feedback: Boolean = false) : Intent
|
||||
data class UpVoteComment(val index: Int, val feedback: Boolean = false) : Intent
|
||||
data class DownVoteComment(val index: Int, val feedback: Boolean = false) : Intent
|
||||
data class SaveComment(val index: Int, val feedback: Boolean = false) : Intent
|
||||
}
|
||||
|
||||
data class UiState(
|
||||
val section: ProfileSavedSection = ProfileSavedSection.Posts,
|
||||
val refreshing: Boolean = false,
|
||||
val loading: Boolean = false,
|
||||
val canFetchMore: Boolean = true,
|
||||
val blurNsfw: Boolean = true,
|
||||
val posts: List<PostModel> = emptyList(),
|
||||
val comments: List<CommentModel> = emptyList(),
|
||||
)
|
||||
|
||||
sealed interface Effect
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.Tab
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.onClick
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.communitydetail.CommunityDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.CommentCard
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.PostCard
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.SectionSelector
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.createcomment.CreateCommentScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCoordinator
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.di.getProfileSavedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.resources.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
internal class ProfileSavedScreen(
|
||||
private val user: UserModel,
|
||||
) : Tab {
|
||||
|
||||
override val options: TabOptions
|
||||
@Composable get() {
|
||||
return TabOptions(0u, "")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel {
|
||||
getProfileSavedViewModel(user = user)
|
||||
}
|
||||
model.bindToLifecycle(key)
|
||||
val uiState by model.uiState.collectAsState()
|
||||
val navigator = remember { getNavigationCoordinator().getRootNavigator() }
|
||||
val bottomSheetNavigator = LocalBottomSheetNavigator.current
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
title = {
|
||||
Text(stringResource(MR.strings.profile_section_saved))
|
||||
},
|
||||
navigationIcon = {
|
||||
Image(
|
||||
modifier = Modifier.onClick {
|
||||
navigator?.pop()
|
||||
},
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = null,
|
||||
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.s),
|
||||
) {
|
||||
SectionSelector(
|
||||
modifier = Modifier.padding(vertical = Spacing.s),
|
||||
titles = listOf(
|
||||
stringResource(MR.strings.profile_section_posts),
|
||||
stringResource(MR.strings.profile_section_comments),
|
||||
),
|
||||
currentSection = when (uiState.section) {
|
||||
ProfileSavedSection.Comments -> 1
|
||||
else -> 0
|
||||
},
|
||||
onSectionSelected = {
|
||||
val section = when (it) {
|
||||
1 -> ProfileSavedSection.Comments
|
||||
else -> ProfileSavedSection.Posts
|
||||
}
|
||||
model.reduce(ProfileSavedMviModel.Intent.ChangeSection(section))
|
||||
},
|
||||
)
|
||||
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||
model.reduce(ProfileSavedMviModel.Intent.Refresh)
|
||||
})
|
||||
Box(
|
||||
modifier = Modifier.pullRefresh(pullRefreshState),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
if (uiState.section == ProfileSavedSection.Posts) {
|
||||
itemsIndexed(uiState.posts) { idx, post ->
|
||||
PostCard(
|
||||
modifier = Modifier.onClick {
|
||||
navigator?.push(
|
||||
PostDetailScreen(post),
|
||||
)
|
||||
},
|
||||
post = post,
|
||||
blurNsfw = uiState.blurNsfw,
|
||||
onOpenCommunity = { community ->
|
||||
navigator?.push(
|
||||
CommunityDetailScreen(community),
|
||||
)
|
||||
},
|
||||
onOpenCreator = { u ->
|
||||
if (u.id != user.id) {
|
||||
navigator?.push(UserDetailScreen(u))
|
||||
}
|
||||
},
|
||||
onUpVote = {
|
||||
model.reduce(
|
||||
ProfileSavedMviModel.Intent.UpVotePost(
|
||||
index = idx,
|
||||
feedback = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
onDownVote = {
|
||||
model.reduce(
|
||||
ProfileSavedMviModel.Intent.DownVotePost(
|
||||
index = idx,
|
||||
feedback = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
onSave = {
|
||||
model.reduce(
|
||||
ProfileSavedMviModel.Intent.SavePost(
|
||||
index = idx,
|
||||
feedback = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
onReply = {
|
||||
val screen = CreateCommentScreen(
|
||||
originalPost = post,
|
||||
)
|
||||
bottomSheetNavigator.show(screen)
|
||||
},
|
||||
onImageClick = { url ->
|
||||
navigator?.push(
|
||||
ZoomableImageScreen(url),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
itemsIndexed(uiState.comments) { idx, comment ->
|
||||
CommentCard(
|
||||
comment = comment,
|
||||
onUpVote = {
|
||||
model.reduce(
|
||||
ProfileSavedMviModel.Intent.UpVoteComment(
|
||||
index = idx,
|
||||
feedback = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
onDownVote = {
|
||||
model.reduce(
|
||||
ProfileSavedMviModel.Intent.DownVoteComment(
|
||||
index = idx,
|
||||
feedback = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
onSave = {
|
||||
model.reduce(
|
||||
ProfileSavedMviModel.Intent.SaveComment(
|
||||
index = idx,
|
||||
feedback = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
onReply = {
|
||||
val screen = CreateCommentScreen(
|
||||
originalPost = PostModel(id = comment.postId),
|
||||
originalComment = comment,
|
||||
)
|
||||
bottomSheetNavigator.show(screen)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
if (!uiState.loading && !uiState.refreshing && uiState.canFetchMore) {
|
||||
model.reduce(ProfileSavedMviModel.Intent.LoadNextPage)
|
||||
}
|
||||
if (uiState.loading && !uiState.refreshing) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(Spacing.xs),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(25.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = uiState.refreshing,
|
||||
state = pullRefreshState,
|
||||
modifier = Modifier.align(Alignment.TopCenter),
|
||||
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved
|
||||
|
||||
sealed interface ProfileSavedSection {
|
||||
object Posts : ProfileSavedSection
|
||||
|
||||
object Comments : ProfileSavedSection
|
||||
}
|
@ -0,0 +1,492 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved
|
||||
|
||||
import cafe.adriel.voyager.core.model.ScreenModel
|
||||
import com.github.diegoberaldin.racconforlemmy.core.utils.HapticFeedback
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenter
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
|
||||
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.SortType
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostsRepository
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.UserRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ProfileSavedViewModel(
|
||||
private val mvi: DefaultMviModel<ProfileSavedMviModel.Intent, ProfileSavedMviModel.UiState, ProfileSavedMviModel.Effect>,
|
||||
private val user: UserModel,
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val userRepository: UserRepository,
|
||||
private val postsRepository: PostsRepository,
|
||||
private val commentRepository: CommentRepository,
|
||||
private val notificationCenter: NotificationCenter,
|
||||
private val hapticFeedback: HapticFeedback,
|
||||
) : ScreenModel,
|
||||
MviModel<ProfileSavedMviModel.Intent, ProfileSavedMviModel.UiState, ProfileSavedMviModel.Effect> by mvi {
|
||||
|
||||
private var currentPage: Int = 1
|
||||
|
||||
init {
|
||||
notificationCenter.addObserver({
|
||||
(it as? PostModel)?.also { post ->
|
||||
handlePostUpdate(post)
|
||||
}
|
||||
}, this::class.simpleName.orEmpty(), NotificationCenterContractKeys.PostUpdated)
|
||||
}
|
||||
|
||||
fun finalize() {
|
||||
notificationCenter.removeObserver(this::class.simpleName.orEmpty())
|
||||
}
|
||||
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
|
||||
if (mvi.uiState.value.posts.isEmpty()) {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
override fun reduce(intent: ProfileSavedMviModel.Intent) {
|
||||
when (intent) {
|
||||
ProfileSavedMviModel.Intent.LoadNextPage -> loadNextPage()
|
||||
ProfileSavedMviModel.Intent.Refresh -> refresh()
|
||||
is ProfileSavedMviModel.Intent.ChangeSection -> changeSection(intent.section)
|
||||
is ProfileSavedMviModel.Intent.DownVoteComment -> toggleDownVoteComment(
|
||||
comment = uiState.value.comments[intent.index],
|
||||
feedback = intent.feedback,
|
||||
)
|
||||
|
||||
is ProfileSavedMviModel.Intent.DownVotePost -> toggleDownVotePost(
|
||||
post = uiState.value.posts[intent.index],
|
||||
feedback = intent.feedback,
|
||||
)
|
||||
|
||||
is ProfileSavedMviModel.Intent.SaveComment -> toggleSaveComment(
|
||||
comment = uiState.value.comments[intent.index],
|
||||
feedback = intent.feedback,
|
||||
)
|
||||
|
||||
is ProfileSavedMviModel.Intent.SavePost -> toggleSavePost(
|
||||
post = uiState.value.posts[intent.index],
|
||||
feedback = intent.feedback,
|
||||
)
|
||||
|
||||
is ProfileSavedMviModel.Intent.UpVoteComment -> toggleUpVoteComment(
|
||||
comment = uiState.value.comments[intent.index],
|
||||
feedback = intent.feedback,
|
||||
)
|
||||
|
||||
is ProfileSavedMviModel.Intent.UpVotePost -> toggleUpVotePost(
|
||||
post = uiState.value.posts[intent.index],
|
||||
feedback = intent.feedback,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refresh() {
|
||||
currentPage = 1
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
canFetchMore = true, refreshing = true
|
||||
)
|
||||
}
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
private fun loadNextPage() {
|
||||
val currentState = mvi.uiState.value
|
||||
if (!currentState.canFetchMore || currentState.loading) {
|
||||
mvi.updateState { it.copy(refreshing = false) }
|
||||
return
|
||||
}
|
||||
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val section = currentState.section
|
||||
if (section == ProfileSavedSection.Posts) {
|
||||
val postList = userRepository.getSavedPosts(
|
||||
auth = auth,
|
||||
id = user.id,
|
||||
page = currentPage,
|
||||
sort = SortType.New,
|
||||
)
|
||||
val canFetchMore = postList.size >= PostsRepository.DEFAULT_PAGE_SIZE
|
||||
mvi.updateState {
|
||||
val newPosts = if (refreshing) {
|
||||
postList
|
||||
} else {
|
||||
it.posts + postList
|
||||
}
|
||||
it.copy(
|
||||
posts = newPosts,
|
||||
loading = false,
|
||||
canFetchMore = canFetchMore,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val commentList = userRepository.getSavedComments(
|
||||
auth = auth,
|
||||
id = user.id,
|
||||
page = currentPage,
|
||||
sort = SortType.New,
|
||||
)
|
||||
val canFetchMore = commentList.size >= PostsRepository.DEFAULT_PAGE_SIZE
|
||||
mvi.updateState {
|
||||
val newComments = if (refreshing) {
|
||||
commentList
|
||||
} else {
|
||||
it.comments + commentList
|
||||
}
|
||||
it.copy(
|
||||
comments = newComments,
|
||||
loading = false,
|
||||
canFetchMore = canFetchMore,
|
||||
refreshing = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
currentPage++
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostUpdate(post: PostModel) {
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
post
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePostDelete(id: Int) {
|
||||
mvi.updateState { it.copy(posts = it.posts.filter { post -> post.id != id }) }
|
||||
}
|
||||
|
||||
private fun deletePost(id: Int) {
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postsRepository.delete(id = id, auth = auth)
|
||||
handlePostDelete(id)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changeSection(section: ProfileSavedSection) {
|
||||
currentPage = 1
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
section = section,
|
||||
canFetchMore = true,
|
||||
refreshing = true,
|
||||
)
|
||||
}
|
||||
loadNextPage()
|
||||
}
|
||||
|
||||
private fun toggleUpVotePost(
|
||||
post: PostModel,
|
||||
feedback: Boolean,
|
||||
) {
|
||||
val newValue = post.myVote <= 0
|
||||
if (feedback) {
|
||||
hapticFeedback.vibrate()
|
||||
}
|
||||
val newPost = postsRepository.asUpVoted(
|
||||
post = post,
|
||||
voted = newValue,
|
||||
)
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
newPost
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postsRepository.upVote(
|
||||
auth = auth,
|
||||
post = post,
|
||||
voted = newValue,
|
||||
)
|
||||
notificationCenter.getObserver(NotificationCenterContractKeys.PostUpdated)?.also {
|
||||
it.invoke(newPost)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
newPost
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleDownVotePost(
|
||||
post: PostModel,
|
||||
feedback: Boolean,
|
||||
) {
|
||||
val newValue = post.myVote >= 0
|
||||
if (feedback) {
|
||||
hapticFeedback.vibrate()
|
||||
}
|
||||
val newPost = postsRepository.asDownVoted(
|
||||
post = post,
|
||||
downVoted = newValue,
|
||||
)
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
newPost
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postsRepository.downVote(
|
||||
auth = auth,
|
||||
post = post,
|
||||
downVoted = newValue,
|
||||
)
|
||||
notificationCenter.getObserver(NotificationCenterContractKeys.PostUpdated)?.also {
|
||||
it.invoke(newPost)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
newPost
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSavePost(
|
||||
post: PostModel,
|
||||
feedback: Boolean,
|
||||
) {
|
||||
val newValue = !post.saved
|
||||
if (feedback) {
|
||||
hapticFeedback.vibrate()
|
||||
}
|
||||
val newPost = postsRepository.asSaved(
|
||||
post = post,
|
||||
saved = newValue,
|
||||
)
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
newPost
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
postsRepository.save(
|
||||
auth = auth,
|
||||
post = post,
|
||||
saved = newValue,
|
||||
)
|
||||
notificationCenter.getObserver(NotificationCenterContractKeys.PostUpdated)?.also {
|
||||
it.invoke(newPost)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
posts = it.posts.map { p ->
|
||||
if (p.id == post.id) {
|
||||
newPost
|
||||
} else {
|
||||
p
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleUpVoteComment(
|
||||
comment: CommentModel,
|
||||
feedback: Boolean,
|
||||
) {
|
||||
val newValue = comment.myVote <= 0
|
||||
if (feedback) {
|
||||
hapticFeedback.vibrate()
|
||||
}
|
||||
val newComment = commentRepository.asUpVoted(
|
||||
comment = comment,
|
||||
voted = newValue,
|
||||
)
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
comments = it.comments.map { c ->
|
||||
if (c.id == comment.id) {
|
||||
newComment
|
||||
} else {
|
||||
c
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
commentRepository.upVote(
|
||||
auth = auth,
|
||||
comment = comment,
|
||||
voted = newValue,
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
comments = it.comments.map { c ->
|
||||
if (c.id == comment.id) {
|
||||
comment
|
||||
} else {
|
||||
c
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleDownVoteComment(
|
||||
comment: CommentModel,
|
||||
feedback: Boolean,
|
||||
) {
|
||||
val newValue = comment.myVote >= 0
|
||||
if (feedback) {
|
||||
hapticFeedback.vibrate()
|
||||
}
|
||||
val newComment = commentRepository.asDownVoted(comment, newValue)
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
comments = it.comments.map { c ->
|
||||
if (c.id == comment.id) {
|
||||
newComment
|
||||
} else {
|
||||
c
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
commentRepository.downVote(
|
||||
auth = auth,
|
||||
comment = comment,
|
||||
downVoted = newValue,
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
comments = it.comments.map { c ->
|
||||
if (c.id == comment.id) {
|
||||
comment
|
||||
} else {
|
||||
c
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleSaveComment(
|
||||
comment: CommentModel,
|
||||
feedback: Boolean,
|
||||
) {
|
||||
val newValue = !comment.saved
|
||||
if (feedback) {
|
||||
hapticFeedback.vibrate()
|
||||
}
|
||||
val newComment = commentRepository.asSaved(
|
||||
comment = comment,
|
||||
saved = newValue,
|
||||
)
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
comments = it.comments.map { c ->
|
||||
if (c.id == comment.id) {
|
||||
newComment
|
||||
} else {
|
||||
c
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
mvi.scope?.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val auth = identityRepository.authToken.value.orEmpty()
|
||||
commentRepository.save(
|
||||
auth = auth,
|
||||
comment = comment,
|
||||
saved = newValue,
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
e.printStackTrace()
|
||||
mvi.updateState {
|
||||
it.copy(
|
||||
comments = it.comments.map { c ->
|
||||
if (c.id == comment.id) {
|
||||
comment
|
||||
} else {
|
||||
c
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,10 +5,8 @@ import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.ProfileC
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.ProfileContentViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments.ProfileCommentsMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments.ProfileCommentsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostsMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved.ProfileSavedMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved.ProfileSavedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.login.LoginBottomSheetMviModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.login.LoginBottomSheetViewModel
|
||||
import org.koin.dsl.module
|
||||
@ -33,26 +31,22 @@ val profileTabModule = module {
|
||||
mvi = DefaultMviModel(ProfileLoggedMviModel.UiState()),
|
||||
identityRepository = get(),
|
||||
siteRepository = get(),
|
||||
)
|
||||
}
|
||||
factory { params ->
|
||||
ProfilePostsViewModel(
|
||||
mvi = DefaultMviModel(ProfilePostsMviModel.UiState()),
|
||||
user = params[0],
|
||||
savedOnly = params[1],
|
||||
identityRepository = get(),
|
||||
userRepository = get(),
|
||||
postsRepository = get(),
|
||||
commentRepository = get(),
|
||||
notificationCenter = get(),
|
||||
)
|
||||
}
|
||||
factory { params ->
|
||||
ProfileCommentsViewModel(
|
||||
mvi = DefaultMviModel(ProfileCommentsMviModel.UiState()),
|
||||
ProfileSavedViewModel(
|
||||
mvi = DefaultMviModel(ProfileSavedMviModel.UiState()),
|
||||
user = params[0],
|
||||
identityRepository = get(),
|
||||
userRepository = get(),
|
||||
postsRepository = get(),
|
||||
commentRepository = get(),
|
||||
hapticFeedback = get(),
|
||||
notificationCenter = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.di
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.ProfileContentViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments.ProfileCommentsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved.ProfileSavedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.login.LoginBottomSheetViewModel
|
||||
|
||||
expect fun getProfileScreenModel(): ProfileContentViewModel
|
||||
@ -13,9 +12,4 @@ expect fun getLoginBottomSheetViewModel(): LoginBottomSheetViewModel
|
||||
|
||||
expect fun getProfileLoggedViewModel(): ProfileLoggedViewModel
|
||||
|
||||
expect fun getProfilePostsViewModel(
|
||||
user: UserModel,
|
||||
savedOnly: Boolean = false,
|
||||
): ProfilePostsViewModel
|
||||
|
||||
expect fun getProfileCommentsViewModel(user: UserModel): ProfileCommentsViewModel
|
||||
expect fun getProfileSavedViewModel(user: UserModel): ProfileSavedViewModel
|
||||
|
@ -3,8 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.profile.di
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.ProfileContentViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.ProfileLoggedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.comments.ProfileCommentsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.logged.posts.ProfilePostsViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.content.saved.ProfileSavedViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature.profile.login.LoginBottomSheetViewModel
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
@ -17,29 +16,19 @@ actual fun getLoginBottomSheetViewModel() = ProfileScreenModelHelper.loginModel
|
||||
actual fun getProfileLoggedViewModel(): ProfileLoggedViewModel =
|
||||
ProfileScreenModelHelper.loggedModel
|
||||
|
||||
actual fun getProfilePostsViewModel(
|
||||
actual fun getProfileSavedViewModel(
|
||||
user: UserModel,
|
||||
savedOnly: Boolean,
|
||||
): ProfilePostsViewModel =
|
||||
ProfileScreenModelHelper.getPostsModel(user = user, savedOnly = savedOnly)
|
||||
|
||||
actual fun getProfileCommentsViewModel(user: UserModel): ProfileCommentsViewModel =
|
||||
ProfileScreenModelHelper.getCommentsModel(user)
|
||||
): ProfileSavedViewModel =
|
||||
ProfileScreenModelHelper.getSavedModel(user = user)
|
||||
|
||||
object ProfileScreenModelHelper : KoinComponent {
|
||||
val profileModel: ProfileContentViewModel by inject()
|
||||
val loginModel: LoginBottomSheetViewModel by inject()
|
||||
val loggedModel: ProfileLoggedViewModel by inject()
|
||||
|
||||
fun getPostsModel(user: UserModel, savedOnly: Boolean): ProfilePostsViewModel {
|
||||
val res: ProfilePostsViewModel by inject(
|
||||
parameters = { parametersOf(user, savedOnly) },
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
fun getCommentsModel(user: UserModel): ProfileCommentsViewModel {
|
||||
val res: ProfileCommentsViewModel by inject(
|
||||
fun getSavedModel(user: UserModel): ProfileSavedViewModel {
|
||||
val res: ProfileSavedViewModel by inject(
|
||||
parameters = { parametersOf(user) },
|
||||
)
|
||||
return res
|
||||
|
Loading…
x
Reference in New Issue
Block a user