mirror of
https://github.com/LiveFastEatTrashRaccoon/RaccoonForLemmy.git
synced 2025-02-09 12:48:42 +01:00
feat(profile): add comment section
This commit is contained in:
parent
c3e81fc3ae
commit
ec5786c458
@ -1,5 +1,7 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.domain_lemmy.data
|
||||
|
||||
data class CommentModel(
|
||||
val id: Int = 0,
|
||||
val text: String,
|
||||
val community: CommunityModel? = null,
|
||||
)
|
||||
|
@ -74,7 +74,9 @@ internal fun PostView.toModel() = PostModel(
|
||||
)
|
||||
|
||||
internal fun CommentView.toModel() = CommentModel(
|
||||
id = comment.id,
|
||||
text = comment.content,
|
||||
community = community.toModel(),
|
||||
)
|
||||
|
||||
internal fun Community.toModel() = CommunityModel(
|
||||
|
@ -2,6 +2,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.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.login.LoginBottomSheetViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_profile.viewmodel.ProfileScreenModel
|
||||
@ -30,3 +31,11 @@ actual fun getProfilePostsViewModel(user: UserModel): ProfilePostsViewModel {
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
actual fun getProfileCommentsViewModel(user: UserModel): ProfileCommentsViewModel {
|
||||
val res: ProfileCommentsViewModel by inject(
|
||||
clazz = ProfileCommentsViewModel::class.java,
|
||||
parameters = { parametersOf(user) },
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
@ -2,10 +2,8 @@ package com.github.diegoberaldin.raccoonforlemmy.feature_profile.content.logged
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
@ -17,6 +15,7 @@ import cafe.adriel.voyager.core.screen.Screen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_lemmy.data.UserModel
|
||||
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.di.getProfileLoggedViewModel
|
||||
|
||||
@ -36,11 +35,11 @@ internal class ProfileLoggedScreen(
|
||||
val uiState by model.uiState.collectAsState()
|
||||
|
||||
ProfileLoggedHeader(user = user)
|
||||
|
||||
ProfileLoggedCounters(user = user)
|
||||
|
||||
Spacer(modifier = Modifier.height(Spacing.s))
|
||||
|
||||
SectionSelector(
|
||||
modifier = Modifier.padding(vertical = Spacing.xs),
|
||||
currentSection = uiState.currentTab,
|
||||
onSectionSelected = {
|
||||
model.reduce(ProfileLoggedMviModel.Intent.SelectTab(it))
|
||||
@ -55,6 +54,10 @@ internal class ProfileLoggedScreen(
|
||||
}
|
||||
|
||||
ProfileLoggedSection.COMMENTS -> {
|
||||
ProfileCommentsScreen(
|
||||
modifier = Modifier.weight(1f).fillMaxWidth(),
|
||||
user = user,
|
||||
).Content()
|
||||
}
|
||||
|
||||
ProfileLoggedSection.SAVED -> {
|
||||
|
@ -24,12 +24,16 @@ import dev.icerock.moko.resources.compose.stringResource
|
||||
|
||||
@Composable
|
||||
internal fun SectionSelector(
|
||||
modifier: Modifier = Modifier,
|
||||
currentSection: ProfileLoggedSection,
|
||||
onSectionSelected: (ProfileLoggedSection) -> Unit,
|
||||
) {
|
||||
val highlightColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
|
||||
Row(
|
||||
modifier = Modifier.height(25.dp).padding(horizontal = Spacing.m).fillMaxWidth()
|
||||
modifier = modifier
|
||||
.height(34.dp)
|
||||
.padding(horizontal = Spacing.m)
|
||||
.fillMaxWidth()
|
||||
.border(
|
||||
color = highlightColor,
|
||||
width = 1.dp,
|
||||
@ -126,4 +130,4 @@ internal fun SectionSelector(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
package com.github.diegoberaldin.raccoonforlemmy.feature_profile.content.logged.comments
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.material3.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.theme.CornerSize
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_md.compose.Markdown
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_lemmy.data.CommentModel
|
||||
import io.kamel.image.KamelImage
|
||||
import io.kamel.image.asyncPainterResource
|
||||
|
||||
@Composable
|
||||
fun ProfileCommentCard(
|
||||
comment: CommentModel,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = RoundedCornerShape(CornerSize.m),
|
||||
).padding(
|
||||
vertical = Spacing.lHalf,
|
||||
horizontal = Spacing.s,
|
||||
),
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.s),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(Spacing.xxs),
|
||||
) {
|
||||
val communityName = comment.community?.name.orEmpty()
|
||||
val communityIcon = comment.community?.icon.orEmpty()
|
||||
val communityHost = comment.community?.host.orEmpty()
|
||||
val iconSize = 16.dp
|
||||
if (communityName.isNotEmpty()) {
|
||||
if (communityIcon.isNotEmpty()) {
|
||||
val painterResource = asyncPainterResource(data = communityIcon)
|
||||
KamelImage(
|
||||
modifier = Modifier.size(iconSize)
|
||||
.clip(RoundedCornerShape(iconSize / 2)),
|
||||
resource = painterResource,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = buildString {
|
||||
append(communityName)
|
||||
if (communityHost.isNotEmpty()) {
|
||||
append("@$communityHost")
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
val body = comment.text
|
||||
if (body.isNotEmpty()) {
|
||||
Markdown(content = body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
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 UiState(
|
||||
val refreshing: Boolean = false,
|
||||
val loading: Boolean = false,
|
||||
val canFetchMore: Boolean = true,
|
||||
val comments: List<CommentModel> = emptyList(),
|
||||
)
|
||||
|
||||
sealed interface Effect
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
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.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.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.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_appearance.theme.Spacing
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.bindToLifecycle
|
||||
import com.github.diegoberaldin.raccoonforlemmy.domain_lemmy.data.UserModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_profile.di.getProfileCommentsViewModel
|
||||
|
||||
internal class ProfileCommentsScreen(
|
||||
private val modifier: Modifier = Modifier,
|
||||
private val user: UserModel,
|
||||
) : Screen {
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val model = rememberScreenModel { getProfileCommentsViewModel(user) }
|
||||
model.bindToLifecycle(key)
|
||||
val uiState by model.uiState.collectAsState()
|
||||
|
||||
val pullRefreshState = rememberPullRefreshState(uiState.refreshing, {
|
||||
model.reduce(ProfileCommentsMviModel.Intent.Refresh)
|
||||
})
|
||||
Box(
|
||||
modifier = modifier.pullRefresh(pullRefreshState),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(Spacing.xs),
|
||||
) {
|
||||
items(uiState.comments) { comment ->
|
||||
ProfileCommentCard(comment)
|
||||
}
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
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.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 ProfileCommentsViewModel(
|
||||
private val mvi: DefaultMviModel<ProfileCommentsMviModel.Intent, ProfileCommentsMviModel.UiState, ProfileCommentsMviModel.Effect>,
|
||||
private val user: UserModel,
|
||||
private val identityRepository: IdentityRepository,
|
||||
private val userRepository: UserRepository,
|
||||
) : ScreenModel,
|
||||
MviModel<ProfileCommentsMviModel.Intent, ProfileCommentsMviModel.UiState, ProfileCommentsMviModel.Effect> by mvi {
|
||||
|
||||
private var currentPage: Int = 1
|
||||
override fun onStarted() {
|
||||
mvi.onStarted()
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun reduce(intent: ProfileCommentsMviModel.Intent) {
|
||||
when (intent) {
|
||||
ProfileCommentsMviModel.Intent.LoadNextPage -> loadNextPage()
|
||||
ProfileCommentsMviModel.Intent.Refresh -> refresh()
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
return
|
||||
}
|
||||
|
||||
mvi.scope.launch(Dispatchers.IO) {
|
||||
mvi.updateState { it.copy(loading = true) }
|
||||
val auth = identityRepository.authToken.value
|
||||
val refreshing = currentState.refreshing
|
||||
val commentList = userRepository.getUserComments(
|
||||
auth = auth,
|
||||
id = user.id,
|
||||
page = currentPage,
|
||||
)
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ package com.github.diegoberaldin.raccoonforlemmy.feature_profile.di
|
||||
import com.github.diegoberaldin.raccoonforlemmy.core_architecture.DefaultMviModel
|
||||
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.login.LoginBottomSheetMviModel
|
||||
@ -38,4 +40,12 @@ val profileTabModule = module {
|
||||
userRepository = get(),
|
||||
)
|
||||
}
|
||||
factory { params ->
|
||||
ProfileCommentsViewModel(
|
||||
mvi = DefaultMviModel(ProfileCommentsMviModel.UiState()),
|
||||
user = params[0],
|
||||
identityRepository = get(),
|
||||
userRepository = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,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.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.login.LoginBottomSheetViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_profile.viewmodel.ProfileScreenModel
|
||||
@ -13,3 +14,5 @@ expect fun getLoginBottomSheetViewModel(): LoginBottomSheetViewModel
|
||||
expect fun getProfileLoggedViewModel(): ProfileLoggedViewModel
|
||||
|
||||
expect fun getProfilePostsViewModel(user: UserModel): ProfilePostsViewModel
|
||||
|
||||
expect fun getProfileCommentsViewModel(user: UserModel): ProfileCommentsViewModel
|
||||
|
@ -2,6 +2,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.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.login.LoginBottomSheetViewModel
|
||||
import com.github.diegoberaldin.raccoonforlemmy.feature_profile.viewmodel.ProfileScreenModel
|
||||
@ -19,6 +20,9 @@ actual fun getProfileLoggedViewModel(): ProfileLoggedViewModel =
|
||||
actual fun getProfilePostsViewModel(user: UserModel): ProfilePostsViewModel =
|
||||
ProfileScreenModelHelper.getPostsModel(user)
|
||||
|
||||
actual fun getProfileCommentsViewModel(user: UserModel): ProfileCommentsViewModel =
|
||||
ProfileScreenModelHelper.getCommentsModel(user)
|
||||
|
||||
object ProfileScreenModelHelper : KoinComponent {
|
||||
val profileModel: ProfileScreenModel by inject()
|
||||
val loginModel: LoginBottomSheetViewModel by inject()
|
||||
@ -30,4 +34,11 @@ object ProfileScreenModelHelper : KoinComponent {
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
fun getCommentsModel(user: UserModel): ProfileCommentsViewModel {
|
||||
val res: ProfileCommentsViewModel by inject(
|
||||
parameters = { parametersOf(user) },
|
||||
)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user