diff --git a/stream-chat-android-compose-sample/build.gradle b/stream-chat-android-compose-sample/build.gradle index e95dd722895..7cb1ab8f249 100644 --- a/stream-chat-android-compose-sample/build.gradle +++ b/stream-chat-android-compose-sample/build.gradle @@ -101,6 +101,7 @@ dependencies { implementation Dependencies.androidxAppCompat implementation Dependencies.materialComponents implementation Dependencies.streamPushFirebase + implementation Dependencies.streamLog // Compose implementation Dependencies.composeUi @@ -109,6 +110,7 @@ dependencies { implementation Dependencies.composeMaterial implementation Dependencies.composeActivity + implementation Dependencies.composeAndroidLifecycle implementation Dependencies.composeViewModel implementation Dependencies.composeAccompanistPermissions implementation Dependencies.composeAccompanistPager diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt index 74ffe722d45..4b7cb85720b 100644 --- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme 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 @@ -46,6 +47,7 @@ import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -55,8 +57,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp +import io.getstream.chat.android.compose.sample.BuildConfig import io.getstream.chat.android.compose.sample.ChatApp import io.getstream.chat.android.compose.sample.R +import io.getstream.chat.android.compose.sample.ui.component.MembersList +import io.getstream.chat.android.compose.sample.vm.MembersViewModel +import io.getstream.chat.android.compose.sample.vm.MembersViewModelFactory import io.getstream.chat.android.compose.state.mediagallerypreview.MediaGalleryPreviewResultType import io.getstream.chat.android.compose.ui.components.composer.MessageInput import io.getstream.chat.android.compose.ui.components.messageoptions.MessageOptionItemVisibility @@ -107,11 +113,19 @@ class MessagesActivity : BaseConnectedActivity() { ) } + private val membersFactory by lazy { + MembersViewModelFactory( + cid = requireNotNull(intent.getStringExtra(KEY_CHANNEL_ID)), + ) + } + private val listViewModel by viewModels(factoryProducer = { factory }) private val attachmentsPickerViewModel by viewModels(factoryProducer = { factory }) private val composerViewModel by viewModels(factoryProducer = { factory }) + private val membersViewModel by viewModels(factoryProducer = { membersFactory }) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -153,15 +167,20 @@ class MessagesActivity : BaseConnectedActivity() { optionVisibility = MessageOptionItemVisibility(), ), ) { - MessagesScreen( - viewModelFactory = factory, - reactionSorting = ReactionSortingByLastReactionAt, - onBackPressed = { finish() }, - onHeaderTitleClick = {}, - onUserAvatarClick = { user -> - Log.i("MessagesActivity", "user avatar clicked: ${user.id}") - }, - ) + Column { + if (BuildConfig.DEBUG) { + MembersList(viewModel = membersViewModel) + } + MessagesScreen( + viewModelFactory = factory, + reactionSorting = ReactionSortingByLastReactionAt, + onBackPressed = { finish() }, + onHeaderTitleClick = {}, + onUserAvatarClick = { user -> + Log.i("MessagesActivity", "user avatar clicked: ${user.id}") + }, + ) + } // MyCustomUi() } diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/MembersList.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/MembersList.kt new file mode 100644 index 00000000000..62acfafc12a --- /dev/null +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/MembersList.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.sample.ui.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import io.getstream.chat.android.compose.sample.vm.MembersViewModel + +@Composable +internal fun MembersList( + viewModel: MembersViewModel, +) { + val memberNames by viewModel.memberNames.collectAsStateWithLifecycle() + LazyRow( + modifier = Modifier.height(32.dp) + .fillMaxWidth() + .background(color = Color.LightGray), + verticalAlignment = Alignment.CenterVertically, + ) { + itemsIndexed(memberNames) { index, (name, isCurrentUser) -> + if (index == 0) { + Spacer(modifier = Modifier.width(16.dp)) + } + val displayName = when (index == memberNames.lastIndex) { + true -> name + else -> "$name, " + } + Text( + modifier = Modifier, + fontWeight = if (isCurrentUser) FontWeight.Bold else FontWeight.Normal, + text = displayName, + ) + if (index == memberNames.lastIndex) { + Spacer(modifier = Modifier.width(16.dp)) + } else { + Spacer(modifier = Modifier.width(4.dp)) + } + } + } +} diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/vm/MembersViewModel.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/vm/MembersViewModel.kt new file mode 100644 index 00000000000..f18c91ffa87 --- /dev/null +++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/vm/MembersViewModel.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.sample.vm + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import io.getstream.chat.android.client.ChatClient +import io.getstream.chat.android.models.Channel +import io.getstream.chat.android.state.extensions.watchChannelAsState +import io.getstream.log.taggedLogger +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +internal class MembersViewModel( + private val cid: String, + private val chatClient: ChatClient, +) : ViewModel() { + + private val logger by taggedLogger("Chat:MembersVM") + + private val _memberNames: MutableStateFlow> = MutableStateFlow(emptyList()) + val memberNames: StateFlow> = _memberNames + + init { + watchChannel() + } + + private fun watchChannel() { + chatClient.watchChannelAsState(cid = cid, messageLimit = 0, coroutineScope = viewModelScope) + .filterNotNull() + .flatMapLatest { state -> + combine( + state.channelData, + state.membersCount, + state.watcherCount, + ) { _, _, _ -> + state.toChannel() + } + } + .distinctUntilChanged() + .onEach(this::updateMembers) + .launchIn(viewModelScope) + } + + private fun updateMembers(channel: Channel) { + val currentUserId = chatClient.getCurrentUser()?.id + val names = channel.members.map { + val isCurrentUser = currentUserId == it.user.id + MemberName( + name = if (isCurrentUser) "You" else it.user.name, + isCurrentUser = isCurrentUser, + ) + } + logger.d { "[updateMembers] names: ${names.map { it.name }}" } + _memberNames.value = names + } +} + +internal data class MemberName(val name: String, val isCurrentUser: Boolean) + +internal class MembersViewModelFactory( + private val cid: String, + private val chatClient: ChatClient = ChatClient.instance(), +) : ViewModelProvider.Factory { + + override fun create(modelClass: Class): T { + return MembersViewModel(cid, chatClient) as T + } +}