mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3210 Space-level chats | Enhancement | Save recently used chat reactions and display it in the dedicated section of the reaction picker (#1967)
This commit is contained in:
parent
f38f3fd8d6
commit
5edb38a440
12 changed files with 183 additions and 16 deletions
|
@ -4,8 +4,10 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.data.auth.repo.block.BlockRemote
|
||||
import com.anytypeio.anytype.di.common.ComponentDependencies
|
||||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import com.anytypeio.anytype.emojifier.data.Emoji
|
||||
import com.anytypeio.anytype.emojifier.data.EmojiProvider
|
||||
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
|
||||
|
@ -59,4 +61,6 @@ interface ChatReactionPickerDependencies : ComponentDependencies {
|
|||
fun dispatchers(): AppCoroutineDispatchers
|
||||
fun suggester(): EmojiSuggester
|
||||
fun repo(): BlockRepository
|
||||
fun auth(): AuthRepository
|
||||
fun prefs(): UserSettingsRepository
|
||||
}
|
|
@ -11,7 +11,6 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
|
|||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_utils.ext.arg
|
||||
|
@ -22,7 +21,6 @@ import com.anytypeio.anytype.feature_discussions.ui.ChatReactionPicker
|
|||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import javax.inject.Inject
|
||||
import kotlin.getValue
|
||||
import okhttp3.internal.notify
|
||||
|
||||
class ChatReactionFragment : BaseBottomSheetComposeFragment() {
|
||||
|
||||
|
@ -45,7 +43,7 @@ class ChatReactionFragment : BaseBottomSheetComposeFragment() {
|
|||
typography = typography
|
||||
) {
|
||||
ChatReactionPicker(
|
||||
views = vm.default.collectAsStateWithLifecycle().value,
|
||||
views = vm.views.collectAsStateWithLifecycle(emptyList()).value,
|
||||
onEmojiClicked = vm::onEmojiClicked
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
|
@ -49,4 +49,7 @@ interface UserSettingsCache {
|
|||
|
||||
suspend fun setRelativeDates(account: Account, enabled: Boolean)
|
||||
suspend fun setDateFormat(account: Account, format: String)
|
||||
|
||||
suspend fun setRecentlyUsedChatReactions(account: Account, emojis: Set<String>)
|
||||
fun observeRecentlyUsedChatReactions(account: Account): Flow<List<String>>
|
||||
}
|
|
@ -131,4 +131,12 @@ class UserSettingsDataRepository(private val cache: UserSettingsCache) : UserSet
|
|||
) {
|
||||
cache.setDateFormat(account, format)
|
||||
}
|
||||
|
||||
override suspend fun setRecentlyUsedChatReactions(account: Account, emojis: Set<String>) {
|
||||
cache.setRecentlyUsedChatReactions(account, emojis)
|
||||
}
|
||||
|
||||
override fun observeRecentlyUsedChatReactions(account: Account,): Flow<List<String>> {
|
||||
return cache.observeRecentlyUsedChatReactions(account)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package com.anytypeio.anytype.domain.chats
|
||||
|
||||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.FlowInteractor
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
|
||||
class ObserveRecentlyUsedChatReactions @Inject constructor(
|
||||
private val auth: AuthRepository,
|
||||
private val repo: UserSettingsRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : FlowInteractor<Unit, List<String>>(dispatchers.io) {
|
||||
|
||||
override fun build(): Flow<List<String>> = flow {
|
||||
val acc = auth.getCurrentAccount()
|
||||
emitAll(repo.observeRecentlyUsedChatReactions(acc))
|
||||
}
|
||||
|
||||
override fun build(params: Unit): Flow<List<String>> = flow {
|
||||
val acc = auth.getCurrentAccount()
|
||||
emitAll(repo.observeRecentlyUsedChatReactions(acc))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.anytypeio.anytype.domain.chats
|
||||
|
||||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.config.UserSettingsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class SetRecentlyUsedChatReactions @Inject constructor(
|
||||
private val auth: AuthRepository,
|
||||
private val repo: UserSettingsRepository,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
) : ResultInteractor<Set<String>, Unit>(dispatchers.io) {
|
||||
|
||||
override suspend fun doWork(params: Set<String>) {
|
||||
val acc = auth.getCurrentAccount()
|
||||
repo.setRecentlyUsedChatReactions(
|
||||
account = acc,
|
||||
emojis = params
|
||||
)
|
||||
}
|
||||
}
|
|
@ -53,4 +53,7 @@ interface UserSettingsRepository {
|
|||
|
||||
suspend fun setRelativeDates(account: Account, enabled: Boolean)
|
||||
suspend fun setDateFormat(account: Account, format: String)
|
||||
|
||||
suspend fun setRecentlyUsedChatReactions(account: Account, emojis: Set<String>)
|
||||
fun observeRecentlyUsedChatReactions(account: Account): Flow<List<String>>
|
||||
}
|
|
@ -5,27 +5,32 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.core_models.Command
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_ui.features.editor.holders.text.Toggle
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.onFailure
|
||||
import com.anytypeio.anytype.domain.chats.ObserveRecentlyUsedChatReactions
|
||||
import com.anytypeio.anytype.domain.chats.SetRecentlyUsedChatReactions
|
||||
import com.anytypeio.anytype.domain.chats.ToggleChatMessageReaction
|
||||
import com.anytypeio.anytype.emojifier.Emojifier
|
||||
import com.anytypeio.anytype.emojifier.data.Emoji
|
||||
import com.anytypeio.anytype.emojifier.data.EmojiProvider
|
||||
import com.anytypeio.anytype.emojifier.suggest.EmojiSuggester
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.picker.EmojiPickerView
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
class ChatReactionViewModel @Inject constructor(
|
||||
private val vmParams: Params,
|
||||
private val provider: EmojiProvider,
|
||||
private val suggester: EmojiSuggester,
|
||||
private val dispatchers: AppCoroutineDispatchers,
|
||||
private val toggleChatMessageReaction: ToggleChatMessageReaction
|
||||
private val toggleChatMessageReaction: ToggleChatMessageReaction,
|
||||
private val setRecentlyUsedChatReactions: SetRecentlyUsedChatReactions,
|
||||
private val observeRecentlyUsedChatReactions: ObserveRecentlyUsedChatReactions
|
||||
) : BaseViewModel() {
|
||||
|
||||
val isDismissed = MutableSharedFlow<Boolean>(replay = 0)
|
||||
|
@ -33,9 +38,37 @@ class ChatReactionViewModel @Inject constructor(
|
|||
/**
|
||||
* Default emoji list, including categories.
|
||||
*/
|
||||
val default = MutableStateFlow<List<ReactionPickerView>>(emptyList())
|
||||
private val default = MutableStateFlow<List<ReactionPickerView>>(emptyList())
|
||||
|
||||
private val recentlyUsed = MutableStateFlow<List<String>>(emptyList())
|
||||
|
||||
val views = combine(default, recentlyUsed) { default, recentlyUsed ->
|
||||
buildList<ReactionPickerView> {
|
||||
if (recentlyUsed.isNotEmpty()) {
|
||||
add(ReactionPickerView.RecentUsedSection)
|
||||
addAll(
|
||||
recentlyUsed.map { unicode ->
|
||||
ReactionPickerView.Emoji(
|
||||
unicode = unicode,
|
||||
page = -1,
|
||||
index = -1,
|
||||
emojified = Emojifier.safeUri(unicode)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
addAll(default)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
observeRecentlyUsedChatReactions
|
||||
.flow()
|
||||
.collect {
|
||||
recentlyUsed.value = it
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val loaded = loadEmojiWithCategories()
|
||||
default.value = loaded
|
||||
|
@ -72,13 +105,23 @@ class ChatReactionViewModel @Inject constructor(
|
|||
|
||||
fun onEmojiClicked(emoji: String) {
|
||||
viewModelScope.launch {
|
||||
toggleChatMessageReaction.execute(
|
||||
setRecentlyUsedChatReactions.async(
|
||||
params = (listOf(emoji) + recentlyUsed.value)
|
||||
.toSet()
|
||||
.take(MAX_RECENTLY_USED_COUNT)
|
||||
.toSet()
|
||||
).onFailure {
|
||||
Timber.e(it, "Error while saving recently used reactions")
|
||||
}
|
||||
toggleChatMessageReaction.async(
|
||||
params = Command.ChatCommand.ToggleMessageReaction(
|
||||
msg = vmParams.msg,
|
||||
chat = vmParams.chat,
|
||||
emoji = emoji
|
||||
)
|
||||
)
|
||||
).onFailure {
|
||||
Timber.e(it, "Error while toggling chat message reaction")
|
||||
}
|
||||
isDismissed.emit(true)
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +131,9 @@ class ChatReactionViewModel @Inject constructor(
|
|||
private val emojiProvider: EmojiProvider,
|
||||
private val emojiSuggester: EmojiSuggester,
|
||||
private val dispatchers: AppCoroutineDispatchers,
|
||||
private val toggleChatMessageReaction: ToggleChatMessageReaction
|
||||
private val toggleChatMessageReaction: ToggleChatMessageReaction,
|
||||
private val setRecentlyUsedChatReactions: SetRecentlyUsedChatReactions,
|
||||
private val observeRecentlyUsedChatReactions: ObserveRecentlyUsedChatReactions
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = ChatReactionViewModel(
|
||||
|
@ -96,7 +141,9 @@ class ChatReactionViewModel @Inject constructor(
|
|||
provider = emojiProvider,
|
||||
suggester = emojiSuggester,
|
||||
dispatchers = dispatchers,
|
||||
toggleChatMessageReaction = toggleChatMessageReaction
|
||||
toggleChatMessageReaction = toggleChatMessageReaction,
|
||||
setRecentlyUsedChatReactions = setRecentlyUsedChatReactions,
|
||||
observeRecentlyUsedChatReactions = observeRecentlyUsedChatReactions
|
||||
) as T
|
||||
}
|
||||
|
||||
|
@ -106,6 +153,7 @@ class ChatReactionViewModel @Inject constructor(
|
|||
)
|
||||
|
||||
sealed class ReactionPickerView {
|
||||
data object RecentUsedSection: ReactionPickerView()
|
||||
data class Category(val index: Int) : ReactionPickerView()
|
||||
data class Emoji(
|
||||
val unicode: String,
|
||||
|
@ -114,4 +162,8 @@ class ChatReactionViewModel @Inject constructor(
|
|||
val emojified: String = ""
|
||||
) : ReactionPickerView()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_RECENTLY_USED_COUNT = 20
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -42,7 +44,7 @@ fun ChatReactionPicker(
|
|||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(6),
|
||||
modifier = Modifier
|
||||
.systemBarsPadding()
|
||||
.nestedScroll(rememberNestedScrollInteropConnection())
|
||||
.fillMaxSize()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
|
@ -58,8 +60,7 @@ fun ChatReactionPicker(
|
|||
is ReactionPickerView.Emoji -> {
|
||||
GridItemSpan(1)
|
||||
}
|
||||
|
||||
is ReactionPickerView.Category -> {
|
||||
is ReactionPickerView.Category, is ReactionPickerView.RecentUsedSection -> {
|
||||
GridItemSpan(maxLineSpan)
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +85,6 @@ fun ChatReactionPicker(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
is ReactionPickerView.Category -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
@ -110,6 +110,20 @@ fun ChatReactionPicker(
|
|||
)
|
||||
}
|
||||
}
|
||||
is ReactionPickerView.RecentUsedSection -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.emoji_recently_used_section),
|
||||
color = colorResource(R.color.text_secondary),
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
style = Caption1Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1861,5 +1861,6 @@ Please provide specific details of your needs here.</string>
|
|||
<string name="emoji_category_objects">Objects</string>
|
||||
<string name="emoji_category_symbols">Symbols</string>
|
||||
<string name="emoji_category_flags">Flags</string>
|
||||
<string name="emoji_recently_used_section">Recently used</string>
|
||||
|
||||
</resources>
|
|
@ -50,7 +50,8 @@ class DefaultUserSettingsCache(
|
|||
return VaultPreference(
|
||||
showIntroduceVault = DEFAULT_SHOW_INTRODUCE_VAULT,
|
||||
isRelativeDates = DEFAULT_RELATIVE_DATES,
|
||||
dateFormat = appDefaultDateFormatProvider.provide()
|
||||
dateFormat = appDefaultDateFormatProvider.provide(),
|
||||
orderOfSpaces = emptyList()
|
||||
)
|
||||
}
|
||||
//endregion
|
||||
|
@ -486,6 +487,39 @@ class DefaultUserSettingsCache(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun setRecentlyUsedChatReactions(
|
||||
account: Account,
|
||||
emojis: Set<String>
|
||||
) {
|
||||
context.vaultPrefsStore.updateData { existingPreferences ->
|
||||
val curr = existingPreferences.preferences.getOrDefault(
|
||||
key = account.id,
|
||||
defaultValue = initialVaultSettings()
|
||||
)
|
||||
existingPreferences.copy(
|
||||
preferences = existingPreferences.preferences + mapOf(
|
||||
account.id to curr.copy(
|
||||
recentlyUsedChatReactions = emojis.toList()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun observeRecentlyUsedChatReactions(account: Account): Flow<List<String>> {
|
||||
return context
|
||||
.vaultPrefsStore
|
||||
.data
|
||||
.map { existing ->
|
||||
val settings = existing.preferences[account.id]
|
||||
if (settings != null) {
|
||||
settings.recentlyUsedChatReactions
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setDateFormat(
|
||||
account: Account,
|
||||
format: String
|
||||
|
|
|
@ -18,6 +18,7 @@ message VaultPreference {
|
|||
bool showIntroduceVault = 2;
|
||||
bool isRelativeDates = 3;
|
||||
optional string dateFormat = 4;
|
||||
repeated string recentlyUsedChatReactions = 5;
|
||||
}
|
||||
|
||||
message SpacePreference {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue