Skip to content

Commit

Permalink
(sample-app) add ability to hide/show channel
Browse files Browse the repository at this point in the history
  • Loading branch information
kanat authored and JcMinarro committed Mar 1, 2024
1 parent 8d3256a commit fe783b6
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ sealed class ChatInfoItem {
data class MemberItem(val member: Member, val createdBy: User) : ChatInfoItem()
data class MembersSeparator(val membersToShow: Int) : ChatInfoItem()
data class ChannelName(val name: String) : ChatInfoItem()
object Separator : ChatInfoItem()
data object Separator : ChatInfoItem()

sealed class Option : ChatInfoItem() {

Expand All @@ -52,35 +52,37 @@ sealed class ChatInfoItem {

open val showRightArrow: Boolean = true

object PinnedMessages : Option() {
open val checkedState: Boolean? = null

data object PinnedMessages : Option() {
override val iconResId: Int
get() = R.drawable.stream_ui_ic_pin
override val textResId: Int
get() = R.string.chat_info_option_pinned_messages
}

object SharedMedia : Option() {
data object SharedMedia : Option() {
override val iconResId: Int
get() = R.drawable.ic_media
override val textResId: Int
get() = R.string.chat_info_option_media
}

object SharedFiles : Option() {
data object SharedFiles : Option() {
override val iconResId: Int
get() = R.drawable.ic_files
override val textResId: Int
get() = R.string.chat_info_option_files
}

object SharedGroups : Option() {
data object SharedGroups : Option() {
override val iconResId: Int
get() = R.drawable.ic_new_group
override val textResId: Int
get() = R.string.chat_info_option_shared_groups
}

object DeleteConversation : Option() {
data object DeleteConversation : Option() {
override val iconResId: Int
get() = R.drawable.ic_delete
override val textResId: Int
Expand All @@ -92,14 +94,27 @@ sealed class ChatInfoItem {
override val showRightArrow: Boolean = false
}

object LeaveGroup : Option() {
data object LeaveGroup : Option() {
override val iconResId: Int
get() = R.drawable.ic_leave_group
override val textResId: Int
get() = R.string.chat_group_info_option_leave
override val showRightArrow: Boolean = false
}

data class HideChannel(var isHidden: Boolean) : Option() {
override val iconResId: Int
get() = R.drawable.ic_hide
override val textResId: Int
get() = R.string.chat_group_info_option_hide

override val showRightArrow: Boolean = false

override val checkedState: Boolean
get() = isHidden
}


sealed class Stateful : Option() {
abstract val isChecked: Boolean

Expand All @@ -125,4 +140,4 @@ sealed class ChatInfoItem {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ class ChatInfoOptionViewHolder(
binding.optionTextView.setTextColor(itemView.context.getColorFromRes(item.textColorResId))
binding.optionImageView.setImageResource(item.iconResId)
binding.optionImageView.setColorFilter(itemView.context.getColorFromRes(item.tintResId))
binding.optionArrowRight.isInvisible = !item.showRightArrow
binding.optionArrowRight.isVisible = item.showRightArrow
binding.optionCompound.isVisible = item.checkedState != null
binding.optionCompound.isChecked = item.checkedState == true
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.events.ChannelHiddenEvent
import io.getstream.chat.android.client.events.ChannelVisibleEvent
import io.getstream.chat.android.client.events.NotificationChannelMutesUpdatedEvent
import io.getstream.chat.android.client.subscribeFor
import io.getstream.chat.android.state.utils.EventObserver
Expand All @@ -43,9 +45,12 @@ import io.getstream.chat.ui.sample.feature.chat.info.group.users.GroupChatInfoAd
import io.getstream.chat.ui.sample.feature.common.ConfirmationDialogFragment
import io.getstream.chat.ui.sample.util.extensions.autoScrollToTop
import io.getstream.chat.ui.sample.util.extensions.useAdjustResize
import io.getstream.log.taggedLogger

class GroupChatInfoFragment : Fragment() {

private val logger by taggedLogger("GroupChatInfo-View")

private val args: GroupChatInfoFragmentArgs by navArgs()
private val viewModel: GroupChatInfoViewModel by viewModels { ChatViewModelFactory(args.cid) }
private val headerViewModel: MessageListHeaderViewModel by viewModels {
Expand Down Expand Up @@ -101,6 +106,7 @@ class GroupChatInfoFragment : Fragment() {

private fun bindGroupInfoViewModel() {
subscribeForChannelMutesUpdatedEvents()
subscribeForChannelVisibilityEvents()
setOnClickListeners()

viewModel.events.observe(
Expand Down Expand Up @@ -135,6 +141,7 @@ class GroupChatInfoFragment : Fragment() {
ChatInfoItem.Separator,
ChatInfoItem.ChannelName(state.channelName),
ChatInfoItem.Option.Stateful.MuteChannel(isChecked = state.channelMuted),
ChatInfoItem.Option.HideChannel(isHidden = state.channelHidden),
ChatInfoItem.Option.PinnedMessages,
ChatInfoItem.Option.SharedMedia,
ChatInfoItem.Option.SharedFiles,
Expand All @@ -148,6 +155,7 @@ class GroupChatInfoFragment : Fragment() {
when (it) {
is GroupChatInfoViewModel.ErrorEvent.ChangeGroupNameError -> R.string.chat_group_info_error_change_name
is GroupChatInfoViewModel.ErrorEvent.MuteChannelError -> R.string.chat_group_info_error_mute_channel
is GroupChatInfoViewModel.ErrorEvent.HideChannelError -> R.string.chat_group_info_error_hide_channel
is GroupChatInfoViewModel.ErrorEvent.LeaveChannelError -> R.string.chat_group_info_error_leave_channel
}.let(::showToast)
},
Expand All @@ -156,14 +164,14 @@ class GroupChatInfoFragment : Fragment() {

private fun setOnClickListeners() {
adapter.setChatInfoStatefulOptionChangedListener { option, isChecked ->
viewModel.onAction(
when (option) {
is ChatInfoItem.Option.Stateful.MuteChannel -> GroupChatInfoViewModel.Action.MuteChannelClicked(
isChecked,
)
else -> throw IllegalStateException("Chat info option $option is not supported!")
},
)
logger.d { "[onStatefulOptionChanged] option: $option, isChecked: $isChecked" }

when (option) {
is ChatInfoItem.Option.Stateful.MuteChannel -> viewModel.onAction(
GroupChatInfoViewModel.Action.MuteChannelClicked(isChecked)
)
else -> throw IllegalStateException("Chat info option $option is not supported!")
}
}
adapter.setChatInfoOptionClickListener { option ->
when (option) {
Expand All @@ -186,6 +194,9 @@ class GroupChatInfoFragment : Fragment() {
}
.show(parentFragmentManager, ConfirmationDialogFragment.TAG)
}
is ChatInfoItem.Option.HideChannel -> prepareHideChannelClickedAction {
viewModel.onAction(it)
}
else -> throw IllegalStateException("Group chat info option $option is not supported!")
}
}
Expand All @@ -199,4 +210,43 @@ class GroupChatInfoFragment : Fragment() {
viewModel.onAction(GroupChatInfoViewModel.Action.ChannelMutesUpdated(it.me.channelMutes))
}
}

private fun subscribeForChannelVisibilityEvents() {
ChatClient.instance().subscribeFor<ChannelHiddenEvent>(viewLifecycleOwner) {
viewModel.onAction(GroupChatInfoViewModel.Action.ChannelHiddenUpdated(
cid = it.cid,
hidden = true,
clearHistory = it.clearHistory
))
}
ChatClient.instance().subscribeFor<ChannelVisibleEvent>(viewLifecycleOwner) {
viewModel.onAction(GroupChatInfoViewModel.Action.ChannelHiddenUpdated(
cid = it.cid,
hidden = false
))
}
}

private fun prepareHideChannelClickedAction(
onReady: (GroupChatInfoViewModel.Action.HideChannelClicked) -> Unit
) {
val curValue = viewModel.state.value!!.channelHidden
val newValue = curValue.not()
val action = GroupChatInfoViewModel.Action.HideChannelClicked(newValue)
if (newValue) {
val channelName = viewModel.state.value!!.channelName
ConfirmationDialogFragment.newHideChannelInstance(requireContext(), channelName)
.apply {
confirmClickListener = ConfirmationDialogFragment.ConfirmClickListener {
onReady(action.copy(clearHistory = true))
}
cancelClickListener = ConfirmationDialogFragment.CancelClickListener {
onReady(action)
}
}
.show(parentFragmentManager, ConfirmationDialogFragment.TAG)
} else {
onReady(action)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@ import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.User
import io.getstream.chat.android.state.extensions.watchChannelAsState
import io.getstream.chat.android.state.utils.Event
import io.getstream.log.taggedLogger
import io.getstream.result.Result
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch

class GroupChatInfoViewModel(
Expand All @@ -44,6 +47,8 @@ class GroupChatInfoViewModel(
private val clientState: ClientState = chatClient.clientState,
) : ViewModel() {

private val logger by taggedLogger("GroupChatInfo-VM")

/**
* Holds information about the current channel and is actively updated.
*/
Expand Down Expand Up @@ -74,16 +79,29 @@ class GroupChatInfoViewModel(
createdBy = channelData.createdBy,
)
}

// TODO we use take(1), cause ChannelState.hidden seems to be not updated properly
_state.addSource(channelState.flatMapLatest { it.hidden }.distinctUntilChanged().take(1).asLiveData()) { hidden ->
logger.v { "[onHiddenChanged] hidden: $hidden" }
_state.value = _state.value?.copy(
channelHidden = hidden,
)
}
}

fun onAction(action: Action) {
when (action) {
is Action.NameChanged -> changeGroupName(action.name)
is Action.MemberClicked -> handleMemberClick(action.member)
Action.MembersSeparatorClicked -> _state.value = _state.value!!.copy(shouldExpandMembers = true)
is Action.MuteChannelClicked -> switchGroupMute(action.isEnabled)
is Action.ChannelMutesUpdated -> updateChannelMuteStatus(action.channelMutes)
Action.LeaveChannelClicked -> leaveChannel()
logger.d { "[onAction] action: $action" }
viewModelScope.launch {
when (action) {
is Action.NameChanged -> changeGroupName(action.name)
is Action.MemberClicked -> handleMemberClick(action.member)
is Action.MembersSeparatorClicked -> _state.value = _state.value!!.copy(shouldExpandMembers = true)
is Action.MuteChannelClicked -> switchGroupMute(action.isEnabled)
is Action.HideChannelClicked -> switchGroupHide(action.isHidden, action.clearHistory)
is Action.ChannelMutesUpdated -> updateChannelMuteStatus(action.channelMutes)
is Action.ChannelHiddenUpdated -> updateChannelHideStatus(action.cid, action.hidden)
is Action.LeaveChannelClicked -> leaveChannel()
}
}
}

Expand Down Expand Up @@ -130,7 +148,13 @@ class GroupChatInfoViewModel(
}

private fun updateChannelMuteStatus(channelMutes: List<ChannelMute>) {
_state.postValue(_state.value!!.copy(channelMuted = channelMutes.any { it.channel.cid == cid }))
_state.value = _state.value!!.copy(channelMuted = channelMutes.any { it.channel.cid == cid })
}

private fun updateChannelHideStatus(eventCid: String, hidden: Boolean) {
if (eventCid != cid) return
logger.v { "[updateChannelHideStatus] hidden: $hidden" }
_state.value = _state.value!!.copy(channelHidden = hidden)
}

private fun switchGroupMute(isEnabled: Boolean) {
Expand All @@ -146,11 +170,26 @@ class GroupChatInfoViewModel(
}
}

private fun switchGroupHide(hide: Boolean, clearHistory: Boolean?) {
logger.v { "[switchGroupHide] hide: $hide, clearHistory: $clearHistory" }
viewModelScope.launch {
val result = if (hide) {
channelClient.hide(clearHistory = clearHistory == true).await()
} else {
channelClient.show().await()
}
if (result is Result.Failure) {
_errorEvents.postValue(Event(ErrorEvent.HideChannelError))
}
}
}

data class State(
val members: List<Member>,
val createdBy: User,
val channelName: String,
val channelMuted: Boolean,
val channelHidden: Boolean,
val shouldExpandMembers: Boolean?,
val membersToShowCount: Int,
val ownCapabilities: Set<String>,
Expand All @@ -159,21 +198,27 @@ class GroupChatInfoViewModel(
sealed class Action {
data class NameChanged(val name: String) : Action()
data class MemberClicked(val member: Member) : Action()
object MembersSeparatorClicked : Action()
data object MembersSeparatorClicked : Action()
data class MuteChannelClicked(val isEnabled: Boolean) : Action()
data class HideChannelClicked(val isHidden: Boolean, val clearHistory: Boolean? = null) : Action()
data class ChannelMutesUpdated(val channelMutes: List<ChannelMute>) : Action()
object LeaveChannelClicked : Action()

data class ChannelHiddenUpdated(
val cid: String, val hidden: Boolean, val clearHistory: Boolean? = null
) : Action()
data object LeaveChannelClicked : Action()
}

sealed class UiEvent {
data class ShowMemberOptions(val member: Member, val channelName: String) : UiEvent()
object RedirectToHome : UiEvent()
data object RedirectToHome : UiEvent()
}

sealed class ErrorEvent {
object ChangeGroupNameError : ErrorEvent()
object MuteChannelError : ErrorEvent()
object LeaveChannelError : ErrorEvent()
data object ChangeGroupNameError : ErrorEvent()
data object MuteChannelError : ErrorEvent()
data object HideChannelError : ErrorEvent()
data object LeaveChannelError : ErrorEvent()
}

companion object {
Expand All @@ -184,6 +229,7 @@ class GroupChatInfoViewModel(
createdBy = User(),
channelName = "",
channelMuted = false,
channelHidden = false,
shouldExpandMembers = null,
membersToShowCount = 0,
emptySet(),
Expand Down
Loading

0 comments on commit fe783b6

Please sign in to comment.