feat(settings): add options to manage nsfw

This commit is contained in:
Diego Beraldin 2023-09-03 22:53:04 +02:00
parent a7120f1617
commit 376f84cfef
23 changed files with 109 additions and 12 deletions

View File

@ -27,6 +27,7 @@ interface CommunityDetailMviModel :
val canFetchMore: Boolean = true, val canFetchMore: Boolean = true,
val sortType: SortType = SortType.Active, val sortType: SortType = SortType.Active,
val posts: List<PostModel> = emptyList(), val posts: List<PostModel> = emptyList(),
val blurNsfw: Boolean = true,
) )
sealed interface Effect sealed interface Effect

View File

@ -331,7 +331,7 @@ class CommunityDetailScreen(
} }
} }
} }
items(uiState.posts, key = { "${it.id}+${it.myVote}" }) { post -> items(uiState.posts) { post ->
SwipeableCard( SwipeableCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
directions = if (isOnOtherInstance) { directions = if (isOnOtherInstance) {
@ -432,7 +432,7 @@ class CommunityDetailScreen(
post = post, post = post,
blurNsfw = when { blurNsfw = when {
community.nsfw -> false community.nsfw -> false
else -> true else -> uiState.blurNsfw
}, },
onUpVote = if (isOnOtherInstance) { onUpVote = if (isOnOtherInstance) {
null null

View File

@ -38,6 +38,7 @@ class CommunityDetailViewModel(
it.copy( it.copy(
community = community, community = community,
sortType = sortType, sortType = sortType,
blurNsfw = keyStore[KeyStoreKeys.BlurNsfw, true],
) )
} }

View File

@ -62,6 +62,7 @@ val commonUiModule = module {
userRepository = get(), userRepository = get(),
postsRepository = get(), postsRepository = get(),
hapticFeedback = get(), hapticFeedback = get(),
keyStore = get(),
) )
} }
factory { factory {

View File

@ -129,7 +129,7 @@ class InstanceInfoScreen(
) )
} }
} }
items(uiState.communities, key = { it.id }) { items(uiState.communities) {
val themeRepository = remember { getThemeRepository() } val themeRepository = remember { getThemeRepository() }
val fontScale by themeRepository.contentFontScale.collectAsState() val fontScale by themeRepository.contentFontScale.collectAsState()
CompositionLocalProvider( CompositionLocalProvider(

View File

@ -205,7 +205,7 @@ class PostDetailScreen(
) )
} }
} }
items(uiState.comments, key = { "${it.id}+${it.myVote}" }) { comment -> items(uiState.comments) { comment ->
SwipeableCard( SwipeableCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
backgroundColor = { backgroundColor = {

View File

@ -112,7 +112,7 @@ internal class UserDetailCommentsScreen(
) )
} }
} }
items(uiState.comments, key = { "${it.id}+${it.myVote}" }) { comment -> items(uiState.comments) { comment ->
SwipeableCard( SwipeableCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
backgroundColor = { backgroundColor = {

View File

@ -121,7 +121,7 @@ internal class UserDetailPostsScreen(
) )
} }
} }
items(uiState.posts, key = { "${it.id}+${it.myVote}" }) { post -> items(uiState.posts) { post ->
SwipeableCard( SwipeableCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
backgroundColor = { backgroundColor = {
@ -193,7 +193,7 @@ internal class UserDetailPostsScreen(
content = { content = {
PostCard( PostCard(
post = post, post = post,
blurNsfw = true, blurNsfw = uiState.blurNsfw,
onUpVote = { onUpVote = {
model.reduce( model.reduce(
UserPostsMviModel.Intent.UpVotePost( UserPostsMviModel.Intent.UpVotePost(

View File

@ -25,6 +25,7 @@ interface UserPostsMviModel :
val posts: List<PostModel> = emptyList(), val posts: List<PostModel> = emptyList(),
val user: UserModel = UserModel(), val user: UserModel = UserModel(),
val sortType: SortType = SortType.Active, val sortType: SortType = SortType.Active,
val blurNsfw: Boolean = true,
) )
sealed interface Effect sealed interface Effect

View File

@ -4,6 +4,8 @@ import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.racconforlemmy.core.utils.HapticFeedback import com.github.diegoberaldin.racconforlemmy.core.utils.HapticFeedback
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.PostModel
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.SortType
@ -21,6 +23,7 @@ class UserPostsViewModel(
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val postsRepository: PostsRepository, private val postsRepository: PostsRepository,
private val hapticFeedback: HapticFeedback, private val hapticFeedback: HapticFeedback,
private val keyStore: TemporaryKeyStore,
) : ScreenModel, ) : ScreenModel,
MviModel<UserPostsMviModel.Intent, UserPostsMviModel.UiState, UserPostsMviModel.Effect> by mvi { MviModel<UserPostsMviModel.Intent, UserPostsMviModel.UiState, UserPostsMviModel.Effect> by mvi {
@ -31,7 +34,12 @@ class UserPostsViewModel(
mvi.scope.launch(Dispatchers.IO) { mvi.scope.launch(Dispatchers.IO) {
val user = userRepository.get(user.id) val user = userRepository.get(user.id)
if (user != null) { if (user != null) {
mvi.updateState { it.copy(user = user) } mvi.updateState {
it.copy(
user = user,
blurNsfw = keyStore[KeyStoreKeys.BlurNsfw, true],
)
}
} }
} }
} }

View File

@ -9,4 +9,6 @@ object KeyStoreKeys {
const val DefaultListingType = "defaultListingType" const val DefaultListingType = "defaultListingType"
const val DefaultPostSortType = "defaultPostSortType" const val DefaultPostSortType = "defaultPostSortType"
const val DefaultCommentSortType = "defaultCommentSortType" const val DefaultCommentSortType = "defaultCommentSortType"
const val IncludeNsfw = "includeNsfw"
const val BlurNsfw = "blurNsfw"
} }

View File

@ -28,6 +28,7 @@ interface PostListMviModel :
val listingType: ListingType = ListingType.Local, val listingType: ListingType = ListingType.Local,
val sortType: SortType = SortType.Active, val sortType: SortType = SortType.Active,
val posts: List<PostModel> = emptyList(), val posts: List<PostModel> = emptyList(),
val blurNsfw: Boolean = true,
) )
sealed interface Effect sealed interface Effect

View File

@ -105,7 +105,7 @@ class PostListScreen : Screen {
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(Spacing.xs), verticalArrangement = Arrangement.spacedBy(Spacing.xs),
) { ) {
items(uiState.posts, key = { "${it.id}+${it.myVote}" }) { post -> items(uiState.posts) { post ->
SwipeableCard( SwipeableCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onGestureBegin = { onGestureBegin = {
@ -178,7 +178,7 @@ class PostListScreen : Screen {
) )
}, },
post = post, post = post,
blurNsfw = true, blurNsfw = uiState.blurNsfw,
onOpenCommunity = { community -> onOpenCommunity = { community ->
navigator.push( navigator.push(
CommunityDetailScreen( CommunityDetailScreen(

View File

@ -74,6 +74,7 @@ class PostListViewModel(
instance = apiConfigRepository.getInstance(), instance = apiConfigRepository.getInstance(),
listingType = listingType, listingType = listingType,
sortType = sortType, sortType = sortType,
blurNsfw = keyStore[KeyStoreKeys.BlurNsfw, true],
) )
} }
@ -123,6 +124,7 @@ class PostListViewModel(
val type = currentState.listingType val type = currentState.listingType
val sort = currentState.sortType val sort = currentState.sortType
val refreshing = currentState.refreshing val refreshing = currentState.refreshing
val includeNsfw = keyStore[KeyStoreKeys.IncludeNsfw, true]
val postList = postsRepository.getAll( val postList = postsRepository.getAll(
auth = auth, auth = auth,
page = currentPage, page = currentPage,
@ -136,6 +138,12 @@ class PostListViewModel(
postList postList
} else { } else {
it.posts + postList it.posts + postList
}.filter { post ->
if (includeNsfw) {
true
} else {
!post.nsfw
}
} }
it.copy( it.copy(
posts = newPosts, posts = newPosts,

View File

@ -91,7 +91,7 @@ class InboxMentionsScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(Spacing.xs), verticalArrangement = Arrangement.spacedBy(Spacing.xs),
) { ) {
items(uiState.mentions, key = { it.id }) { mention -> items(uiState.mentions) { mention ->
SwipeableCard( SwipeableCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
backgroundColor = { backgroundColor = {

View File

@ -91,7 +91,7 @@ class InboxRepliesScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(Spacing.xs), verticalArrangement = Arrangement.spacedBy(Spacing.xs),
) { ) {
items(uiState.mentions, key = { it.id }) { mention -> items(uiState.mentions) { mention ->
SwipeableCard( SwipeableCard(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
backgroundColor = { backgroundColor = {

View File

@ -51,6 +51,7 @@ kotlin {
implementation(projects.coreArchitecture) implementation(projects.coreArchitecture)
implementation(projects.coreAppearance) implementation(projects.coreAppearance)
implementation(projects.coreUtils) implementation(projects.coreUtils)
implementation(projects.corePreferences)
implementation(projects.coreCommonui) implementation(projects.coreCommonui)
implementation(projects.domainIdentity) implementation(projects.domainIdentity)

View File

@ -3,6 +3,8 @@ package com.github.diegoberaldin.raccoonforlemmy.feature.search.communitylist
import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel
import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.KeyStoreKeys
import com.github.diegoberaldin.raccoonforlemmy.core.preferences.TemporaryKeyStore
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.ApiConfigurationRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository
import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.ListingType
@ -22,6 +24,7 @@ class CommunityListViewModel(
private val apiConfigRepository: ApiConfigurationRepository, private val apiConfigRepository: ApiConfigurationRepository,
private val identityRepository: IdentityRepository, private val identityRepository: IdentityRepository,
private val communityRepository: CommunityRepository, private val communityRepository: CommunityRepository,
private val keyStore: TemporaryKeyStore,
) : ScreenModel, ) : ScreenModel,
MviModel<CommunityListMviModel.Intent, CommunityListMviModel.UiState, CommunityListMviModel.Effect> by mvi { MviModel<CommunityListMviModel.Intent, CommunityListMviModel.UiState, CommunityListMviModel.Effect> by mvi {
@ -102,6 +105,7 @@ class CommunityListViewModel(
val refreshing = currentState.refreshing val refreshing = currentState.refreshing
val listingType = currentState.listingType val listingType = currentState.listingType
val sortType = currentState.sortType val sortType = currentState.sortType
val inclueNsfw = keyStore[KeyStoreKeys.IncludeNsfw, true]
val items = communityRepository.getAll( val items = communityRepository.getAll(
query = searchText, query = searchText,
auth = auth, auth = auth,
@ -116,6 +120,12 @@ class CommunityListViewModel(
items items
} else { } else {
it.communities + items it.communities + items
}.filter { community ->
if (inclueNsfw) {
true
} else {
!community.nsfw
}
} }
it.copy( it.copy(
communities = newItems, communities = newItems,

View File

@ -12,6 +12,7 @@ val searchTabModule = module {
apiConfigRepository = get(), apiConfigRepository = get(),
identityRepository = get(), identityRepository = get(),
communityRepository = get(), communityRepository = get(),
keyStore = get(),
) )
} }
} }

View File

@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -36,3 +37,26 @@ internal fun SettingsRow(
) )
} }
} }
@Composable
internal fun SettingsSwitchRow(
title: String,
value: Boolean,
onValueChanged: (Boolean) -> Unit,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
)
Spacer(modifier = Modifier.weight(1f))
Switch(
checked = value,
onCheckedChange = {
onValueChanged(it)
}
)
}
}

View File

@ -209,6 +209,21 @@ class SettingsScreen : Screen {
) )
}, },
) )
// TODO!
SettingsSwitchRow(
title = "Include NSFW contents",
value = uiState.includeNsfw,
onValueChanged = { value ->
model.reduce(SettingsScreenMviModel.Intent.ChangeIncludeNsfw(value))
}
)
SettingsSwitchRow(
title = "Blur NSFW",
value = uiState.blurNsfw,
onValueChanged = { value ->
model.reduce(SettingsScreenMviModel.Intent.ChangeBlurNsfw(value))
}
)
} }
} }
} }

View File

@ -16,6 +16,8 @@ interface SettingsScreenMviModel :
data class ChangeDefaultListingType(val value: ListingType) : Intent data class ChangeDefaultListingType(val value: ListingType) : Intent
data class ChangeDefaultPostSortType(val value: SortType) : Intent data class ChangeDefaultPostSortType(val value: SortType) : Intent
data class ChangeDefaultCommentSortType(val value: SortType) : Intent data class ChangeDefaultCommentSortType(val value: SortType) : Intent
data class ChangeIncludeNsfw(val value: Boolean) : Intent
data class ChangeBlurNsfw(val value: Boolean) : Intent
} }
data class UiState( data class UiState(
@ -26,6 +28,8 @@ interface SettingsScreenMviModel :
val defaultListingType: ListingType = ListingType.Local, val defaultListingType: ListingType = ListingType.Local,
val defaultPostSortType: SortType = SortType.Active, val defaultPostSortType: SortType = SortType.Active,
val defaultCommentSortType: SortType = SortType.New, val defaultCommentSortType: SortType = SortType.New,
val includeNsfw: Boolean = true,
val blurNsfw: Boolean = true,
) )
sealed interface Effect sealed interface Effect

View File

@ -55,6 +55,8 @@ class SettingsScreenViewModel(
defaultListingType = listingType, defaultListingType = listingType,
defaultPostSortType = postSortType, defaultPostSortType = postSortType,
defaultCommentSortType = commentSortType, defaultCommentSortType = commentSortType,
includeNsfw = keyStore[KeyStoreKeys.IncludeNsfw, true],
blurNsfw = keyStore[KeyStoreKeys.BlurNsfw, true],
) )
} }
} }
@ -75,6 +77,9 @@ class SettingsScreenViewModel(
is SettingsScreenMviModel.Intent.ChangeDefaultPostSortType -> changeDefaultPostSortType( is SettingsScreenMviModel.Intent.ChangeDefaultPostSortType -> changeDefaultPostSortType(
intent.value, intent.value,
) )
is SettingsScreenMviModel.Intent.ChangeBlurNsfw -> changeBlurNsfw(intent.value)
is SettingsScreenMviModel.Intent.ChangeIncludeNsfw -> changeIncludeNsfw(intent.value)
} }
} }
@ -119,4 +124,18 @@ class SettingsScreenViewModel(
keyStore.save(KeyStoreKeys.DefaultCommentSortType, value.toInt()) keyStore.save(KeyStoreKeys.DefaultCommentSortType, value.toInt())
} }
} }
private fun changeIncludeNsfw(value: Boolean) {
mvi.updateState { it.copy(includeNsfw = value) }
mvi.scope.launch(Dispatchers.Main) {
keyStore.save(KeyStoreKeys.IncludeNsfw, value)
}
}
private fun changeBlurNsfw(value: Boolean) {
mvi.updateState { it.copy(blurNsfw = value) }
mvi.scope.launch(Dispatchers.Main) {
keyStore.save(KeyStoreKeys.BlurNsfw, value)
}
}
} }