mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-3217 Space-level chat | Enhancement | Screen with message reaction and corresponding members (#1970)
This commit is contained in:
parent
9be32dff13
commit
1bfaf2085e
16 changed files with 829 additions and 212 deletions
|
@ -49,8 +49,9 @@ import com.anytypeio.anytype.di.feature.ViewerFilterModule
|
|||
import com.anytypeio.anytype.di.feature.ViewerSortModule
|
||||
import com.anytypeio.anytype.di.feature.auth.DaggerDeletedAccountComponent
|
||||
import com.anytypeio.anytype.di.feature.cover.UnsplashModule
|
||||
import com.anytypeio.anytype.di.feature.discussions.DaggerChatReactionPickerComponent
|
||||
import com.anytypeio.anytype.di.feature.discussions.DaggerChatReactionComponent
|
||||
import com.anytypeio.anytype.di.feature.discussions.DaggerDiscussionComponent
|
||||
import com.anytypeio.anytype.di.feature.discussions.DaggerSelectChatReactionComponent
|
||||
import com.anytypeio.anytype.di.feature.discussions.DaggerSpaceLevelChatComponent
|
||||
import com.anytypeio.anytype.di.feature.gallery.DaggerGalleryInstallationComponent
|
||||
import com.anytypeio.anytype.di.feature.home.DaggerHomeScreenComponent
|
||||
|
@ -105,6 +106,7 @@ import com.anytypeio.anytype.feature_allcontent.presentation.AllContentViewModel
|
|||
import com.anytypeio.anytype.feature_date.viewmodel.DateObjectVmParams
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.ChatReactionViewModel
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.DiscussionViewModel
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.SelectChatReactionViewModel
|
||||
import com.anytypeio.anytype.gallery_experience.viewmodel.GalleryInstallationViewModel
|
||||
import com.anytypeio.anytype.presentation.editor.EditorViewModel
|
||||
import com.anytypeio.anytype.presentation.history.VersionHistoryViewModel
|
||||
|
@ -1081,8 +1083,16 @@ class ComponentManager(
|
|||
.build()
|
||||
}
|
||||
|
||||
val chatReactionPickerComponent = ComponentMapWithParam { params: ChatReactionViewModel.Params ->
|
||||
DaggerChatReactionPickerComponent
|
||||
val selectChatReactionComponent = ComponentMapWithParam { params: SelectChatReactionViewModel.Params ->
|
||||
DaggerSelectChatReactionComponent
|
||||
.builder()
|
||||
.withDependencies(findComponentDependencies())
|
||||
.withParams(params)
|
||||
.build()
|
||||
}
|
||||
|
||||
val chatReactionComponent = ComponentMapWithParam { params: ChatReactionViewModel.Params ->
|
||||
DaggerChatReactionComponent
|
||||
.builder()
|
||||
.withDependencies(findComponentDependencies())
|
||||
.withParams(params)
|
||||
|
|
|
@ -2,50 +2,40 @@ package com.anytypeio.anytype.di.feature.discussions
|
|||
|
||||
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
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.ChatReactionViewModel
|
||||
import com.anytypeio.anytype.ui.chats.ChatReactionFragment
|
||||
import dagger.Binds
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Component(
|
||||
dependencies = [ChatReactionPickerDependencies::class],
|
||||
dependencies = [ChatReactionDependencies::class],
|
||||
modules = [
|
||||
ChatReactionPickerModule::class,
|
||||
ChatReactionPickerModule.Declarations::class
|
||||
ChatReactionModule::class,
|
||||
ChatReactionModule.Declarations::class
|
||||
]
|
||||
)
|
||||
@PerScreen
|
||||
interface ChatReactionPickerComponent {
|
||||
interface ChatReactionComponent {
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun withParams(params: ChatReactionViewModel.Params): Builder
|
||||
fun withDependencies(dependencies: ChatReactionPickerDependencies): Builder
|
||||
fun build(): ChatReactionPickerComponent
|
||||
fun withDependencies(dependencies: ChatReactionDependencies): Builder
|
||||
fun build(): ChatReactionComponent
|
||||
}
|
||||
|
||||
fun getViewModel(): ChatReactionViewModel
|
||||
fun inject(fragment: ChatReactionFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
object ChatReactionPickerModule {
|
||||
@Provides
|
||||
@PerScreen
|
||||
@JvmStatic
|
||||
fun provideEmojiProvider(): EmojiProvider = Emoji
|
||||
object ChatReactionModule {
|
||||
|
||||
@Module
|
||||
interface Declarations {
|
||||
|
@ -57,10 +47,9 @@ object ChatReactionPickerModule {
|
|||
}
|
||||
}
|
||||
|
||||
interface ChatReactionPickerDependencies : ComponentDependencies {
|
||||
interface ChatReactionDependencies : ComponentDependencies {
|
||||
fun dispatchers(): AppCoroutineDispatchers
|
||||
fun suggester(): EmojiSuggester
|
||||
fun repo(): BlockRepository
|
||||
fun auth(): AuthRepository
|
||||
fun prefs(): UserSettingsRepository
|
||||
fun urlBuilder(): UrlBuilder
|
||||
fun members(): ActiveSpaceMemberSubscriptionContainer
|
||||
}
|
|
@ -84,9 +84,10 @@ class DiscussionFragment : BaseComposeFragment() {
|
|||
onRequestOpenFullScreenImage = {
|
||||
// TODO
|
||||
},
|
||||
onChatReaction = {
|
||||
onSelectChatReaction = {
|
||||
// TODO
|
||||
}
|
||||
},
|
||||
onViewChatReaction = { a, b -> }
|
||||
)
|
||||
|
||||
if (showBottomSheet) {
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package com.anytypeio.anytype.di.feature.discussions
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
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
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.SelectChatReactionViewModel
|
||||
import com.anytypeio.anytype.ui.chats.SelectChatReactionFragment
|
||||
import dagger.Binds
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Component(
|
||||
dependencies = [SelectChatReactionDependencies::class],
|
||||
modules = [
|
||||
SelectChatReactionModule::class,
|
||||
SelectChatReactionModule.Declarations::class
|
||||
]
|
||||
)
|
||||
@PerScreen
|
||||
interface SelectChatReactionComponent {
|
||||
@Component.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun withParams(params: SelectChatReactionViewModel.Params): Builder
|
||||
fun withDependencies(dependencies: SelectChatReactionDependencies): Builder
|
||||
fun build(): SelectChatReactionComponent
|
||||
}
|
||||
|
||||
fun getViewModel(): SelectChatReactionViewModel
|
||||
fun inject(fragment: SelectChatReactionFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
object SelectChatReactionModule {
|
||||
@Provides
|
||||
@PerScreen
|
||||
@JvmStatic
|
||||
fun provideEmojiProvider(): EmojiProvider = Emoji
|
||||
|
||||
@Module
|
||||
interface Declarations {
|
||||
@PerScreen
|
||||
@Binds
|
||||
fun bindViewModelFactory(
|
||||
factory: SelectChatReactionViewModel.Factory
|
||||
): ViewModelProvider.Factory
|
||||
}
|
||||
}
|
||||
|
||||
interface SelectChatReactionDependencies : ComponentDependencies {
|
||||
fun dispatchers(): AppCoroutineDispatchers
|
||||
fun suggester(): EmojiSuggester
|
||||
fun repo(): BlockRepository
|
||||
fun auth(): AuthRepository
|
||||
fun prefs(): UserSettingsRepository
|
||||
}
|
|
@ -20,7 +20,8 @@ import com.anytypeio.anytype.di.feature.ObjectTypeChangeSubComponent
|
|||
import com.anytypeio.anytype.di.feature.PersonalizationSettingsSubComponent
|
||||
import com.anytypeio.anytype.di.feature.SplashDependencies
|
||||
import com.anytypeio.anytype.di.feature.auth.DeletedAccountDependencies
|
||||
import com.anytypeio.anytype.di.feature.discussions.ChatReactionPickerDependencies
|
||||
import com.anytypeio.anytype.di.feature.discussions.ChatReactionDependencies
|
||||
import com.anytypeio.anytype.di.feature.discussions.SelectChatReactionDependencies
|
||||
import com.anytypeio.anytype.di.feature.discussions.DiscussionComponentDependencies
|
||||
import com.anytypeio.anytype.di.feature.gallery.GalleryInstallationComponentDependencies
|
||||
import com.anytypeio.anytype.di.feature.home.HomeScreenDependencies
|
||||
|
@ -137,7 +138,8 @@ interface MainComponent :
|
|||
LinkToObjectDependencies,
|
||||
MoveToDependencies,
|
||||
DateObjectDependencies,
|
||||
ChatReactionPickerDependencies
|
||||
SelectChatReactionDependencies,
|
||||
ChatReactionDependencies
|
||||
{
|
||||
|
||||
fun inject(app: AndroidApplication)
|
||||
|
@ -391,6 +393,11 @@ abstract class ComponentDependenciesModule {
|
|||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ComponentDependenciesKey(ChatReactionPickerDependencies::class)
|
||||
@ComponentDependenciesKey(SelectChatReactionDependencies::class)
|
||||
abstract fun provideChatReactionPickerDependencies(component: MainComponent): ComponentDependencies
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ComponentDependenciesKey(ChatReactionDependencies::class)
|
||||
abstract fun provideChatReactionDependencies(component: MainComponent): ComponentDependencies
|
||||
}
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.core.os.bundleOf
|
||||
|
@ -17,7 +16,7 @@ import com.anytypeio.anytype.core_utils.ext.arg
|
|||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.ChatReactionViewModel
|
||||
import com.anytypeio.anytype.feature_discussions.ui.ChatReactionPicker
|
||||
import com.anytypeio.anytype.feature_discussions.ui.ChatReactionScreen
|
||||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import javax.inject.Inject
|
||||
import kotlin.getValue
|
||||
|
@ -26,6 +25,7 @@ class ChatReactionFragment : BaseBottomSheetComposeFragment() {
|
|||
|
||||
private val chat: Id get() = arg<Id>(CHAT_ID_KEY)
|
||||
private val msg: Id get() = arg<Id>(MSG_ID_KEY)
|
||||
private val emoji: String get() = arg<String>(EMOJI_KEY)
|
||||
|
||||
@Inject
|
||||
lateinit var factory: ChatReactionViewModel.Factory
|
||||
|
@ -42,49 +42,51 @@ class ChatReactionFragment : BaseBottomSheetComposeFragment() {
|
|||
MaterialTheme(
|
||||
typography = typography
|
||||
) {
|
||||
ChatReactionPicker(
|
||||
views = vm.views.collectAsStateWithLifecycle(emptyList()).value,
|
||||
onEmojiClicked = vm::onEmojiClicked
|
||||
ChatReactionScreen(
|
||||
viewState = vm.viewState.collectAsStateWithLifecycle().value
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
vm.isDismissed.collect { isDismissed ->
|
||||
if (isDismissed) dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().chatReactionPickerComponent
|
||||
componentManager().chatReactionComponent
|
||||
.get(
|
||||
key = chat,
|
||||
key = getComponentKey(),
|
||||
param = ChatReactionViewModel.Params(
|
||||
chat = chat,
|
||||
msg = msg
|
||||
msg = msg,
|
||||
emoji = emoji
|
||||
)
|
||||
)
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
// TODO
|
||||
componentManager().chatReactionComponent.release(id = getComponentKey())
|
||||
}
|
||||
|
||||
private fun getComponentKey(): String = "$COMPONENT_PREFIX-$chat"
|
||||
|
||||
companion object {
|
||||
|
||||
private const val SPACE_ID_KEY = "chat.reaction-picker.space"
|
||||
private const val CHAT_ID_KEY = "chat.reaction-picker.chat"
|
||||
private const val MSG_ID_KEY = "chat.reaction-picker.msg"
|
||||
private const val COMPONENT_PREFIX = "chat-reaction"
|
||||
|
||||
private const val SPACE_ID_KEY = "chat.reaction.space"
|
||||
private const val CHAT_ID_KEY = "chat.reaction.chat"
|
||||
private const val MSG_ID_KEY = "chat.reaction.msg"
|
||||
private const val EMOJI_KEY = "chat.reaction.emoji"
|
||||
|
||||
fun args(
|
||||
space: SpaceId,
|
||||
chat: String,
|
||||
msg: String
|
||||
msg: String,
|
||||
emoji: String
|
||||
): Bundle = bundleOf(
|
||||
SPACE_ID_KEY to space.id,
|
||||
CHAT_ID_KEY to chat,
|
||||
MSG_ID_KEY to msg
|
||||
MSG_ID_KEY to msg,
|
||||
EMOJI_KEY to emoji
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package com.anytypeio.anytype.ui.chats
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.primitives.SpaceId
|
||||
import com.anytypeio.anytype.core_utils.ext.arg
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.SelectChatReactionViewModel
|
||||
import com.anytypeio.anytype.feature_discussions.ui.SelectChatReactionScreen
|
||||
import com.anytypeio.anytype.ui.settings.typography
|
||||
import javax.inject.Inject
|
||||
import kotlin.getValue
|
||||
|
||||
class SelectChatReactionFragment : BaseBottomSheetComposeFragment() {
|
||||
|
||||
private val chat: Id get() = arg<Id>(CHAT_ID_KEY)
|
||||
private val msg: Id get() = arg<Id>(MSG_ID_KEY)
|
||||
|
||||
@Inject
|
||||
lateinit var factory: SelectChatReactionViewModel.Factory
|
||||
|
||||
private val vm by viewModels<SelectChatReactionViewModel> { factory }
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View = ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
MaterialTheme(
|
||||
typography = typography
|
||||
) {
|
||||
SelectChatReactionScreen(
|
||||
views = vm.views.collectAsStateWithLifecycle(initialValue = emptyList()).value,
|
||||
onEmojiClicked = vm::onEmojiClicked
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
vm.isDismissed.collect { isDismissed ->
|
||||
if (isDismissed) dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun injectDependencies() {
|
||||
componentManager().selectChatReactionComponent
|
||||
.get(
|
||||
key = getComponentKey(),
|
||||
param = SelectChatReactionViewModel.Params(
|
||||
chat = chat,
|
||||
msg = msg
|
||||
)
|
||||
)
|
||||
.inject(this)
|
||||
}
|
||||
|
||||
override fun releaseDependencies() {
|
||||
componentManager().selectChatReactionComponent.release(id = getComponentKey())
|
||||
}
|
||||
|
||||
private fun getComponentKey(): String = "$COMPONENT_PREFIX-$chat"
|
||||
|
||||
companion object {
|
||||
private const val COMPONENT_PREFIX = "select-chat-reaction"
|
||||
private const val SPACE_ID_KEY = "select-chat-reaction.space"
|
||||
private const val CHAT_ID_KEY = "select-chat-reaction.chat"
|
||||
private const val MSG_ID_KEY = "select-chat-reaction.msg"
|
||||
|
||||
fun args(
|
||||
space: SpaceId,
|
||||
chat: String,
|
||||
msg: String
|
||||
): Bundle = bundleOf(
|
||||
SPACE_ID_KEY to space.id,
|
||||
CHAT_ID_KEY to chat,
|
||||
MSG_ID_KEY to msg
|
||||
)
|
||||
}
|
||||
}
|
|
@ -66,6 +66,7 @@ import com.anytypeio.anytype.presentation.widgets.DropDownMenuAction
|
|||
import com.anytypeio.anytype.presentation.widgets.WidgetView
|
||||
import com.anytypeio.anytype.ui.base.navigation
|
||||
import com.anytypeio.anytype.ui.chats.ChatReactionFragment
|
||||
import com.anytypeio.anytype.ui.chats.SelectChatReactionFragment
|
||||
import com.anytypeio.anytype.ui.editor.EditorFragment
|
||||
import com.anytypeio.anytype.ui.editor.gallery.FullScreenPictureFragment
|
||||
import com.anytypeio.anytype.ui.gallery.GalleryInstallationFragment
|
||||
|
@ -197,15 +198,34 @@ class HomeScreenFragment : BaseComposeFragment(),
|
|||
)
|
||||
)
|
||||
},
|
||||
onChatReaction = {
|
||||
findNavController().navigate(
|
||||
R.id.chatReactionScreen,
|
||||
ChatReactionFragment.args(
|
||||
space = Space(space),
|
||||
chat = spaceLevelChatViewModel.chat,
|
||||
msg = it
|
||||
onSelectChatReaction = {
|
||||
runCatching {
|
||||
findNavController().navigate(
|
||||
R.id.selectChatReactionScreen,
|
||||
SelectChatReactionFragment.args(
|
||||
space = Space(space),
|
||||
chat = spaceLevelChatViewModel.chat,
|
||||
msg = it
|
||||
)
|
||||
)
|
||||
)
|
||||
}.onFailure {
|
||||
Timber.e(it, "Error while opening chat-reaction picker")
|
||||
}
|
||||
},
|
||||
onViewChatReaction = { msg, emoji ->
|
||||
runCatching {
|
||||
findNavController().navigate(
|
||||
R.id.chatReactionScreen,
|
||||
ChatReactionFragment.args(
|
||||
space = Space(space),
|
||||
chat = spaceLevelChatViewModel.chat,
|
||||
msg = msg,
|
||||
emoji = emoji
|
||||
)
|
||||
)
|
||||
}.onFailure {
|
||||
Timber.e(it, "Error while opening a chat reaction")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -162,6 +162,11 @@
|
|||
android:name="com.anytypeio.anytype.di.feature.discussions.DiscussionFragment"
|
||||
android:label="Discussion" />
|
||||
|
||||
<dialog
|
||||
android:id="@+id/selectChatReactionScreen"
|
||||
android:name="com.anytypeio.anytype.ui.chats.SelectChatReactionFragment"
|
||||
android:label="Select chat reaction" />
|
||||
|
||||
<dialog
|
||||
android:id="@+id/chatReactionScreen"
|
||||
android:name="com.anytypeio.anytype.ui.chats.ChatReactionFragment"
|
||||
|
|
|
@ -5,165 +5,135 @@ 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.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.domain.base.getOrDefault
|
||||
import com.anytypeio.anytype.domain.chats.GetChatMessagesByIds
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer
|
||||
import com.anytypeio.anytype.presentation.common.BaseViewModel
|
||||
import com.anytypeio.anytype.presentation.objects.SpaceMemberIconView
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
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 setRecentlyUsedChatReactions: SetRecentlyUsedChatReactions,
|
||||
private val observeRecentlyUsedChatReactions: ObserveRecentlyUsedChatReactions
|
||||
private val getChatMessagesByIds: GetChatMessagesByIds,
|
||||
private val members: ActiveSpaceMemberSubscriptionContainer,
|
||||
private val urlBuilder: UrlBuilder
|
||||
) : BaseViewModel() {
|
||||
|
||||
val isDismissed = MutableSharedFlow<Boolean>(replay = 0)
|
||||
|
||||
/**
|
||||
* Default emoji list, including categories.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
val viewState = MutableStateFlow<ViewState>(ViewState.Init(vmParams.emoji))
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
observeRecentlyUsedChatReactions
|
||||
.flow()
|
||||
.collect {
|
||||
recentlyUsed.value = it
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val loaded = loadEmojiWithCategories()
|
||||
default.value = loaded
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun loadEmojiWithCategories() = withContext(dispatchers.io) {
|
||||
|
||||
val views = mutableListOf<ReactionPickerView>()
|
||||
|
||||
provider.emojis.forEachIndexed { categoryIndex, emojis ->
|
||||
views.add(
|
||||
ReactionPickerView.Category(
|
||||
index = categoryIndex
|
||||
)
|
||||
)
|
||||
emojis.forEachIndexed { emojiIndex, emoji ->
|
||||
val skin = Emoji.COLORS.any { color -> emoji.contains(color) }
|
||||
if (!skin)
|
||||
views.add(
|
||||
ReactionPickerView.Emoji(
|
||||
unicode = emoji,
|
||||
page = categoryIndex,
|
||||
index = emojiIndex,
|
||||
emojified = Emojifier.safeUri(emoji)
|
||||
)
|
||||
val result = getChatMessagesByIds
|
||||
.async(
|
||||
Command.ChatCommand.GetMessagesByIds(
|
||||
chat = vmParams.chat,
|
||||
messages = listOf(vmParams.msg)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
views
|
||||
}
|
||||
|
||||
fun onEmojiClicked(emoji: String) {
|
||||
viewModelScope.launch {
|
||||
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")
|
||||
val msg = result.getOrDefault(emptyList()).firstOrNull()
|
||||
if (msg != null) {
|
||||
val identities = msg.reactions.getOrDefault(
|
||||
key = vmParams.emoji,
|
||||
defaultValue = emptyList()
|
||||
)
|
||||
if (identities.isNotEmpty()) {
|
||||
members.observe().map { store ->
|
||||
when(store) {
|
||||
is ActiveSpaceMemberSubscriptionContainer.Store.Data -> {
|
||||
identities.mapNotNull { identity ->
|
||||
val member = store.members.firstOrNull { it.identity == identity }
|
||||
if (member != null) {
|
||||
ViewState.Member(
|
||||
icon = SpaceMemberIconView.icon(
|
||||
obj = member,
|
||||
urlBuilder = urlBuilder
|
||||
),
|
||||
name = member.name.orEmpty(),
|
||||
isUser = false
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
is ActiveSpaceMemberSubscriptionContainer.Store.Empty -> {
|
||||
emptyList<ViewState.Member>()
|
||||
}
|
||||
}
|
||||
}.collect {
|
||||
viewState.value = ViewState.Success(
|
||||
emoji = vmParams.emoji,
|
||||
members = it
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewState.value = ViewState.Empty(
|
||||
emoji = vmParams.emoji
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewState.value = ViewState.Error.MessageNotFound(
|
||||
emoji = vmParams.emoji
|
||||
)
|
||||
}
|
||||
isDismissed.emit(true)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val params: Params,
|
||||
private val emojiProvider: EmojiProvider,
|
||||
private val emojiSuggester: EmojiSuggester,
|
||||
private val dispatchers: AppCoroutineDispatchers,
|
||||
private val toggleChatMessageReaction: ToggleChatMessageReaction,
|
||||
private val setRecentlyUsedChatReactions: SetRecentlyUsedChatReactions,
|
||||
private val observeRecentlyUsedChatReactions: ObserveRecentlyUsedChatReactions
|
||||
private val vmParams: Params,
|
||||
private val getChatMessagesByIds: GetChatMessagesByIds,
|
||||
private val members: ActiveSpaceMemberSubscriptionContainer,
|
||||
private val urlBuilder: UrlBuilder
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = ChatReactionViewModel(
|
||||
vmParams = params,
|
||||
provider = emojiProvider,
|
||||
suggester = emojiSuggester,
|
||||
dispatchers = dispatchers,
|
||||
toggleChatMessageReaction = toggleChatMessageReaction,
|
||||
setRecentlyUsedChatReactions = setRecentlyUsedChatReactions,
|
||||
observeRecentlyUsedChatReactions = observeRecentlyUsedChatReactions
|
||||
vmParams = vmParams,
|
||||
getChatMessagesByIds = getChatMessagesByIds,
|
||||
members = members,
|
||||
urlBuilder = urlBuilder
|
||||
) as T
|
||||
}
|
||||
|
||||
data class Params @Inject constructor(
|
||||
val chat: Id,
|
||||
val msg: Id
|
||||
val msg: Id,
|
||||
val emoji: String
|
||||
)
|
||||
|
||||
sealed class ReactionPickerView {
|
||||
data object RecentUsedSection: ReactionPickerView()
|
||||
data class Category(val index: Int) : ReactionPickerView()
|
||||
data class Emoji(
|
||||
val unicode: String,
|
||||
val page: Int,
|
||||
val index: Int,
|
||||
val emojified: String = ""
|
||||
) : ReactionPickerView()
|
||||
}
|
||||
sealed class ViewState {
|
||||
abstract val emoji: String
|
||||
|
||||
companion object {
|
||||
const val MAX_RECENTLY_USED_COUNT = 20
|
||||
data class Init(
|
||||
override val emoji: String
|
||||
) : ViewState()
|
||||
|
||||
sealed class Error : ViewState() {
|
||||
data class MessageNotFound(
|
||||
override val emoji: String
|
||||
) : Error()
|
||||
}
|
||||
|
||||
data class Loading(
|
||||
override val emoji: String
|
||||
) : ViewState()
|
||||
|
||||
data class Empty(
|
||||
override val emoji: String
|
||||
): ViewState()
|
||||
|
||||
data class Success(
|
||||
override val emoji: String,
|
||||
val members: List<Member>
|
||||
) : ViewState()
|
||||
|
||||
data class Member(
|
||||
val name: String,
|
||||
val icon: SpaceMemberIconView,
|
||||
val isUser: Boolean
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package com.anytypeio.anytype.feature_discussions.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.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 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 SelectChatReactionViewModel @Inject constructor(
|
||||
private val vmParams: Params,
|
||||
private val provider: EmojiProvider,
|
||||
private val suggester: EmojiSuggester,
|
||||
private val dispatchers: AppCoroutineDispatchers,
|
||||
private val toggleChatMessageReaction: ToggleChatMessageReaction,
|
||||
private val setRecentlyUsedChatReactions: SetRecentlyUsedChatReactions,
|
||||
private val observeRecentlyUsedChatReactions: ObserveRecentlyUsedChatReactions
|
||||
) : BaseViewModel() {
|
||||
|
||||
val isDismissed = MutableSharedFlow<Boolean>(replay = 0)
|
||||
|
||||
/**
|
||||
* Default emoji list, including categories.
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun loadEmojiWithCategories() = withContext(dispatchers.io) {
|
||||
|
||||
val views = mutableListOf<ReactionPickerView>()
|
||||
|
||||
provider.emojis.forEachIndexed { categoryIndex, emojis ->
|
||||
views.add(
|
||||
ReactionPickerView.Category(
|
||||
index = categoryIndex
|
||||
)
|
||||
)
|
||||
emojis.forEachIndexed { emojiIndex, emoji ->
|
||||
val skin = Emoji.COLORS.any { color -> emoji.contains(color) }
|
||||
if (!skin)
|
||||
views.add(
|
||||
ReactionPickerView.Emoji(
|
||||
unicode = emoji,
|
||||
page = categoryIndex,
|
||||
index = emojiIndex,
|
||||
emojified = Emojifier.safeUri(emoji)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
views
|
||||
}
|
||||
|
||||
fun onEmojiClicked(emoji: String) {
|
||||
viewModelScope.launch {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val params: Params,
|
||||
private val emojiProvider: EmojiProvider,
|
||||
private val emojiSuggester: EmojiSuggester,
|
||||
private val dispatchers: AppCoroutineDispatchers,
|
||||
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 = SelectChatReactionViewModel(
|
||||
vmParams = params,
|
||||
provider = emojiProvider,
|
||||
suggester = emojiSuggester,
|
||||
dispatchers = dispatchers,
|
||||
toggleChatMessageReaction = toggleChatMessageReaction,
|
||||
setRecentlyUsedChatReactions = setRecentlyUsedChatReactions,
|
||||
observeRecentlyUsedChatReactions = observeRecentlyUsedChatReactions
|
||||
) as T
|
||||
}
|
||||
|
||||
data class Params @Inject constructor(
|
||||
val chat: Id,
|
||||
val msg: Id
|
||||
)
|
||||
|
||||
sealed class ReactionPickerView {
|
||||
data object RecentUsedSection: ReactionPickerView()
|
||||
data class Category(val index: Int) : ReactionPickerView()
|
||||
data class Emoji(
|
||||
val unicode: String,
|
||||
val page: Int,
|
||||
val index: Int,
|
||||
val emojified: String = ""
|
||||
) : ReactionPickerView()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_RECENTLY_USED_COUNT = 20
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.anytypeio.anytype.feature_discussions.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
@ -8,7 +7,6 @@ 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.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
|
@ -28,13 +26,13 @@ import com.anytypeio.anytype.core_ui.foundation.noRippleClickable
|
|||
import com.anytypeio.anytype.core_ui.views.Caption1Medium
|
||||
import com.anytypeio.anytype.emojifier.data.Emoji
|
||||
import com.anytypeio.anytype.feature_discussions.R
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.ChatReactionViewModel.ReactionPickerView
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.SelectChatReactionViewModel.ReactionPickerView
|
||||
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
|
||||
import com.bumptech.glide.integration.compose.GlideImage
|
||||
|
||||
@OptIn(ExperimentalGlideComposeApi::class)
|
||||
@Composable
|
||||
fun ChatReactionPicker(
|
||||
fun SelectChatReactionScreen(
|
||||
views: List<ReactionPickerView> = emptyList(),
|
||||
onEmojiClicked: (String) -> Unit
|
||||
) {
|
||||
|
@ -133,7 +131,7 @@ fun ChatReactionPicker(
|
|||
@DefaultPreviews
|
||||
@Composable
|
||||
fun PickerPreview() {
|
||||
ChatReactionPicker(
|
||||
SelectChatReactionScreen(
|
||||
views = buildList {
|
||||
add(
|
||||
ReactionPickerView.Emoji(
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
package com.anytypeio.anytype.feature_discussions.ui
|
||||
|
||||
import com.anytypeio.anytype.feature_discussions.R
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.anytypeio.anytype.core_ui.common.DefaultPreviews
|
||||
import com.anytypeio.anytype.core_ui.features.multiplayer.SpaceMemberIcon
|
||||
import com.anytypeio.anytype.core_ui.foundation.Dragger
|
||||
import com.anytypeio.anytype.core_ui.views.BodyCallout
|
||||
import com.anytypeio.anytype.core_ui.views.BodyRegular
|
||||
import com.anytypeio.anytype.core_ui.views.Relations3
|
||||
import com.anytypeio.anytype.feature_discussions.presentation.ChatReactionViewModel.ViewState
|
||||
import com.anytypeio.anytype.presentation.objects.SpaceMemberIconView
|
||||
|
||||
@Composable
|
||||
fun ChatReactionScreen(
|
||||
viewState: ViewState
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Dragger(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 6.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
EmojiToolbar(viewState)
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize().weight(1f)
|
||||
) {
|
||||
when(viewState) {
|
||||
is ViewState.Init -> {
|
||||
// Do nothing.
|
||||
}
|
||||
is ViewState.Empty -> {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize()
|
||||
) {
|
||||
EmptyState(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is ViewState.Success -> {
|
||||
items(
|
||||
count = viewState.members.size
|
||||
) { idx ->
|
||||
Member(
|
||||
member = viewState.members[idx]
|
||||
)
|
||||
}
|
||||
}
|
||||
is ViewState.Error.MessageNotFound -> {
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier.fillParentMaxSize()
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = colorResource(R.color.palette_system_red),
|
||||
text = "Message not found",
|
||||
style = BodyCallout
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
is ViewState.Loading -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Member(
|
||||
modifier: Modifier = Modifier,
|
||||
member: ViewState.Member
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(72.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
SpaceMemberIcon(
|
||||
icon = member.icon,
|
||||
iconSize = 48.dp,
|
||||
modifier = Modifier.align(
|
||||
alignment = Alignment.CenterStart
|
||||
)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.CenterStart)
|
||||
.padding(start = 60.dp)
|
||||
) {
|
||||
Text(
|
||||
text = member.name.ifEmpty {
|
||||
stringResource(R.string.untitled)
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.object_types_human),
|
||||
style = Relations3
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmojiToolbar(
|
||||
viewState: ViewState
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(48.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(viewState.emoji)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
when(viewState) {
|
||||
is ViewState.Success -> {
|
||||
Text(
|
||||
text = viewState.members.size.toString(),
|
||||
style = BodyRegular,
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
}
|
||||
is ViewState.Empty -> {
|
||||
Text(
|
||||
text = "0",
|
||||
style = BodyRegular,
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyState(
|
||||
modifier: Modifier
|
||||
) {
|
||||
Column(modifier = modifier.padding(horizontal = 20.dp)) {
|
||||
Text(
|
||||
stringResource(R.string.chat_message_reactions_no_reactions_yet),
|
||||
style = BodyRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.chat_message_reactions_no_reactions_message),
|
||||
style = BodyRegular,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = colorResource(R.color.text_primary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun MemberPreview() {
|
||||
Member(
|
||||
member = ViewState.Member(
|
||||
name = "Walter Benjamin",
|
||||
icon = SpaceMemberIconView.Placeholder(
|
||||
name = "Walter"
|
||||
),
|
||||
isUser = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun EmojiToolbarPreview() {
|
||||
EmojiToolbar(
|
||||
viewState = ViewState.Empty(
|
||||
emoji = "😀"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun EmptyStatePreview() {
|
||||
EmptyState(modifier = Modifier)
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun ChatReactionEmptyStateScreenPreview() {
|
||||
ChatReactionScreen(
|
||||
viewState = ViewState.Empty(
|
||||
emoji = "😀"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@DefaultPreviews
|
||||
@Composable
|
||||
private fun ChatReactionSuccessStateScreenPreview() {
|
||||
ChatReactionScreen(
|
||||
viewState = ViewState.Success(
|
||||
emoji = "😀",
|
||||
members = listOf(
|
||||
ViewState.Member(
|
||||
name = "Walter Benjamin",
|
||||
icon = SpaceMemberIconView.Placeholder(
|
||||
name = "Walter"
|
||||
),
|
||||
isUser = false
|
||||
),
|
||||
ViewState.Member(
|
||||
name = "Walter Benjamin",
|
||||
icon = SpaceMemberIconView.Placeholder(
|
||||
name = "Walter"
|
||||
),
|
||||
isUser = false
|
||||
),
|
||||
ViewState.Member(
|
||||
name = "Walter Benjamin",
|
||||
icon = SpaceMemberIconView.Placeholder(
|
||||
name = "Walter"
|
||||
),
|
||||
isUser = false
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
|
@ -69,7 +69,8 @@ fun DiscussionPreview() {
|
|||
onEditMessage = {},
|
||||
onMarkupLinkClicked = {},
|
||||
onReplyMessage = {},
|
||||
onAddReactionClicked = {}
|
||||
onAddReactionClicked = {},
|
||||
onViewChatReaction = { a, b -> }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -125,7 +126,8 @@ fun DiscussionScreenPreview() {
|
|||
onClearReplyClicked = {},
|
||||
onChatBoxMediaPicked = {},
|
||||
onChatBoxFilePicked = {},
|
||||
onAddReactionClicked = {}
|
||||
onAddReactionClicked = {},
|
||||
onViewChatReaction = { a, b -> }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -152,7 +154,8 @@ fun BubblePreview() {
|
|||
onMarkupLinkClicked = {},
|
||||
onReply = {},
|
||||
onScrollToReplyClicked = {},
|
||||
onAddReactionClicked = {}
|
||||
onAddReactionClicked = {},
|
||||
onViewChatReaction = {}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -180,7 +183,8 @@ fun BubbleEditedPreview() {
|
|||
onMarkupLinkClicked = {},
|
||||
onReply = {},
|
||||
onScrollToReplyClicked = {},
|
||||
onAddReactionClicked = {}
|
||||
onAddReactionClicked = {},
|
||||
onViewChatReaction = {}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -216,6 +220,7 @@ fun BubbleWithAttachmentPreview() {
|
|||
onMarkupLinkClicked = {},
|
||||
onReply = {},
|
||||
onScrollToReplyClicked = {},
|
||||
onAddReactionClicked = {}
|
||||
onAddReactionClicked = {},
|
||||
onViewChatReaction = {}
|
||||
)
|
||||
}
|
|
@ -13,10 +13,12 @@ import androidx.compose.animation.fadeIn
|
|||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.scaleOut
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
@ -150,7 +152,8 @@ fun DiscussionScreenWrapper(
|
|||
onBackButtonClicked: () -> Unit,
|
||||
onMarkupLinkClicked: (String) -> Unit,
|
||||
onRequestOpenFullScreenImage: (String) -> Unit,
|
||||
onChatReaction: (String) -> Unit
|
||||
onSelectChatReaction: (String) -> Unit,
|
||||
onViewChatReaction: (Id, String) -> Unit
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false)
|
||||
var showReactionSheet by remember { mutableStateOf(false) }
|
||||
|
@ -238,7 +241,8 @@ fun DiscussionScreenWrapper(
|
|||
}
|
||||
vm.onChatBoxFilePicked(infos)
|
||||
},
|
||||
onAddReactionClicked = onChatReaction
|
||||
onAddReactionClicked = onSelectChatReaction,
|
||||
onViewChatReaction = onViewChatReaction
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
vm.commands.collect { command ->
|
||||
|
@ -268,7 +272,7 @@ fun DiscussionScreenWrapper(
|
|||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||
dragHandle = null
|
||||
) {
|
||||
ChatReactionPicker(
|
||||
SelectChatReactionScreen(
|
||||
onEmojiClicked = {}
|
||||
)
|
||||
}
|
||||
|
@ -307,7 +311,8 @@ fun DiscussionScreen(
|
|||
onUploadAttachmentClicked: () -> Unit,
|
||||
onChatBoxMediaPicked: (List<Uri>) -> Unit,
|
||||
onChatBoxFilePicked: (List<Uri>) -> Unit,
|
||||
onAddReactionClicked: (String) -> Unit
|
||||
onAddReactionClicked: (String) -> Unit,
|
||||
onViewChatReaction: (Id, String) -> Unit
|
||||
) {
|
||||
var textState by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(TextFieldValue(""))
|
||||
|
@ -364,7 +369,8 @@ fun DiscussionScreen(
|
|||
chatBoxFocusRequester.requestFocus()
|
||||
},
|
||||
onMarkupLinkClicked = onMarkupLinkClicked,
|
||||
onAddReactionClicked = onAddReactionClicked
|
||||
onAddReactionClicked = onAddReactionClicked,
|
||||
onViewChatReaction = onViewChatReaction
|
||||
)
|
||||
// Jump to bottom button shows up when user scrolls past a threshold.
|
||||
// Convert to pixels:
|
||||
|
@ -981,7 +987,8 @@ fun Messages(
|
|||
onEditMessage: (DiscussionView.Message) -> Unit,
|
||||
onReplyMessage: (DiscussionView.Message) -> Unit,
|
||||
onMarkupLinkClicked: (String) -> Unit,
|
||||
onAddReactionClicked: (String) -> Unit
|
||||
onAddReactionClicked: (String) -> Unit,
|
||||
onViewChatReaction: (Id, String) -> Unit
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
LazyColumn(
|
||||
|
@ -1048,6 +1055,9 @@ fun Messages(
|
|||
},
|
||||
onAddReactionClicked = {
|
||||
onAddReactionClicked(msg.id)
|
||||
},
|
||||
onViewChatReaction = { emoji ->
|
||||
onViewChatReaction(msg.id, emoji)
|
||||
}
|
||||
)
|
||||
if (msg.isUserAuthor) {
|
||||
|
@ -1174,7 +1184,8 @@ fun Bubble(
|
|||
onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit,
|
||||
onMarkupLinkClicked: (String) -> Unit,
|
||||
onScrollToReplyClicked: (DiscussionView.Message.Reply) -> Unit,
|
||||
onAddReactionClicked: () -> Unit
|
||||
onAddReactionClicked: () -> Unit,
|
||||
onViewChatReaction: (String) -> Unit
|
||||
) {
|
||||
var showDropdownMenu by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
|
@ -1322,7 +1333,8 @@ fun Bubble(
|
|||
if (reactions.isNotEmpty()) {
|
||||
ReactionList(
|
||||
reactions = reactions,
|
||||
onReacted = onReacted
|
||||
onReacted = onReacted,
|
||||
onViewReaction = onViewChatReaction
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
@ -1660,11 +1672,12 @@ fun GoToBottomButton(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@OptIn(ExperimentalLayoutApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ReactionList(
|
||||
reactions: List<DiscussionView.Message.Reaction>,
|
||||
onReacted: (String) -> Unit
|
||||
onReacted: (String) -> Unit,
|
||||
onViewReaction: (String) -> Unit
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
||||
|
@ -1694,9 +1707,14 @@ fun ReactionList(
|
|||
else
|
||||
Modifier
|
||||
)
|
||||
.clickable {
|
||||
onReacted(reaction.emoji)
|
||||
}
|
||||
.combinedClickable(
|
||||
onClick = {
|
||||
onReacted(reaction.emoji)
|
||||
},
|
||||
onLongClick = {
|
||||
onViewReaction(reaction.emoji)
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = reaction.emoji,
|
||||
|
@ -1766,7 +1784,8 @@ fun ReactionListPreview() {
|
|||
isSelected = false
|
||||
)
|
||||
),
|
||||
onReacted = {}
|
||||
onReacted = {},
|
||||
onViewReaction = {}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1862,5 +1862,9 @@ Please provide specific details of your needs here.</string>
|
|||
<string name="emoji_category_symbols">Symbols</string>
|
||||
<string name="emoji_category_flags">Flags</string>
|
||||
<string name="emoji_recently_used_section">Recently used</string>
|
||||
<string name="chat_message_reactions_no_reactions_yet">No reactions yet</string>
|
||||
<string name="chat_message_reactions_no_reactions_message">Probably someone has just removed the reaction or technical issue happened</string>
|
||||
|
||||
<string name="object_types_human">Human</string>
|
||||
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue