diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b92637c9b0..306ab5959c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### ⬆️ Improved ### ✅ Added +- Add `Channel.membership.pinnedAt` property notifiying if/when a channel was pinned by the current user. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) +- Add `Channel.membership.archiveAt` property notifiying if/when a channel was archived by the current user. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) ### ⚠️ Changed @@ -16,6 +18,10 @@ ### ⬆️ Improved ### ✅ Added +- Add `ChatClient.pinChannel` and `ChannelClient.unpinChannel` methods to pin/unpin a channel. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) +- Add `ChatClient.archiveChannel` and `ChannelClient.unarchiveChannel` methods to archive/unarchive a channel. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) +- Add `ChannelClient.pinChannel` and `ChannelClient.unpinChannel` methods to pin/unpin a channel. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) +- Add `ChannelClient.archiveChannel` and `ChannelClient.unarchiveChannel` methods to archive/unarchive a channel. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) ### ⚠️ Changed @@ -75,6 +81,8 @@ ### ✅ Added - The `StreamAttachmentFactories.defaultFactories()` method now accepts a `skipTypes` parameter to skip specific factory types. [#5494](https://github.com/GetStream/stream-chat-android/pull/5494) - Add `ChatTheme.keyboardBehaviour` property to customize different keyboard behaviours. [#5506](https://github.com/GetStream/stream-chat-android/pull/5506) +- Add `MessageOptionItemVisibility.isBlockUserVisible` property to show/hide the block user option. [#5512](https://github.com/GetStream/stream-chat-android/pull/5512) +- Add `ChatTheme.channelOptionsTheme` property to customize the channel options. [#5513](https://github.com/GetStream/stream-chat-android/pull/5513) ### ⚠️ Changed diff --git a/stream-chat-android-client/api/stream-chat-android-client.api b/stream-chat-android-client/api/stream-chat-android-client.api index 23e58981d58..f0ee449b27d 100644 --- a/stream-chat-android-client/api/stream-chat-android-client.api +++ b/stream-chat-android-client/api/stream-chat-android-client.api @@ -15,6 +15,7 @@ public final class io/getstream/chat/android/client/ChatClient { public static synthetic fun addMembers$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/chat/android/models/Message;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/result/call/Call; public final fun addSocketListener (Lio/getstream/chat/android/client/socket/SocketListener;)V public final fun appSettings ()Lio/getstream/result/call/Call; + public final fun archiveChannel (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun banUser (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lio/getstream/result/call/Call; public final fun blockUser (Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun castPollAnswer (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; @@ -92,6 +93,7 @@ public final class io/getstream/chat/android/client/ChatClient { public static final fun instance ()Lio/getstream/chat/android/client/ChatClient; public final fun inviteMembers (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/chat/android/models/Message;Ljava/lang/Boolean;)Lio/getstream/result/call/Call; public static synthetic fun inviteMembers$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/getstream/chat/android/models/Message;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/result/call/Call; + public static final fun isInitialized ()Z public final fun isSocketConnected ()Z public final fun keystroke (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public static synthetic fun keystroke$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/result/call/Call; @@ -116,6 +118,7 @@ public final class io/getstream/chat/android/client/ChatClient { public static synthetic fun partialUpdateThread$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/result/call/Call; public final fun partialUpdateUser (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;)Lio/getstream/result/call/Call; public static synthetic fun partialUpdateUser$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/result/call/Call; + public final fun pinChannel (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun pinMessage (Lio/getstream/chat/android/models/Message;I)Lio/getstream/result/call/Call; public final fun pinMessage (Lio/getstream/chat/android/models/Message;Ljava/util/Date;)Lio/getstream/result/call/Call; public static synthetic fun pinMessage$default (Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/models/Message;Ljava/util/Date;ILjava/lang/Object;)Lio/getstream/result/call/Call; @@ -190,6 +193,7 @@ public final class io/getstream/chat/android/client/ChatClient { public final fun translate (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun truncateChannel (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/models/Message;)Lio/getstream/result/call/Call; public static synthetic fun truncateChannel$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/models/Message;ILjava/lang/Object;)Lio/getstream/result/call/Call; + public final fun unarchiveChannel (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun unbanUser (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun unblockUser (Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun unflagMessage (Ljava/lang/String;)Lio/getstream/result/call/Call; @@ -197,6 +201,7 @@ public final class io/getstream/chat/android/client/ChatClient { public final fun unmuteChannel (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun unmuteCurrentUser ()Lio/getstream/result/call/Call; public final fun unmuteUser (Ljava/lang/String;)Lio/getstream/result/call/Call; + public final fun unpinChannel (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun unpinMessage (Lio/getstream/chat/android/models/Message;)Lio/getstream/result/call/Call; public final fun updateChannel (Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/models/Message;Ljava/util/Map;)Lio/getstream/result/call/Call; public static synthetic fun updateChannel$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Ljava/lang/String;Lio/getstream/chat/android/models/Message;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/result/call/Call; @@ -630,6 +635,7 @@ public final class io/getstream/chat/android/client/channel/ChannelClient { public final fun acceptInvite (Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun addMembers (Ljava/util/List;Lio/getstream/chat/android/models/Message;Ljava/lang/Boolean;Ljava/lang/Boolean;)Lio/getstream/result/call/Call; public static synthetic fun addMembers$default (Lio/getstream/chat/android/client/channel/ChannelClient;Ljava/util/List;Lio/getstream/chat/android/models/Message;Ljava/lang/Boolean;Ljava/lang/Boolean;ILjava/lang/Object;)Lio/getstream/result/call/Call; + public final fun archive ()Lio/getstream/result/call/Call; public final fun banUser (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lio/getstream/result/call/Call; public final fun create (Ljava/util/List;Ljava/util/Map;)Lio/getstream/result/call/Call; public final fun delete ()Lio/getstream/result/call/Call; @@ -674,6 +680,7 @@ public final class io/getstream/chat/android/client/channel/ChannelClient { public static synthetic fun muteUser$default (Lio/getstream/chat/android/client/channel/ChannelClient;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/result/call/Call; public final fun partialUpdateMember (Ljava/lang/String;Ljava/util/Map;Ljava/util/List;)Lio/getstream/result/call/Call; public static synthetic fun partialUpdateMember$default (Lio/getstream/chat/android/client/channel/ChannelClient;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lio/getstream/result/call/Call; + public final fun pin ()Lio/getstream/result/call/Call; public final fun pinMessage (Lio/getstream/chat/android/models/Message;I)Lio/getstream/result/call/Call; public final fun pinMessage (Lio/getstream/chat/android/models/Message;Ljava/util/Date;)Lio/getstream/result/call/Call; public final fun query (Lio/getstream/chat/android/client/api/models/QueryChannelRequest;)Lio/getstream/result/call/Call; @@ -723,10 +730,12 @@ public final class io/getstream/chat/android/client/channel/ChannelClient { public final fun truncate ()Lio/getstream/result/call/Call; public final fun truncate (Lio/getstream/chat/android/models/Message;)Lio/getstream/result/call/Call; public static synthetic fun truncate$default (Lio/getstream/chat/android/client/channel/ChannelClient;Lio/getstream/chat/android/models/Message;ILjava/lang/Object;)Lio/getstream/result/call/Call; + public final fun unarchive ()Lio/getstream/result/call/Call; public final fun unbanUser (Ljava/lang/String;)Lio/getstream/result/call/Call; public final fun unmute ()Lio/getstream/result/call/Call; public final fun unmuteCurrentUser ()Lio/getstream/result/call/Call; public final fun unmuteUser (Ljava/lang/String;)Lio/getstream/result/call/Call; + public final fun unpin ()Lio/getstream/result/call/Call; public final fun unpinMessage (Lio/getstream/chat/android/models/Message;)Lio/getstream/result/call/Call; public final fun update (Lio/getstream/chat/android/models/Message;Ljava/util/Map;)Lio/getstream/result/call/Call; public static synthetic fun update$default (Lio/getstream/chat/android/client/channel/ChannelClient;Lio/getstream/chat/android/models/Message;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/result/call/Call; @@ -2487,7 +2496,9 @@ public final class io/getstream/chat/android/client/extensions/ChannelExtensionK public static final fun countUnreadMentionsForUser (Lio/getstream/chat/android/models/Channel;Lio/getstream/chat/android/models/User;)I public static final fun getCurrentUserUnreadCount (Lio/getstream/chat/android/models/Channel;)I public static final fun isAnonymousChannel (Lio/getstream/chat/android/models/Channel;)Z + public static final fun isArchive (Lio/getstream/chat/android/models/Channel;)Z public static final fun isMutedFor (Lio/getstream/chat/android/models/Channel;Lio/getstream/chat/android/models/User;)Z + public static final fun isPinned (Lio/getstream/chat/android/models/Channel;)Z public static final fun syncUnreadCountWithReads (Lio/getstream/chat/android/models/Channel;)Lio/getstream/chat/android/models/Channel; } diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt index ea5d6641bfe..53058ccc5df 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/ChatClient.kt @@ -1944,6 +1944,92 @@ internal constructor( ) } + /** + * Pin the channel for the current user. + * + * @param channelType The channel type. + * @param channelId The channel ID. + * + * @return Executable async [Call] responsible for pinning the channel. + */ + public fun pinChannel( + channelType: String, + channelId: String, + ): Call { + logger.d { "[pinChannel] channelType: $channelType, channelId: $channelId" } + val set = mapOf("pinned" to true) + return partialUpdateMember( + channelType = channelType, + channelId = channelId, + userId = getCurrentUser()?.id ?: "", + set = set, + ) + } + + /** + * Unpin the channel for the current user. + * + * @param channelType The channel type. + * @param channelId The channel ID. + * + * @return Executable async [Call] responsible for unpinning the channel. + */ + public fun unpinChannel( + channelType: String, + channelId: String, + ): Call { + logger.d { "[unpinChannel] channelType: $channelType, channelId: $channelId" } + return partialUpdateMember( + channelType = channelType, + channelId = channelId, + userId = getCurrentUser()?.id ?: "", + unset = listOf("pinned"), + ) + } + + /** + * Archive the channel for the current user. + * + * @param channelType The channel type. + * @param channelId The channel ID. + * + * @return Executable async [Call] responsible for archiving the channel. + */ + public fun archiveChannel( + channelType: String, + channelId: String, + ): Call { + logger.d { "[archiveChannel] channelType: $channelType, channelId: $channelId" } + val set = mapOf("archived" to true) + return partialUpdateMember( + channelType = channelType, + channelId = channelId, + userId = getCurrentUser()?.id ?: "", + set = set, + ) + } + + /** + * Unarchive the channel for the current user. + * + * @param channelType The channel type. + * @param channelId The channel ID. + * + * @return Executable async [Call] responsible for un-archiving the channel. + */ + public fun unarchiveChannel( + channelType: String, + channelId: String, + ): Call { + logger.d { "[unarchiveChannel]channelType: $channelType, channelId: $channelId" } + return partialUpdateMember( + channelType = channelType, + channelId = channelId, + userId = getCurrentUser()?.id ?: "", + unset = listOf("archived"), + ) + } + /** * Pins the message. * @@ -3821,6 +3907,7 @@ internal constructor( ) } + @JvmStatic public val isInitialized: Boolean get() = instance != null diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/internal/ExtraDataValidator.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/internal/ExtraDataValidator.kt index 8ef28acd472..7e0de297f0c 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/internal/ExtraDataValidator.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api/internal/ExtraDataValidator.kt @@ -239,6 +239,8 @@ internal class ExtraDataValidator( "notifications_muted", "status", "ban_expires", + "pinned_at", + "archived_at", ) private val reservedInChannelPredicate: (String) -> Boolean = reservedInChannel::contains diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/MemberMapping.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/MemberMapping.kt index e524a353678..e88356f63fe 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/MemberMapping.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/mapping/MemberMapping.kt @@ -35,6 +35,8 @@ internal fun DownstreamMemberDto.toDomain(currentUserId: UserId?): Member = notificationsMuted = notifications_muted, status = status, banExpires = ban_expires, + pinnedAt = pinned_at, + archivedAt = archived_at, extraData = extraData, ) @@ -52,5 +54,7 @@ internal fun Member.toDto(): UpstreamMemberDto = notifications_muted = notificationsMuted, status = status, ban_expires = banExpires, + pinned_at = pinnedAt, + archived_at = archivedAt, extraData = extraData, ) diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MemberDtos.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MemberDtos.kt index c3aba0c9b4c..39266e6b132 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MemberDtos.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/api2/model/dto/MemberDtos.kt @@ -42,7 +42,8 @@ internal data class UpstreamMemberDto( val notifications_muted: Boolean?, val status: String?, val ban_expires: Date?, - + val pinned_at: Date?, + val archived_at: Date?, val extraData: Map, ) : ExtraDataDto @@ -68,6 +69,7 @@ internal data class DownstreamMemberDto( val notifications_muted: Boolean?, val status: String?, val ban_expires: Date?, - + val pinned_at: Date?, + val archived_at: Date?, val extraData: Map, ) : ExtraDataDto diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/channel/ChannelClient.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/channel/ChannelClient.kt index 638cb4edda8..fc06f2f9949 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/channel/ChannelClient.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/channel/ChannelClient.kt @@ -967,4 +967,36 @@ public class ChannelClient internal constructor( @CheckResult public fun unpinMessage(message: Message): Call = client.unpinMessage(message) + + /** + * Pins the channel for the current user. + * + * @return Executable async [Call] responsible for pinning the channel. + */ + @CheckResult + public fun pin(): Call = client.pinChannel(channelType, channelId) + + /** + * Unpins the channel for the current user. + * + * @return Executable async [Call] responsible for unpinning the channel. + */ + @CheckResult + public fun unpin(): Call = client.unpinChannel(channelType, channelId) + + /** + * Archives the channel for the current user. + * + * @return Executable async [Call] responsible for archiving the channel. + */ + @CheckResult + public fun archive(): Call = client.archiveChannel(channelType, channelId) + + /** + * Un-archives the channel for the current user. + * + * @return Executable async [Call] responsible for un-archiving the channel. + */ + @CheckResult + public fun unarchive(): Call = client.unarchiveChannel(channelType, channelId) } diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/ChannelExtension.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/ChannelExtension.kt index 826bea94de9..44d12a57536 100644 --- a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/ChannelExtension.kt +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/extensions/ChannelExtension.kt @@ -26,6 +26,20 @@ import io.getstream.chat.android.models.User public fun Channel.isAnonymousChannel(): Boolean = id.isAnonymousChannelId() +/** + * Checks if the channel is pinned or not for the current user. + * + * @return True if the channel is pinned for the current user. + */ +public fun Channel.isPinned(): Boolean = membership?.pinnedAt != null + +/** + * Checks if the channel is archived or not for the current user. + * + * @return True if the channel is archived for the current user. + */ +public fun Channel.isArchive(): Boolean = membership?.archivedAt != null + /** * Checks if [Channel] is muted for [user]. * diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/channel/ChannelArchiveTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/channel/ChannelArchiveTest.kt new file mode 100644 index 00000000000..be7bbe0a431 --- /dev/null +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/channel/ChannelArchiveTest.kt @@ -0,0 +1,98 @@ +/* + * 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.client.channel + +import android.annotation.SuppressLint +import io.getstream.chat.android.client.ChatClient +import io.getstream.chat.android.client.MockClientBuilder +import io.getstream.chat.android.test.TestCoroutineExtension +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify + +internal class ChannelArchiveTest { + + companion object { + const val CHANNEL_TYPE = "type" + const val CHANNEL_ID = "id" + const val USER_ID = "jc" + + @JvmField + @RegisterExtension + val testCoroutines = TestCoroutineExtension() + } + + @SuppressLint("CheckResult") + @Test + fun `When archive is called, it propagates the call to ChatClient`() { + // Given + val chatClient = mock() + val channelClient = ChannelClient(CHANNEL_TYPE, CHANNEL_ID, chatClient) + + // When + channelClient.archive() + + // Then + verify(chatClient).archiveChannel(CHANNEL_TYPE, CHANNEL_ID) + } + + @SuppressLint("CheckResult") + @Test + fun `When unarchive is called, it propagates the call to ChatClient`() { + // Given + val chatClient = mock() + val channelClient = ChannelClient(CHANNEL_TYPE, CHANNEL_ID, chatClient) + + // When + channelClient.unarchive() + + // Then + verify(chatClient).unarchiveChannel(CHANNEL_TYPE, CHANNEL_ID) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @SuppressLint("CheckResult") + @Test + fun `When archiveChannel is called it does partial update with rchived true`() { + // Given + val chatClient = MockClientBuilder(testCoroutineExtension = testCoroutines).build() + val spyClient = spy(chatClient) + // When + spyClient.archiveChannel(CHANNEL_TYPE, CHANNEL_ID) + + // Then + verify(spyClient).partialUpdateMember(CHANNEL_TYPE, CHANNEL_ID, USER_ID, set = mapOf("archived" to true)) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @SuppressLint("CheckResult") + @Test + fun `When unpinChannel is called it does partial update with archived as unset option`() = runTest { + // Given + val chatClient = MockClientBuilder(testCoroutineExtension = testCoroutines).build() + val spyClient = spy(chatClient) + // When + spyClient.unarchiveChannel(CHANNEL_TYPE, CHANNEL_ID) + + // Then + verify(spyClient).partialUpdateMember(CHANNEL_TYPE, CHANNEL_ID, USER_ID, unset = listOf("archived")) + } +} diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/channel/ChannelPinTest.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/channel/ChannelPinTest.kt new file mode 100644 index 00000000000..1fdf29064ff --- /dev/null +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/channel/ChannelPinTest.kt @@ -0,0 +1,98 @@ +/* + * 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.client.channel + +import android.annotation.SuppressLint +import io.getstream.chat.android.client.ChatClient +import io.getstream.chat.android.client.MockClientBuilder +import io.getstream.chat.android.test.TestCoroutineExtension +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import org.mockito.Mockito.mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify + +internal class ChannelPinTest { + + companion object { + const val CHANNEL_TYPE = "type" + const val CHANNEL_ID = "id" + const val USER_ID = "jc" + + @JvmField + @RegisterExtension + val testCoroutines = TestCoroutineExtension() + } + + @SuppressLint("CheckResult") + @Test + fun `When pin is called, it propagates the call to ChatClient`() { + // Given + val chatClient = mock() + val channelClient = ChannelClient(CHANNEL_TYPE, CHANNEL_ID, chatClient) + + // When + channelClient.pin() + + // Then + verify(chatClient).pinChannel(CHANNEL_TYPE, CHANNEL_ID) + } + + @SuppressLint("CheckResult") + @Test + fun `When unpin is called, it propagates the call to ChatClient`() { + // Given + val chatClient = mock() + val channelClient = ChannelClient(CHANNEL_TYPE, CHANNEL_ID, chatClient) + + // When + channelClient.unpin() + + // Then + verify(chatClient).unpinChannel(CHANNEL_TYPE, CHANNEL_ID) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @SuppressLint("CheckResult") + @Test + fun `When pinChannel is called it does partial update with pinned true`() { + // Given + val chatClient = MockClientBuilder(testCoroutineExtension = testCoroutines).build() + val spyClient = spy(chatClient) + // When + spyClient.pinChannel(CHANNEL_TYPE, CHANNEL_ID) + + // Then + verify(spyClient).partialUpdateMember(CHANNEL_TYPE, CHANNEL_ID, USER_ID, set = mapOf("pinned" to true)) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @SuppressLint("CheckResult") + @Test + fun `When unpinChannel is called it does partial update with pinned as unset option`() = runTest { + // Given + val chatClient = MockClientBuilder(testCoroutineExtension = testCoroutines).build() + val spyClient = spy(chatClient) + // When + spyClient.unpinChannel(CHANNEL_TYPE, CHANNEL_ID) + + // Then + verify(spyClient).partialUpdateMember(CHANNEL_TYPE, CHANNEL_ID, USER_ID, unset = listOf("pinned")) + } +} diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ChannelDtoTestData.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ChannelDtoTestData.kt index 56860ab353f..c9b46af168e 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ChannelDtoTestData.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ChannelDtoTestData.kt @@ -125,7 +125,9 @@ internal object ChannelDtoTestData { "channel_role": "member", "notifications_muted": false, "status": "member", - "ban_expires" : "2021-03-08T15:42:31.355Z" + "ban_expires" : "2021-03-08T15:42:31.355Z", + "pinned_at": "2020-06-10T11:04:31.588Z", + "archived_at": "2020-06-10T11:04:31.588Z" } ], "watchers": [${UserDtoTestData.downstreamJson}], @@ -186,6 +188,8 @@ internal object ChannelDtoTestData { notifications_muted = false, status = "member", ban_expires = Date(1615218151355), + pinned_at = Date(1591787071588), + archived_at = Date(1591787071588), extraData = emptyMap(), ), ), @@ -217,6 +221,8 @@ internal object ChannelDtoTestData { notifications_muted = false, status = "member", ban_expires = null, + pinned_at = null, + archived_at = null, extraData = emptyMap(), ), extraData = mapOf("draft" to true), @@ -326,7 +332,9 @@ internal object ChannelDtoTestData { "channel_role": "member", "notifications_muted": false, "status": "member", - "ban_expires" : "2021-03-08T15:42:31.355Z" + "ban_expires" : "2021-03-08T15:42:31.355Z", + "pinned_at": "2020-06-10T11:04:31.588Z", + "archived_at": "2020-06-10T11:04:31.588Z" } ], "watchers": [${UserDtoTestData.upstreamJson}], @@ -373,6 +381,8 @@ internal object ChannelDtoTestData { notifications_muted = false, status = "member", ban_expires = Date(1615218151355), + pinned_at = Date(1591787071588), + archived_at = Date(1591787071588), extraData = emptyMap(), ), ), diff --git a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MemberDtoTestData.kt b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MemberDtoTestData.kt index c11fe2283d8..b12bb4c89ab 100644 --- a/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MemberDtoTestData.kt +++ b/stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MemberDtoTestData.kt @@ -69,6 +69,8 @@ internal object MemberDtoTestData { notifications_muted = null, status = null, ban_expires = null, + pinned_at = null, + archived_at = null, extraData = mapOf("is_premium" to true), ) @@ -85,6 +87,8 @@ internal object MemberDtoTestData { notifications_muted = null, status = null, ban_expires = null, + pinned_at = null, + archived_at = null, extraData = emptyMap(), ) @@ -101,6 +105,8 @@ internal object MemberDtoTestData { notifications_muted = null, status = null, ban_expires = null, + pinned_at = null, + archived_at = null, extraData = mapOf("is_premium" to true), ) @@ -117,6 +123,8 @@ internal object MemberDtoTestData { notifications_muted = null, status = null, ban_expires = null, + pinned_at = null, + archived_at = null, extraData = emptyMap(), ) } diff --git a/stream-chat-android-compose/api/stream-chat-android-compose.api b/stream-chat-android-compose/api/stream-chat-android-compose.api index 0e4a583a1b2..5c27f8dc11d 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -918,6 +918,30 @@ public final class io/getstream/chat/android/compose/ui/components/channels/Chan public static final fun ChannelMembers (Ljava/util/List;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } +public final class io/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility { + public static final field $stable I + public fun ()V + public fun (ZZZZZZ)V + public synthetic fun (ZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()Z + public final fun component3 ()Z + public final fun component4 ()Z + public final fun component5 ()Z + public final fun component6 ()Z + public final fun copy (ZZZZZZ)Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility;ZZZZZZILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public final fun isArchiveChannelVisible ()Z + public final fun isDeleteChannelVisible ()Z + public final fun isLeaveChannelVisible ()Z + public final fun isMuteChannelVisible ()Z + public final fun isPinChannelVisible ()Z + public final fun isViewInfoVisible ()Z + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/compose/ui/components/channels/ChannelOptionsKt { public static final fun ChannelOptions (Ljava/util/List;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V public static final fun buildDefaultChannelOptionsState (Lio/getstream/chat/android/models/Channel;ZLjava/util/Set;Landroidx/compose/runtime/Composer;I)Ljava/util/List; @@ -1032,9 +1056,10 @@ public final class io/getstream/chat/android/compose/ui/components/messageoption public final class io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility { public static final field $stable I public fun ()V - public fun (ZZZZZZZZZ)V - public synthetic fun (ZZZZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (ZZZZZZZZZZ)V + public synthetic fun (ZZZZZZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Z + public final fun component10 ()Z public final fun component2 ()Z public final fun component3 ()Z public final fun component4 ()Z @@ -1043,10 +1068,11 @@ public final class io/getstream/chat/android/compose/ui/components/messageoption public final fun component7 ()Z public final fun component8 ()Z public final fun component9 ()Z - public final fun copy (ZZZZZZZZZ)Lio/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility; - public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility;ZZZZZZZZZILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility; + public final fun copy (ZZZZZZZZZZ)Lio/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility;ZZZZZZZZZZILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility; public fun equals (Ljava/lang/Object;)Z public fun hashCode ()I + public final fun isBlockUserVisible ()Z public final fun isCopyTextVisible ()Z public final fun isDeleteMessageVisible ()Z public final fun isEditMessageVisible ()Z @@ -2014,6 +2040,24 @@ public final class io/getstream/chat/android/compose/ui/theme/AttachmentPickerTh public final fun defaultTheme (Lio/getstream/chat/android/compose/ui/theme/StreamColors;Landroidx/compose/runtime/Composer;II)Lio/getstream/chat/android/compose/ui/theme/AttachmentPickerTheme; } +public final class io/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme { + public static final field $stable I + public static final field Companion Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme$Companion; + public fun (Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility;)V + public final fun component1 ()Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility; + public final fun copy (Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility;)Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme; + public static synthetic fun copy$default (Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme;Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme; + public fun equals (Ljava/lang/Object;)Z + public final fun getOptionVisibility ()Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme$Companion { + public final fun defaultTheme (Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility;)Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme; + public static synthetic fun defaultTheme$default (Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme$Companion;Lio/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme; +} + public final class io/getstream/chat/android/compose/ui/theme/ChatTheme { public static final field $stable I public static final field INSTANCE Lio/getstream/chat/android/compose/ui/theme/ChatTheme; @@ -2023,6 +2067,7 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatTheme { public final fun getAttachmentsPickerTabFactories (Landroidx/compose/runtime/Composer;I)Ljava/util/List; public final fun getAutoTranslationEnabled (Landroidx/compose/runtime/Composer;I)Z public final fun getChannelNameFormatter (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter; + public final fun getChannelOptionsTheme (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme; public final fun getColors (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/compose/ui/theme/StreamColors; public final fun getDateFormatter (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/ui/common/helper/DateFormatter; public final fun getDimens (Landroidx/compose/runtime/Composer;I)Lio/getstream/chat/android/compose/ui/theme/StreamDimens; @@ -2061,7 +2106,7 @@ public final class io/getstream/chat/android/compose/ui/theme/ChatTheme { } public final class io/getstream/chat/android/compose/ui/theme/ChatThemeKt { - public static final fun ChatTheme (ZZZZLio/getstream/chat/android/compose/ui/theme/StreamColors;Lio/getstream/chat/android/compose/ui/theme/StreamDimens;Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Lio/getstream/chat/android/compose/ui/theme/StreamRippleConfiguration;Ljava/util/List;Lio/getstream/chat/android/compose/ui/components/messages/factory/MessageContentFactory;Ljava/util/List;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionIconFactory;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory;Lio/getstream/chat/android/compose/ui/util/PollSwitchItemFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/ui/common/helper/ImageHeadersProvider;Lio/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator;Lio/getstream/chat/android/ui/common/helper/DownloadRequestInterceptor;Lio/getstream/chat/android/ui/common/helper/ImageAssetTransformer;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/ui/common/state/messages/list/MessageOptionsUserReactionAlignment;Ljava/util/List;ZLio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;ZLio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageDateSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageUnreadSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/theme/AttachmentPickerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/chat/android/compose/ui/util/QuotedMessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lio/getstream/chat/android/compose/ui/theme/StreamKeyboardBehaviour;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIIIII)V + public static final fun ChatTheme (ZZZZLio/getstream/chat/android/compose/ui/theme/StreamColors;Lio/getstream/chat/android/compose/ui/theme/StreamDimens;Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Lio/getstream/chat/android/compose/ui/theme/StreamRippleConfiguration;Ljava/util/List;Lio/getstream/chat/android/compose/ui/components/messages/factory/MessageContentFactory;Ljava/util/List;Ljava/util/List;Lio/getstream/chat/android/compose/ui/util/ReactionIconFactory;Lio/getstream/chat/android/compose/ui/theme/ReactionOptionsTheme;Lio/getstream/chat/android/compose/ui/util/MessagePreviewIconFactory;Lio/getstream/chat/android/compose/ui/util/PollSwitchItemFactory;ZLio/getstream/chat/android/ui/common/helper/DateFormatter;Lio/getstream/chat/android/ui/common/helper/TimeProvider;Lio/getstream/chat/android/ui/common/utils/ChannelNameFormatter;Lio/getstream/chat/android/compose/ui/util/MessagePreviewFormatter;Lio/getstream/chat/android/compose/ui/util/SearchResultNameFormatter;Lio/getstream/chat/android/compose/ui/util/StreamCoilImageLoaderFactory;Lio/getstream/chat/android/ui/common/helper/ImageHeadersProvider;Lio/getstream/chat/android/ui/common/helper/DownloadAttachmentUriGenerator;Lio/getstream/chat/android/ui/common/helper/DownloadRequestInterceptor;Lio/getstream/chat/android/ui/common/helper/ImageAssetTransformer;Lio/getstream/chat/android/compose/ui/util/MessageAlignmentProvider;Lio/getstream/chat/android/compose/ui/theme/MessageOptionsTheme;Lio/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme;Lio/getstream/chat/android/ui/common/state/messages/list/MessageOptionsUserReactionAlignment;Ljava/util/List;ZLio/getstream/chat/android/ui/common/images/resizing/StreamCdnImageResizing;ZLio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageTheme;Lio/getstream/chat/android/compose/ui/theme/MessageDateSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageUnreadSeparatorTheme;Lio/getstream/chat/android/compose/ui/theme/MessageComposerTheme;Lio/getstream/chat/android/compose/ui/theme/AttachmentPickerTheme;Lio/getstream/chat/android/compose/ui/util/MessageTextFormatter;Lio/getstream/chat/android/compose/ui/util/QuotedMessageTextFormatter;Lio/getstream/sdk/chat/audio/recording/StreamMediaRecorder;Lio/getstream/chat/android/compose/ui/theme/StreamKeyboardBehaviour;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;IIIIIII)V } public final class io/getstream/chat/android/compose/ui/theme/ComponentOffset { @@ -3438,6 +3483,7 @@ public final class io/getstream/chat/android/compose/viewmodel/channels/ChannelL public static final field $stable I public fun (Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/models/querysort/QuerySorter;Lio/getstream/chat/android/models/FilterObject;IIILio/getstream/chat/android/state/event/handler/chat/factory/ChatEventHandlerFactory;J)V public synthetic fun (Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/models/querysort/QuerySorter;Lio/getstream/chat/android/models/FilterObject;IIILio/getstream/chat/android/state/event/handler/chat/factory/ChatEventHandlerFactory;JILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun archiveChannel (Lio/getstream/chat/android/models/Channel;)V public final fun deleteConversation (Lio/getstream/chat/android/models/Channel;)V public final fun dismissChannelAction ()V public final fun getActiveChannelAction ()Lio/getstream/chat/android/ui/common/state/channels/actions/ChannelAction; @@ -3452,13 +3498,16 @@ public final class io/getstream/chat/android/compose/viewmodel/channels/ChannelL public final fun loadMore ()V public final fun muteChannel (Lio/getstream/chat/android/models/Channel;)V public final fun performChannelAction (Lio/getstream/chat/android/ui/common/state/channels/actions/ChannelAction;)V + public final fun pinChannel (Lio/getstream/chat/android/models/Channel;)V public final fun refresh ()V public final fun selectChannel (Lio/getstream/chat/android/models/Channel;)V public final fun setFilters (Lio/getstream/chat/android/models/FilterObject;)V public final fun setQuerySort (Lio/getstream/chat/android/models/querysort/QuerySorter;)V public final fun setSearchQuery (Lio/getstream/chat/android/compose/state/channels/list/SearchQuery;)V public final fun setSearchQuery (Ljava/lang/String;)V + public final fun unarchiveChannel (Lio/getstream/chat/android/models/Channel;)V public final fun unmuteChannel (Lio/getstream/chat/android/models/Channel;)V + public final fun unpinChannel (Lio/getstream/chat/android/models/Channel;)V } public final class io/getstream/chat/android/compose/viewmodel/channels/ChannelViewModelFactory : androidx/lifecycle/ViewModelProvider$Factory { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewActivity.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewActivity.kt index ee7e38f5005..23f94d35e5c 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewActivity.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaGalleryPreviewActivity.kt @@ -228,6 +228,11 @@ public class MediaGalleryPreviewActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (ChatClient.isInitialized.not()) { + finish() + return + } + uiState = savedInstanceState?.getParcelable( KeyMediaGalleryPreviewActivityState, ) ?: intent?.getParcelableExtra(KeyMediaGalleryPreviewActivityState) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaPreviewActivity.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaPreviewActivity.kt index dc0c666f3c6..57cd0346515 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaPreviewActivity.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/attachments/preview/MediaPreviewActivity.kt @@ -56,6 +56,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.isVisible +import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.compose.R import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.ui.util.mirrorRtl @@ -71,7 +72,7 @@ public class MediaPreviewActivity : AppCompatActivity() { val url = intent.getStringExtra(KEY_URL) val title = intent.getStringExtra(KEY_TITLE) ?: "" - if (url.isNullOrEmpty()) { + if (url.isNullOrEmpty() || ChatClient.isInitialized.not()) { finish() return } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/ChannelsScreen.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/ChannelsScreen.kt index c88db25f2c3..53cb39cdb81 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/ChannelsScreen.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/channels/ChannelsScreen.kt @@ -60,10 +60,14 @@ import io.getstream.chat.android.compose.viewmodel.channels.ChannelListViewModel import io.getstream.chat.android.compose.viewmodel.channels.ChannelViewModelFactory import io.getstream.chat.android.models.Channel import io.getstream.chat.android.models.Message +import io.getstream.chat.android.ui.common.state.channels.actions.ArchiveChannel import io.getstream.chat.android.ui.common.state.channels.actions.DeleteConversation import io.getstream.chat.android.ui.common.state.channels.actions.LeaveGroup import io.getstream.chat.android.ui.common.state.channels.actions.MuteChannel +import io.getstream.chat.android.ui.common.state.channels.actions.PinChannel +import io.getstream.chat.android.ui.common.state.channels.actions.UnarchiveChannel import io.getstream.chat.android.ui.common.state.channels.actions.UnmuteChannel +import io.getstream.chat.android.ui.common.state.channels.actions.UnpinChannel import io.getstream.chat.android.ui.common.state.channels.actions.ViewInfo /** @@ -211,6 +215,10 @@ public fun ChannelsScreen( is ViewInfo -> onViewChannelInfoAction(action.channel) is MuteChannel -> listViewModel.muteChannel(action.channel) is UnmuteChannel -> listViewModel.unmuteChannel(action.channel) + is PinChannel -> listViewModel.pinChannel(action.channel) + is UnpinChannel -> listViewModel.unpinChannel(action.channel) + is ArchiveChannel -> listViewModel.archiveChannel(action.channel) + is UnarchiveChannel -> listViewModel.unarchiveChannel(action.channel) else -> listViewModel.performChannelAction(action) } } diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility.kt new file mode 100644 index 00000000000..abfbaaaf653 --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptionItemVisibility.kt @@ -0,0 +1,38 @@ +/* + * 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.ui.components.channels + +/** + * Controls option visibility in the channel options menu. + * + * @param isViewInfoVisible Visibility of the view channel info option. + * @param isLeaveChannelVisible Visibility of the leave channel option. + * @param isMuteChannelVisible Visibility of the mute channel option. + * @param isArchiveChannelVisible Visibility of the archive channel option. + * @param isPinChannelVisible Visibility of the pin channel option. + * @param isDeleteChannelVisible Visibility of the delete channel option. + * + * @see [ChannelOptions] + */ +public data class ChannelOptionItemVisibility( + val isViewInfoVisible: Boolean = true, + val isLeaveChannelVisible: Boolean = true, + val isMuteChannelVisible: Boolean = true, + val isArchiveChannelVisible: Boolean = false, + val isPinChannelVisible: Boolean = false, + val isDeleteChannelVisible: Boolean = true, +) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt index 4570ad5c0d5..2626e41898a 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/channels/ChannelOptions.kt @@ -32,6 +32,8 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import io.getstream.chat.android.client.extensions.isArchive +import io.getstream.chat.android.client.extensions.isPinned import io.getstream.chat.android.compose.R import io.getstream.chat.android.compose.state.channels.list.ChannelOptionState import io.getstream.chat.android.compose.ui.theme.ChatTheme @@ -39,12 +41,16 @@ import io.getstream.chat.android.compose.util.extensions.toSet import io.getstream.chat.android.models.Channel import io.getstream.chat.android.models.ChannelCapabilities import io.getstream.chat.android.previewdata.PreviewChannelData +import io.getstream.chat.android.ui.common.state.channels.actions.ArchiveChannel import io.getstream.chat.android.ui.common.state.channels.actions.Cancel import io.getstream.chat.android.ui.common.state.channels.actions.ChannelAction import io.getstream.chat.android.ui.common.state.channels.actions.DeleteConversation import io.getstream.chat.android.ui.common.state.channels.actions.LeaveGroup import io.getstream.chat.android.ui.common.state.channels.actions.MuteChannel +import io.getstream.chat.android.ui.common.state.channels.actions.PinChannel +import io.getstream.chat.android.ui.common.state.channels.actions.UnarchiveChannel import io.getstream.chat.android.ui.common.state.channels.actions.UnmuteChannel +import io.getstream.chat.android.ui.common.state.channels.actions.UnpinChannel import io.getstream.chat.android.ui.common.state.channels.actions.ViewInfo /** @@ -102,6 +108,7 @@ public fun ChannelOptions( * @param ownCapabilities Set of capabilities the user is given for the current channel. * @return The list of channel option items to display. */ +@Suppress("LongMethod") @Composable public fun buildDefaultChannelOptionsState( selectedChannel: Channel, @@ -112,15 +119,20 @@ public fun buildDefaultChannelOptionsState( val canDeleteChannel = ownCapabilities.contains(ChannelCapabilities.DELETE_CHANNEL) val canMuteChannel = ownCapabilities.contains(ChannelCapabilities.MUTE_CHANNEL) + val optionVisibility = ChatTheme.channelOptionsTheme.optionVisibility return listOfNotNull( - ChannelOptionState( - title = stringResource(id = R.string.stream_compose_selected_channel_menu_view_info), - titleColor = ChatTheme.colors.textHighEmphasis, - iconPainter = painterResource(id = R.drawable.stream_compose_ic_person), - iconColor = ChatTheme.colors.textLowEmphasis, - action = ViewInfo(selectedChannel), - ), - if (canLeaveChannel) { + if (optionVisibility.isViewInfoVisible) { + ChannelOptionState( + title = stringResource(id = R.string.stream_compose_selected_channel_menu_view_info), + titleColor = ChatTheme.colors.textHighEmphasis, + iconPainter = painterResource(id = R.drawable.stream_compose_ic_person), + iconColor = ChatTheme.colors.textLowEmphasis, + action = ViewInfo(selectedChannel), + ) + } else { + null + }, + if (optionVisibility.isLeaveChannelVisible && canLeaveChannel) { ChannelOptionState( title = stringResource(id = R.string.stream_compose_selected_channel_menu_leave_group), titleColor = ChatTheme.colors.textHighEmphasis, @@ -131,8 +143,20 @@ public fun buildDefaultChannelOptionsState( } else { null }, - buildMuteOption(canMuteChannel, isMuted, selectedChannel), - if (canDeleteChannel) { + buildMuteOption( + canMuteChannel = optionVisibility.isMuteChannelVisible && canMuteChannel, + isMuted = isMuted, + selectedChannel = selectedChannel, + ), + buildPinOption( + canPinChannel = optionVisibility.isPinChannelVisible, + selectedChannel = selectedChannel, + ), + buildArchiveOption( + canArchiveChannel = optionVisibility.isArchiveChannelVisible, + selectedChannel = selectedChannel, + ), + if (optionVisibility.isDeleteChannelVisible && canDeleteChannel) { ChannelOptionState( title = stringResource(id = R.string.stream_compose_selected_channel_menu_delete_conversation), titleColor = ChatTheme.colors.errorAccent, @@ -153,6 +177,74 @@ public fun buildDefaultChannelOptionsState( ) } +/** + * Builds the pin option for the channel, based on the current state. + * + * @param canPinChannel If the user can pin the channel. + * @param selectedChannel The currently selected channel. + */ +@Composable +private fun buildPinOption( + canPinChannel: Boolean, + selectedChannel: Channel, +) = when (selectedChannel.isPinned().takeIf { canPinChannel }) { + false -> Triple( + R.string.stream_compose_selected_channel_menu_pin_channel, + R.drawable.stream_compose_ic_pin, + PinChannel(selectedChannel), + ) + + true -> Triple( + R.string.stream_compose_selected_channel_menu_unpin_channel, + R.drawable.stream_compose_ic_unpin, + UnpinChannel(selectedChannel), + ) + + null -> null +}?.let { + ChannelOptionState( + title = stringResource(id = it.first), + titleColor = ChatTheme.colors.textHighEmphasis, + iconPainter = painterResource(id = it.second), + iconColor = ChatTheme.colors.textLowEmphasis, + action = it.third, + ) +} + +/** + * Builds the archive option for the channel, based on the current state. + * + * @param canArchiveChannel If the user can archive the channel. + * @param selectedChannel The currently selected channel. + */ +@Composable +private fun buildArchiveOption( + canArchiveChannel: Boolean, + selectedChannel: Channel, +) = when (selectedChannel.isArchive().takeIf { canArchiveChannel }) { + false -> Triple( + R.string.stream_compose_selected_channel_menu_archive_channel, + R.drawable.stream_compose_ic_archive, + ArchiveChannel(selectedChannel), + ) + + true -> Triple( + R.string.stream_compose_selected_channel_menu_unarchive_channel, + R.drawable.stream_compose_ic_unarchive, + UnarchiveChannel(selectedChannel), + ) + + null -> null +}?.let { + ChannelOptionState( + title = stringResource(id = it.first), + titleColor = ChatTheme.colors.textHighEmphasis, + iconPainter = painterResource(id = it.second), + iconColor = ChatTheme.colors.textLowEmphasis, + action = it.third, + ) +} + @Composable private fun buildMuteOption( canMuteChannel: Boolean, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility.kt index 113354739c9..6e870bd5370 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptionItemVisibility.kt @@ -30,6 +30,7 @@ import io.getstream.chat.android.compose.ui.components.selectedmessage.SelectedM * @param isFlagMessageVisible Visibility of the flag message option. * @param isPinMessageVisible Visibility of the pin message to chat option. * @param isDeleteMessageVisible Visibility of the delete message option. + * @param isBlockUserVisible Visibility of the block user option. * * @see [SelectedMessageMenu] * @see [MessageOptions] @@ -45,4 +46,5 @@ public data class MessageOptionItemVisibility( val isFlagMessageVisible: Boolean = true, val isPinMessageVisible: Boolean = true, val isDeleteMessageVisible: Boolean = true, + val isBlockUserVisible: Boolean = true, ) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptions.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptions.kt index ba9106f460c..9fbb4b4bf20 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptions.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/messageoptions/MessageOptions.kt @@ -236,14 +236,20 @@ public fun defaultMessageOptionsState( MessageOptionItemState( title = if (selectedMessage.pinned) R.string.stream_compose_unpin_message else R.string.stream_compose_pin_message, action = Pin(selectedMessage), - iconPainter = painterResource(id = if (selectedMessage.pinned) R.drawable.stream_compose_ic_unpin_message else R.drawable.stream_compose_ic_pin_message), + iconPainter = painterResource( + id = if (selectedMessage.pinned) { + R.drawable.stream_compose_ic_unpin + } else { + R.drawable.stream_compose_ic_pin + }, + ), iconColor = ChatTheme.colors.textLowEmphasis, titleColor = ChatTheme.colors.textHighEmphasis, ) } else { null }, - if (!isOwnMessage) { + if (visibility.isBlockUserVisible && !isOwnMessage) { MessageOptionItemState( title = R.string.stream_compose_block_user, iconPainter = painterResource(R.drawable.stream_compose_ic_clear), diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme.kt new file mode 100644 index 00000000000..dbb7e9c5775 --- /dev/null +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChannelOptionsTheme.kt @@ -0,0 +1,42 @@ +/* + * 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.ui.theme + +import androidx.compose.runtime.Immutable +import io.getstream.chat.android.compose.ui.components.channels.ChannelOptionItemVisibility + +/** + * Represents the theme for the channel option list in the selected channel menu. + * + * @param optionVisibility The visibility of the channel options. + * + * @see ChannelOptionItemVisibility + */ +@Immutable +public data class ChannelOptionsTheme( + public val optionVisibility: ChannelOptionItemVisibility, +) { + public companion object { + public fun defaultTheme( + optionVisibility: ChannelOptionItemVisibility = ChannelOptionItemVisibility(), + ): ChannelOptionsTheme { + return ChannelOptionsTheme( + optionVisibility = optionVisibility, + ) + } + } +} diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt index 71708483d12..764fb076fd6 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/theme/ChatTheme.kt @@ -156,6 +156,9 @@ private val LocalMessageAlignmentProvider = compositionLocalOf { error("No MessageOptionsTheme provided! Make sure to wrap all usages of Stream components in a ChatTheme.") } +private val LocalChannelOptionsTheme = compositionLocalOf { + error("No ChannelOptionsTheme provided! Make sure to wrap all usages of Stream components in a ChatTheme.") +} private val LocalMessageOptionsUserReactionAlignment = compositionLocalOf { error( "No LocalMessageOptionsUserReactionAlignment provided! Make sure to wrap all usages of Stream components " + @@ -313,6 +316,7 @@ public fun ChatTheme( imageAssetTransformer: ImageAssetTransformer = DefaultImageAssetTransformer, messageAlignmentProvider: MessageAlignmentProvider = MessageAlignmentProvider.defaultMessageAlignmentProvider(), messageOptionsTheme: MessageOptionsTheme = MessageOptionsTheme.defaultTheme(), + channelOptionsTheme: ChannelOptionsTheme = ChannelOptionsTheme.defaultTheme(), messageOptionsUserReactionAlignment: MessageOptionsUserReactionAlignment = MessageOptionsUserReactionAlignment.END, attachmentsPickerTabFactories: List = if (useDefaultSystemMediaPicker) { @@ -410,6 +414,7 @@ public fun ChatTheme( LocalStreamImageAssetTransformer provides imageAssetTransformer, LocalMessageAlignmentProvider provides messageAlignmentProvider, LocalMessageOptionsTheme provides messageOptionsTheme, + LocalChannelOptionsTheme provides channelOptionsTheme, LocalMessageOptionsUserReactionAlignment provides messageOptionsUserReactionAlignment, LocalAttachmentsPickerTabFactories provides attachmentsPickerTabFactories, LocalVideoThumbnailsEnabled provides videoThumbnailsEnabled, @@ -610,6 +615,14 @@ public object ChatTheme { @ReadOnlyComposable get() = LocalMessageOptionsTheme.current + /** + * Retrieves the current [ChannelOptionsTheme] at the call site's position in the hierarchy. + */ + public val channelOptionsTheme: ChannelOptionsTheme + @Composable + @ReadOnlyComposable + get() = LocalChannelOptionsTheme.current + /** * Retrieves the current [MessageOptionsUserReactionAlignment] at the call site's position in the hierarchy. */ diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt index a584f23c4fa..de19bb4ad8f 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/viewmodel/channels/ChannelListViewModel.kt @@ -597,6 +597,42 @@ public class ChannelListViewModel( chatClient.muteChannel(channel.type, channel.id).enqueue() } + /** + * Pins a channel. + * + * @param channel The channel to pin. + */ + public fun pinChannel(channel: Channel) { + dismissChannelAction() + chatClient.pinChannel(channel.type, channel.id).enqueue() + } + + /** + * Unpins a channel. + * + * @param channel The channel to unpin. + */ + public fun unpinChannel(channel: Channel) { + dismissChannelAction() + chatClient.unpinChannel(channel.type, channel.id).enqueue() + } + + public fun archiveChannel(channel: Channel) { + dismissChannelAction() + chatClient.archiveChannel( + channel.type, + channel.id, + ).enqueue() + } + + public fun unarchiveChannel(channel: Channel) { + dismissChannelAction() + chatClient.unarchiveChannel( + channel.type, + channel.id, + ).enqueue() + } + /** * Unmutes a channel. * diff --git a/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_archive.xml b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_archive.xml new file mode 100644 index 00000000000..a8fd5bedc8e --- /dev/null +++ b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_archive.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_pin_message.xml b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_pin.xml similarity index 100% rename from stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_pin_message.xml rename to stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_pin.xml diff --git a/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_unarchive.xml b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_unarchive.xml new file mode 100644 index 00000000000..0ee9e223acb --- /dev/null +++ b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_unarchive.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_unpin_message.xml b/stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_unpin.xml similarity index 100% rename from stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_unpin_message.xml rename to stream-chat-android-compose/src/main/res/drawable/stream_compose_ic_unpin.xml diff --git a/stream-chat-android-compose/src/main/res/values/strings.xml b/stream-chat-android-compose/src/main/res/values/strings.xml index 0549fc3e955..f6489cba931 100644 --- a/stream-chat-android-compose/src/main/res/values/strings.xml +++ b/stream-chat-android-compose/src/main/res/values/strings.xml @@ -30,6 +30,10 @@ View info Leave group + Pin Channel + Unpin Channel + Archive Channel + Unarchive Channel Leave group Do you want to leave the %1$s group? Mute channel diff --git a/stream-chat-android-core/api/stream-chat-android-core.api b/stream-chat-android-core/api/stream-chat-android-core.api index 0b8df8fe28e..3febdd3c57d 100644 --- a/stream-chat-android-core/api/stream-chat-android-core.api +++ b/stream-chat-android-core/api/stream-chat-android-core.api @@ -1040,13 +1040,15 @@ public final class io/getstream/chat/android/models/LinkPreview$Companion { } public final class io/getstream/chat/android/models/Member : io/getstream/chat/android/models/CustomObject, io/getstream/chat/android/models/UserEntity, io/getstream/chat/android/models/querysort/ComparableFieldProvider { - public fun (Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Map;)V - public synthetic fun (Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Map;)V + public synthetic fun (Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lio/getstream/chat/android/models/User; public final fun component10 ()Ljava/lang/Boolean; public final fun component11 ()Ljava/lang/String; public final fun component12 ()Ljava/util/Date; - public final fun component13 ()Ljava/util/Map; + public final fun component13 ()Ljava/util/Date; + public final fun component14 ()Ljava/util/Date; + public final fun component15 ()Ljava/util/Map; public final fun component2 ()Ljava/util/Date; public final fun component3 ()Ljava/util/Date; public final fun component4 ()Ljava/lang/Boolean; @@ -1055,9 +1057,10 @@ public final class io/getstream/chat/android/models/Member : io/getstream/chat/a public final fun component7 ()Z public final fun component8 ()Z public final fun component9 ()Ljava/lang/String; - public final fun copy (Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Map;)Lio/getstream/chat/android/models/Member; - public static synthetic fun copy$default (Lio/getstream/chat/android/models/Member;Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/chat/android/models/Member; + public final fun copy (Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Map;)Lio/getstream/chat/android/models/Member; + public static synthetic fun copy$default (Lio/getstream/chat/android/models/Member;Lio/getstream/chat/android/models/User;Ljava/util/Date;Ljava/util/Date;Ljava/lang/Boolean;Ljava/util/Date;Ljava/util/Date;ZZLjava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Date;Ljava/util/Date;Ljava/util/Date;Ljava/util/Map;ILjava/lang/Object;)Lio/getstream/chat/android/models/Member; public fun equals (Ljava/lang/Object;)Z + public final fun getArchivedAt ()Ljava/util/Date; public final fun getBanExpires ()Ljava/util/Date; public final fun getBanned ()Z public final fun getChannelRole ()Ljava/lang/String; @@ -1068,6 +1071,7 @@ public final class io/getstream/chat/android/models/Member : io/getstream/chat/a public final fun getInviteAcceptedAt ()Ljava/util/Date; public final fun getInviteRejectedAt ()Ljava/util/Date; public final fun getNotificationsMuted ()Ljava/lang/Boolean; + public final fun getPinnedAt ()Ljava/util/Date; public final fun getShadowBanned ()Z public final fun getStatus ()Ljava/lang/String; public final fun getUpdatedAt ()Ljava/util/Date; diff --git a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Channel.kt b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Channel.kt index c353975731d..9b57dda9f56 100644 --- a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Channel.kt +++ b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Channel.kt @@ -153,6 +153,8 @@ public data class Channel( "last_updated", "lastUpdated" -> lastUpdated "unread_count", "unreadCount" -> unreadCount "has_unread", "hasUnread" -> hasUnread + "pinned_at", "pinnedAt" -> membership?.pinnedAt + "archived_at", "archivedAt" -> membership?.archivedAt else -> extraData[fieldName] as? Comparable<*> } } diff --git a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Member.kt b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Member.kt index 334d537424e..e57c11fb2a4 100644 --- a/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Member.kt +++ b/stream-chat-android-core/src/main/java/io/getstream/chat/android/models/Member.kt @@ -81,6 +81,15 @@ public data class Member( * The date the ban expires. */ val banExpires: Date? = null, + /** + * The date when this channel was pinned. + */ + val pinnedAt: Date? = null, + + /** + * The date when this channel was archived. + */ + val archivedAt: Date? = null, /** * A map of custom fields for the member. */ @@ -101,6 +110,8 @@ public data class Member( "channel_role", "channelRole" -> channelRole "notifications_muted", "notificationsMuted" -> notificationsMuted "status" -> status + "pinned_at", "pinnedAt" -> pinnedAt + "archived_at", "archivedAt" -> archivedAt else -> null } } diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt index 16cc821f675..3d4836cef14 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/database/internal/ChatDatabase.kt @@ -81,7 +81,7 @@ import io.getstream.chat.android.offline.repository.domain.user.internal.UserEnt ThreadEntity::class, ThreadOrderEntity::class, ], - version = 80, + version = 81, exportSchema = false, ) @TypeConverters( diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberEntity.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberEntity.kt index e50f6c22c4d..4c5b054c9a0 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberEntity.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberEntity.kt @@ -60,6 +60,12 @@ internal data class MemberEntity( /** The date the ban expires. */ var banExpires: Date? = null, + /** The date when the member pinned the channel. */ + val pinnedAt: Date? = null, + + /** The date when the member archived the channel. */ + val archivedAt: Date? = null, + /** Map of custom fields for the member. */ val extraData: Map = emptyMap(), ) diff --git a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberMapper.kt b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberMapper.kt index 86512e8fca2..65b8a7bb97f 100644 --- a/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberMapper.kt +++ b/stream-chat-android-offline/src/main/java/io/getstream/chat/android/offline/repository/domain/channel/member/internal/MemberMapper.kt @@ -33,6 +33,8 @@ internal fun Member.toEntity(): MemberEntity = MemberEntity( status = status, banExpires = banExpires, extraData = extraData, + pinnedAt = pinnedAt, + archivedAt = archivedAt, ) internal suspend fun MemberEntity.toModel(getUser: suspend (userId: String) -> User): Member = Member( @@ -48,5 +50,7 @@ internal suspend fun MemberEntity.toModel(getUser: suspend (userId: String) -> U notificationsMuted = notificationsMuted, status = status, banExpires = banExpires, + pinnedAt = pinnedAt, + archivedAt = archivedAt, extraData = extraData, ) diff --git a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/chat/DefaultChatEventHandler.kt b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/chat/DefaultChatEventHandler.kt index 1bf8d5f4b44..0375e85cd9f 100644 --- a/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/chat/DefaultChatEventHandler.kt +++ b/stream-chat-android-state/src/main/java/io/getstream/chat/android/state/event/handler/chat/DefaultChatEventHandler.kt @@ -21,10 +21,13 @@ import io.getstream.chat.android.client.events.CidEvent import io.getstream.chat.android.client.events.HasChannel import io.getstream.chat.android.client.events.MemberAddedEvent import io.getstream.chat.android.client.events.MemberRemovedEvent +import io.getstream.chat.android.client.events.MemberUpdatedEvent import io.getstream.chat.android.client.events.NewMessageEvent import io.getstream.chat.android.client.events.NotificationAddedToChannelEvent import io.getstream.chat.android.client.events.NotificationMessageNewEvent import io.getstream.chat.android.client.events.NotificationRemovedFromChannelEvent +import io.getstream.chat.android.client.extensions.internal.updateMember +import io.getstream.chat.android.client.extensions.internal.updateMembership import io.getstream.chat.android.client.setup.state.ClientState import io.getstream.chat.android.models.Channel import io.getstream.chat.android.models.FilterObject @@ -61,6 +64,7 @@ public open class DefaultChatEventHandler( is NewMessageEvent -> handleNewMessageEvent(event, cachedChannel) is MemberRemovedEvent -> removeIfCurrentUserLeftChannel(event.cid, event.member) is MemberAddedEvent -> addIfCurrentUserJoinedChannel(cachedChannel, event.member) + is MemberUpdatedEvent -> addIfMembershipUpdated(cachedChannel, event.member) else -> super.handleCidEvent(event, filter, cachedChannel) } } @@ -102,6 +106,17 @@ public open class DefaultChatEventHandler( } } + private fun addIfMembershipUpdated(channel: Channel?, member: Member): EventHandlingResult { + return if (channel?.membership?.getUserId() == member.getUserId()) { + EventHandlingResult.Add( + channel.updateMembership(member) + .updateMember(member), + ) + } else { + EventHandlingResult.Skip + } + } + /** * Checks if the current user has left the channel and the channel is visible. * If yes then it removes it. Otherwise, it simply skips the event. diff --git a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api index d37dea6fb2e..113171b18ac 100644 --- a/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api +++ b/stream-chat-android-ui-common/api/stream-chat-android-ui-common.api @@ -565,6 +565,18 @@ public final class io/getstream/chat/android/ui/common/notifications/StreamCoilU public fun buildIcon (Lio/getstream/chat/android/models/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class io/getstream/chat/android/ui/common/state/channels/actions/ArchiveChannel : io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Channel;)V + public final fun component1 ()Lio/getstream/chat/android/models/Channel; + public final fun copy (Lio/getstream/chat/android/models/Channel;)Lio/getstream/chat/android/ui/common/state/channels/actions/ArchiveChannel; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/channels/actions/ArchiveChannel;Lio/getstream/chat/android/models/Channel;ILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/channels/actions/ArchiveChannel; + public fun equals (Ljava/lang/Object;)Z + public fun getChannel ()Lio/getstream/chat/android/models/Channel; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/ui/common/state/channels/actions/Cancel : io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction { public static final field $stable I public static final field INSTANCE Lio/getstream/chat/android/ui/common/state/channels/actions/Cancel; @@ -613,6 +625,30 @@ public final class io/getstream/chat/android/ui/common/state/channels/actions/Mu public fun toString ()Ljava/lang/String; } +public final class io/getstream/chat/android/ui/common/state/channels/actions/PinChannel : io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Channel;)V + public final fun component1 ()Lio/getstream/chat/android/models/Channel; + public final fun copy (Lio/getstream/chat/android/models/Channel;)Lio/getstream/chat/android/ui/common/state/channels/actions/PinChannel; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/channels/actions/PinChannel;Lio/getstream/chat/android/models/Channel;ILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/channels/actions/PinChannel; + public fun equals (Ljava/lang/Object;)Z + public fun getChannel ()Lio/getstream/chat/android/models/Channel; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/chat/android/ui/common/state/channels/actions/UnarchiveChannel : io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Channel;)V + public final fun component1 ()Lio/getstream/chat/android/models/Channel; + public final fun copy (Lio/getstream/chat/android/models/Channel;)Lio/getstream/chat/android/ui/common/state/channels/actions/UnarchiveChannel; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/channels/actions/UnarchiveChannel;Lio/getstream/chat/android/models/Channel;ILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/channels/actions/UnarchiveChannel; + public fun equals (Ljava/lang/Object;)Z + public fun getChannel ()Lio/getstream/chat/android/models/Channel; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/ui/common/state/channels/actions/UnmuteChannel : io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction { public static final field $stable I public fun (Lio/getstream/chat/android/models/Channel;)V @@ -625,6 +661,18 @@ public final class io/getstream/chat/android/ui/common/state/channels/actions/Un public fun toString ()Ljava/lang/String; } +public final class io/getstream/chat/android/ui/common/state/channels/actions/UnpinChannel : io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction { + public static final field $stable I + public fun (Lio/getstream/chat/android/models/Channel;)V + public final fun component1 ()Lio/getstream/chat/android/models/Channel; + public final fun copy (Lio/getstream/chat/android/models/Channel;)Lio/getstream/chat/android/ui/common/state/channels/actions/UnpinChannel; + public static synthetic fun copy$default (Lio/getstream/chat/android/ui/common/state/channels/actions/UnpinChannel;Lio/getstream/chat/android/models/Channel;ILjava/lang/Object;)Lio/getstream/chat/android/ui/common/state/channels/actions/UnpinChannel; + public fun equals (Ljava/lang/Object;)Z + public fun getChannel ()Lio/getstream/chat/android/models/Channel; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/chat/android/ui/common/state/channels/actions/ViewInfo : io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction { public static final field $stable I public fun (Lio/getstream/chat/android/models/Channel;)V diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/documents/AttachmentDocumentActivity.java b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/documents/AttachmentDocumentActivity.java index 568a0d8c9f1..97317842a5a 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/documents/AttachmentDocumentActivity.java +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/documents/AttachmentDocumentActivity.java @@ -47,6 +47,11 @@ public class AttachmentDocumentActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (!ChatClient.isInitialized()) { + finish(); + return; + } + setContentView(R.layout.stream_activity_attachment_document); rootView = findViewById(R.id.rootView); webView = findViewById(R.id.webView); diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt index c2c5325d84a..59a3bb8d904 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/state/channels/actions/ChannelAction.kt @@ -52,6 +52,26 @@ public data class UnmuteChannel(override val channel: Channel) : ChannelAction() */ public data class DeleteConversation(override val channel: Channel) : ChannelAction() +/** + * Shows a dialog to pin the channel. + */ +public data class PinChannel(override val channel: Channel) : ChannelAction() + +/** + * Shows a dialog to unpin the channel. + */ +public data class UnpinChannel(override val channel: Channel) : ChannelAction() + +/** + * Shows a dialog to archive the channel. + */ +public data class ArchiveChannel(override val channel: Channel) : ChannelAction() + +/** + * Shows a dialog to unarchive the channel. + */ +public data class UnarchiveChannel(override val channel: Channel) : ChannelAction() + /** * Dismisses the actions. */ diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/ChannelListActivity.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/ChannelListActivity.kt index db6d269d329..1bf45413d5c 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/ChannelListActivity.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/channels/ChannelListActivity.kt @@ -23,6 +23,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.databinding.StreamUiFragmentContainerBinding @@ -35,6 +36,11 @@ public open class ChannelListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + if (ChatClient.isInitialized.not()) { + finish() + return + } binding = StreamUiFragmentContainerBinding.inflate(layoutInflater) setContentView(binding.root) setupEdgeToEdge() diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentActivity.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentActivity.kt index b77ad9c0f54..f30dbcf6efb 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentActivity.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentActivity.kt @@ -28,6 +28,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updatePadding +import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.models.AttachmentType import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.databinding.StreamUiActivityAttachmentBinding @@ -46,6 +47,11 @@ public class AttachmentActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (ChatClient.isInitialized.not()) { + finish() + return + } + binding = StreamUiActivityAttachmentBinding.inflate(streamThemeInflater) setContentView(binding.root) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentGalleryActivity.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentGalleryActivity.kt index 7086febbc13..65b211d9f24 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentGalleryActivity.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentGalleryActivity.kt @@ -34,6 +34,7 @@ import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope import androidx.viewpager2.widget.ViewPager2 +import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.core.internal.coroutines.DispatcherProvider import io.getstream.chat.android.models.Attachment import io.getstream.chat.android.models.AttachmentType @@ -117,6 +118,11 @@ public class AttachmentGalleryActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + if (ChatClient.isInitialized.not()) { + finish() + return + } + binding = StreamUiActivityAttachmentGalleryBinding.inflate(streamThemeInflater) setContentView(binding.root) setupEdgeToEdge() diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt index 93e74e4d5e8..d4328658c62 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/gallery/AttachmentMediaActivity.kt @@ -31,6 +31,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updatePadding +import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.databinding.StreamUiActivityAttachmentMediaBinding import io.getstream.chat.android.ui.utils.extensions.getColorCompat @@ -52,6 +53,12 @@ public class AttachmentMediaActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + if (ChatClient.isInitialized.not()) { + finish() + return + } + binding = StreamUiActivityAttachmentMediaBinding.inflate(streamThemeInflater) setContentView(binding.root) diff --git a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/MessageListActivity.kt b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/MessageListActivity.kt index 66cf9b5394b..9811dda679b 100644 --- a/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/MessageListActivity.kt +++ b/stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/MessageListActivity.kt @@ -23,6 +23,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import io.getstream.chat.android.client.ChatClient import io.getstream.chat.android.ui.R import io.getstream.chat.android.ui.databinding.StreamUiFragmentContainerBinding @@ -35,6 +36,11 @@ public open class MessageListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + if (ChatClient.isInitialized.not()) { + finish() + return + } binding = StreamUiFragmentContainerBinding.inflate(layoutInflater) setContentView(binding.root) setupEdgeToEdge()