Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
227ec07
chore(ci): update stream-build-conventions to v0.9.0 (#6151)
aleksandar-apostolov Feb 13, 2026
396c9e9
Fix local only message deletion (#6157)
VelikovPetar Feb 17, 2026
3e3e389
AUTOMATION: Version Bump
github-actions[bot] Feb 17, 2026
714ea4f
Fix auto scroll to bottom when exiting a thread (#6165)
VelikovPetar Feb 18, 2026
04a2a58
Fix CaptureMediaContract not remembered across recompositions (#6169)
VelikovPetar Feb 19, 2026
38f908a
Explicitly set repository name in the conventions plugin (#6170)
gpunto Feb 20, 2026
d7a52d6
Fix delivery receipts not being sent in privacy settings updates (#6178)
VelikovPetar Feb 23, 2026
7abef21
[skip ci] Update SDK sizes
github-actions[bot] Feb 23, 2026
0a76dd6
Fix `rememberCaptureMediaLauncher` not surviving `Activity` death (#6…
VelikovPetar Feb 23, 2026
ab79e25
[skip ci] Update SDK sizes
github-actions[bot] Feb 23, 2026
982316b
Fix delete message not working when offline (SYNC_NEEDED) (#6184)
VelikovPetar Feb 24, 2026
3b6345c
Restrict StreamFileProvider paths to only used directories (#6183)
VelikovPetar Feb 24, 2026
b16c85c
AUTOMATION: Version Bump
github-actions[bot] Feb 24, 2026
4c55830
Add`headerContent` and `footerContent` slots to `Messages` composable…
tafelnl Feb 25, 2026
9a4e338
[skip ci] Update SDK sizes
github-actions[bot] Feb 25, 2026
567d02e
Fix incorrect validation for searchMessages parameters (#6180)
VelikovPetar Feb 25, 2026
3a3ce49
[skip ci] Update SDK sizes
github-actions[bot] Feb 25, 2026
e8e97bf
Use cursor-based pagination for search messages (#6179)
VelikovPetar Feb 27, 2026
db0d4c8
[skip ci] Update SDK sizes
github-actions[bot] Feb 27, 2026
6807a8e
Add `messageSearchSort` option to `ChannelListViewModel` and `Channel…
VelikovPetar Feb 27, 2026
7b63787
AUTOMATION: Version Bump
github-actions[bot] Feb 27, 2026
29b2d88
AUTOMATION: Clean Detekt Baseline Files (#6198)
stream-pr-merger[bot] Mar 2, 2026
b18ddb8
Introduce thread safe `AsyncImageHeadersProvider` for custom image he…
VelikovPetar Mar 5, 2026
210b487
[skip ci] Update SDK sizes
github-actions[bot] Mar 5, 2026
3b5cd86
AUTOMATION: Version Bump
github-actions[bot] Mar 6, 2026
56a1073
Disable swipe-to-reply for deleted messages (#6226)
VelikovPetar Mar 9, 2026
167b04d
Ensure SyncManager respects messageLimit and memberLimit when restori…
VelikovPetar Mar 10, 2026
73f14e6
Expose ChannelsScreen isBackPressEnabled config flag. (#6228)
VelikovPetar Mar 10, 2026
02e4407
Fix sending wrong type for incomplete command messages (#6236)
VelikovPetar Mar 11, 2026
b7c7cc9
[skip ci] Update SDK sizes
github-actions[bot] Mar 11, 2026
d7ce8ed
AUTOMATION: Version Bump
github-actions[bot] Mar 13, 2026
540854d
Merge branch 'develop' into merge/develop_to_v7_20260317
VelikovPetar Mar 17, 2026
946eabe
Post merge clean-up.
VelikovPetar Mar 17, 2026
3801d7f
Merge branch 'v7' into merge/develop_to_v7_20260317
VelikovPetar Mar 17, 2026
bc31abb
Merge branch 'v7' into merge/develop_to_v7_20260317
VelikovPetar Mar 17, 2026
231e9a1
Merge branch 'v7' into merge/develop_to_v7_20260317
VelikovPetar Mar 17, 2026
c3ebea1
Merge branch 'v7' into merge/develop_to_v7_20260317
VelikovPetar Mar 18, 2026
59b3932
ApiDUmp.
VelikovPetar Mar 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ concurrency:

jobs:
base-android-ci:
uses: GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml@v0.7.1
uses: GetStream/stream-build-conventions-android/.github/workflows/android-ci.yml@v0.9.0
secrets: inherit

detekt:
name: Detekt
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4.2.2
- uses: GetStream/stream-build-conventions-android/.github/actions/setup-gradle@v0.7.1
- uses: GetStream/stream-build-conventions-android/.github/actions/setup-gradle@v0.9.0
- name: Detekt
run: ./gradlew detekt

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-cleanup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ permissions:

jobs:
pr-clean-stale:
uses: GetStream/stream-build-conventions-android/.github/workflows/pr-clean-stale.yaml@v0.6.1
uses: GetStream/stream-build-conventions-android/.github/workflows/pr-clean-stale.yaml@v0.9.0
secrets: inherit
2 changes: 1 addition & 1 deletion .github/workflows/pr-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ concurrency:

jobs:
pr-checklist:
uses: GetStream/stream-build-conventions-android/.github/workflows/pr-quality.yml@v0.7.1
uses: GetStream/stream-build-conventions-android/.github/workflows/pr-quality.yml@v0.9.0
secrets: inherit
2 changes: 1 addition & 1 deletion .github/workflows/publish-new-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
permissions:
contents: write
needs: pre_release_check
uses: GetStream/stream-build-conventions-android/.github/workflows/release.yml@v0.8.0
uses: GetStream/stream-build-conventions-android/.github/workflows/release.yml@v0.9.0
with:
bump: ${{ inputs.bump }}
secrets:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sdk-size-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ concurrency:

jobs:
compare-sdk-sizes:
uses: GetStream/stream-build-conventions-android/.github/workflows/sdk-size-checks.yml@v0.6.1
uses: GetStream/stream-build-conventions-android/.github/workflows/sdk-size-checks.yml@v0.9.0
with:
modules: "stream-chat-android-client stream-chat-android-ui-components stream-chat-android-compose"
metrics-project: "stream-chat-android-metrics"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/sdk-size-updates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ concurrency:

jobs:
update-sdk-sizes:
uses: GetStream/stream-build-conventions-android/.github/workflows/sdk-size-updates.yml@v0.6.1
uses: GetStream/stream-build-conventions-android/.github/workflows/sdk-size-updates.yml@v0.9.0
with:
modules: "stream-chat-android-client stream-chat-android-ui-components stream-chat-android-compose"
metrics-project: "stream-chat-android-metrics"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

![stream-chat-android-client](https://img.shields.io/badge/stream--chat--android--client-5.25%20MB-lightgreen)
![stream-chat-android-ui-components](https://img.shields.io/badge/stream--chat--android--ui--components-10.62%20MB-lightgreen)
![stream-chat-android-compose](https://img.shields.io/badge/stream--chat--android--compose-12.84%20MB-lightgreen)
![stream-chat-android-compose](https://img.shields.io/badge/stream--chat--android--compose-12.85%20MB-lightgreen)

</div>

Expand Down
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ plugins {
}

streamProject {
repositoryName = "stream-chat-android"

spotless {
ignoredModules = setOf("stream-chat-android-docs")
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ android.enableR8.fullMode=true
android.suppressUnsupportedCompileSdk=34

# Project version
version=6.32.2
version=6.35.0
Original file line number Diff line number Diff line change
Expand Up @@ -1766,8 +1766,11 @@ internal constructor(
next: String? = null,
sort: QuerySorter<Message>? = null,
): Call<SearchMessagesResult> {
if (offset != null && (sort != null || next != null)) {
return ErrorCall(userScope, Error.GenericError("Cannot specify offset with sort or next parameters"))
if (offset != null && next != null) {
return ErrorCall(
userScope,
Error.GenericError("Cannot use both offset and next values. Specify only one of these options."),
)
}
return api.searchMessages(
channelFilter = channelFilter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package io.getstream.chat.android.client.api2.mapping

import io.getstream.chat.android.DeliveryReceipts
import io.getstream.chat.android.PrivacySettings
import io.getstream.chat.android.ReadReceipts
import io.getstream.chat.android.TypingIndicators
import io.getstream.chat.android.client.api2.model.dto.AttachmentDto
import io.getstream.chat.android.client.api2.model.dto.DeliveryReceiptsDto
import io.getstream.chat.android.client.api2.model.dto.DeviceDto
import io.getstream.chat.android.client.api2.model.dto.PrivacySettingsDto
import io.getstream.chat.android.client.api2.model.dto.ReadReceiptsDto
Expand Down Expand Up @@ -207,6 +209,7 @@ internal class DtoMapping(
internal fun PrivacySettings.toDto(): PrivacySettingsDto = PrivacySettingsDto(
typing_indicators = typingIndicators?.toDto(),
read_receipts = readReceipts?.toDto(),
delivery_receipts = deliveryReceipts?.toDto(),
)

/**
Expand All @@ -223,6 +226,13 @@ internal class DtoMapping(
enabled = enabled,
)

/**
* Maps the domain [DeliveryReceipts] model to a network [DeliveryReceiptsDto] model.
*/
internal fun DeliveryReceipts.toDto(): DeliveryReceiptsDto = DeliveryReceiptsDto(
enabled = enabled,
)

/**
* Maps the domain [User] model to a network [UpstreamUserDto] model.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@

package io.getstream.chat.android.client.internal.offline.plugin.listener.internal

import io.getstream.chat.android.client.errors.cause.MessageModerationDeletedException
import io.getstream.chat.android.client.extensions.internal.users
import io.getstream.chat.android.client.persistance.repository.MessageRepository
import io.getstream.chat.android.client.persistance.repository.UserRepository
import io.getstream.chat.android.client.plugin.listeners.DeleteMessageListener
import io.getstream.chat.android.client.setup.state.ClientState
import io.getstream.chat.android.client.utils.message.isModerationError
import io.getstream.chat.android.client.utils.message.shouldDeleteRemote
import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.SyncStatus
import io.getstream.result.Error
import io.getstream.result.Result
import java.util.Date

Expand All @@ -45,24 +43,19 @@ internal class DeleteMessageListenerDatabase(
* @param messageId The message id to be deleted.
*/
override suspend fun onMessageDeletePrecondition(messageId: String): Result<Unit> {
return messageRepository.selectMessage(messageId)?.let { message ->
val currentUserId = clientState.user.value?.id
val isModerationFailed = message.isModerationError(currentUserId)

if (isModerationFailed) {
messageRepository.deleteChannelMessage(message)
Result.Failure(
Error.ThrowableError(
message = "Message with failed moderation has been deleted locally: $messageId",
cause = MessageModerationDeletedException(
"Message with failed moderation has been deleted locally: $messageId",
),
),
)
} else {
Result.Success(Unit)
}
} ?: Result.Success(Unit)
val localMessage = messageRepository.selectMessage(messageId)
val currentUserId = clientState.user.value?.id
// We don't have the message locally, we must attempt to delete the message remotely
if (localMessage == null) {
return Result.Success(Unit)
}
// Check if the message is local-only (if attempting remote delete should be skipped
val shouldDeleteRemote = localMessage.shouldDeleteRemote(currentUserId)
if (shouldDeleteRemote is Result.Failure) {
// Delete the message ONLY locally
messageRepository.deleteChannelMessage(localMessage)
}
return shouldDeleteRemote
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,13 @@
package io.getstream.chat.android.client.internal.state.plugin.listener.internal

import io.getstream.chat.android.client.api.state.GlobalState
import io.getstream.chat.android.client.errors.cause.MessageModerationDeletedException
import io.getstream.chat.android.client.internal.state.plugin.logic.channel.internal.ChannelLogic
import io.getstream.chat.android.client.internal.state.plugin.logic.internal.LogicRegistry
import io.getstream.chat.android.client.plugin.listeners.DeleteMessageListener
import io.getstream.chat.android.client.setup.state.ClientState
import io.getstream.chat.android.client.utils.message.isModerationError
import io.getstream.chat.android.client.utils.message.shouldDeleteRemote
import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.SyncStatus
import io.getstream.result.Error
import io.getstream.result.Result
import java.util.Date

Expand All @@ -45,25 +43,19 @@ internal class DeleteMessageListenerState(
* @param messageId The message id to be deleted.
*/
override suspend fun onMessageDeletePrecondition(messageId: String): Result<Unit> {
val channelLogic: ChannelLogic? = logic.channelFromMessageId(messageId)

return channelLogic?.getMessage(messageId)?.let { message ->
val isModerationFailed = message.isModerationError(clientState.user.value?.id)

if (isModerationFailed) {
deleteMessage(message)
Result.Failure(
Error.ThrowableError(
message = "Message with failed moderation has been deleted locally: $messageId",
cause = MessageModerationDeletedException(
"Message with failed moderation has been deleted locally: $messageId",
),
),
)
} else {
Result.Success(Unit)
}
} ?: Result.Success(Unit)
val localMessage = logic.channelFromMessageId(messageId)?.getMessage(messageId)
val currentUserId = clientState.user.value?.id
// We don't have the message locally, we must attempt to delete the message remotely
if (localMessage == null) {
return Result.Success(Unit)
}
// Check if the message is local-only (if attempting remote delete should be skipped)
val shouldDeleteRemote = localMessage.shouldDeleteRemote(currentUserId)
if (shouldDeleteRemote is Result.Failure) {
// Delete the message ONLY locally
deleteMessage(localMessage)
}
return shouldDeleteRemote
}

/**
Expand All @@ -75,19 +67,13 @@ internal class DeleteMessageListenerState(
val channelLogic: ChannelLogic? = logic.channelFromMessageId(messageId)

channelLogic?.getMessage(messageId)?.let { message ->
val isModerationFailed = message.isModerationError(clientState.user.value?.id)
val networkAvailable = clientState.isNetworkAvailable
val messageToBeDeleted = message.copy(
deletedAt = Date(),
syncStatus = if (!networkAvailable) SyncStatus.SYNC_NEEDED else SyncStatus.IN_PROGRESS,
)

if (isModerationFailed) {
deleteMessage(message)
} else {
val networkAvailable = clientState.isNetworkAvailable
val messageToBeDeleted = message.copy(
deletedAt = Date(),
syncStatus = if (!networkAvailable) SyncStatus.SYNC_NEEDED else SyncStatus.IN_PROGRESS,
)

updateMessage(messageToBeDeleted)
}
updateMessage(messageToBeDeleted)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ import io.getstream.log.taggedLogger
import io.getstream.result.Result
import kotlinx.coroutines.flow.StateFlow

private const val MESSAGE_LIMIT = 1
private const val MEMBER_LIMIT = 30
private const val INITIAL_CHANNEL_OFFSET = 0
private const val CHANNEL_LIMIT = 30

Expand Down Expand Up @@ -155,13 +153,16 @@ internal class QueryChannelsLogic(
*/
internal suspend fun queryFirstPage(): Result<List<Channel>> {
logger.d { "[queryFirstPage] no args" }
val currentRequest = queryChannelsStateLogic.getState().currentRequest.value
val messageLimit = currentRequest?.messageLimit
val memberLimit = currentRequest?.memberLimit
val request = QueryChannelsRequest(
filter = filter,
offset = INITIAL_CHANNEL_OFFSET,
limit = CHANNEL_LIMIT,
querySort = sort,
messageLimit = MESSAGE_LIMIT,
memberLimit = MEMBER_LIMIT,
messageLimit = messageLimit,
memberLimit = memberLimit,
)

queryChannelsStateLogic.setCurrentRequest(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,14 @@ internal class SocketFactory(

private fun PrivacySettings.reducePrivacySettings(): Map<String, Any> = mutableMapOf<String, Any>()
.apply {
typingIndicators?.also {
put(
"typing_indicators",
mapOf<String, Any>(
"enabled" to it.enabled,
),
)
typingIndicators?.let {
put("typing_indicators", mapOf<String, Any>("enabled" to it.enabled))
}
readReceipts?.also {
put(
"read_receipts",
mapOf<String, Any>(
"enabled" to it.enabled,
),
)
deliveryReceipts?.let {
put("delivery_receipts", mapOf<String, Any>("enabled" to it.enabled))
}
readReceipts?.let {
put("read_receipts", mapOf<String, Any>("enabled" to it.enabled))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,14 @@ import io.getstream.chat.android.client.extensions.internal.hasPendingAttachment
import io.getstream.chat.android.core.internal.InternalStreamChatApi
import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.MessageType
import java.util.regex.Pattern

private val COMMAND_PATTERN = Pattern.compile("^/[a-z]*$")

/**
* Updates the type of the [Message] based on its content.
*
* If the message contains a command or has attachments to upload, the type will be [MessageType.EPHEMERAL].
* If the message has attachments to upload, the type will be [MessageType.EPHEMERAL] (local-only - after the
* attachments are uploaded, the type is changed to [MessageType.REGULAR]).
* If the message is a system message, the type will be [MessageType.SYSTEM].
* Otherwise, the type will be [MessageType.REGULAR], as we cannot send messages which are not regular, ephemeral, or
* system.
* Otherwise, the type will be [MessageType.REGULAR], as we cannot send messages which are not regular or system.
*
* @param message The message to update.
*/
Expand All @@ -39,7 +36,7 @@ public fun getMessageType(message: Message): String {
val hasAttachments = message.attachments.isNotEmpty()
val hasAttachmentsToUpload = message.hasPendingAttachments()

return if (COMMAND_PATTERN.matcher(message.text).find() || (hasAttachments && hasAttachmentsToUpload)) {
return if (hasAttachments && hasAttachmentsToUpload) {
MessageType.EPHEMERAL
} else if (message.type == MessageType.SYSTEM) {
MessageType.SYSTEM
Expand Down
Loading
Loading