Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
### ⬆️ Improved

### ✅ Added
- Add `ChatClient.queryReactions(String, FilterObject?, Int?, String?, QuerySorter<Reaction>?)` operation for querying reactions with filtering, sorting, and pagination support. [#6040](https://github.com/GetStream/stream-chat-android/pull/6040)
- Add `ChatClient.markUnread(String, String, Date)` for marking a channel as unread from a given timestamp. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
- Add `ChatClient.markThreadUnread(String, String, String)` for marking a thread as unread. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
- Add `ChannelClient.markUnread(Date)` for marking a channel as unread from a given timestamp. [#6027](https://github.com/GetStream/stream-chat-android/pull/6027)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ public final class io/getstream/chat/android/client/ChatClient {
public static synthetic fun queryPollVotes$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
public final fun queryPolls (Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;)Lio/getstream/result/call/Call;
public static synthetic fun queryPolls$default (Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
public final fun queryReactions (Ljava/lang/String;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;)Lio/getstream/result/call/Call;
public static synthetic fun queryReactions$default (Lio/getstream/chat/android/client/ChatClient;Ljava/lang/String;Lio/getstream/chat/android/models/FilterObject;Ljava/lang/Integer;Ljava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
public final fun queryReminders (Lio/getstream/chat/android/models/FilterObject;ILjava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;)Lio/getstream/result/call/Call;
public static synthetic fun queryReminders$default (Lio/getstream/chat/android/client/ChatClient;Lio/getstream/chat/android/models/FilterObject;ILjava/lang/String;Lio/getstream/chat/android/models/querysort/QuerySorter;ILjava/lang/Object;)Lio/getstream/result/call/Call;
public final fun queryThreads (Lio/getstream/chat/android/client/api/models/QueryThreadsRequest;)Lio/getstream/result/call/Call;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ import io.getstream.chat.android.models.PushPreferenceLevel
import io.getstream.chat.android.models.QueryDraftsResult
import io.getstream.chat.android.models.QueryPollVotesResult
import io.getstream.chat.android.models.QueryPollsResult
import io.getstream.chat.android.models.QueryReactionsResult
import io.getstream.chat.android.models.QueryRemindersResult
import io.getstream.chat.android.models.QueryThreadsResult
import io.getstream.chat.android.models.Reaction
Expand Down Expand Up @@ -1144,6 +1145,26 @@ internal constructor(
return api.getReactions(messageId, offset, limit)
}

/**
* Queries reactions for a given message.
*
* @param messageId The ID of the message to which the reactions belong.
* @param filter The filter to apply to the reactions.
* @param limit The maximum number of reactions to retrieve.
* @param next The pagination token for fetching the next set of results.
* @param sort The sorting criteria for the reactions.
*/
@CheckResult
public fun queryReactions(
messageId: String,
filter: FilterObject? = null,
limit: Int? = null,
next: String? = null,
sort: QuerySorter<Reaction>? = null,
): Call<QueryReactionsResult> {
return api.queryReactions(messageId, filter, limit, next, sort)
}

/**
* Deletes the reaction associated with the message with the given message id.
* [cid] parameter is being used in side effect functions executed by plugins.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import io.getstream.chat.android.models.PushPreferenceLevel
import io.getstream.chat.android.models.QueryDraftsResult
import io.getstream.chat.android.models.QueryPollVotesResult
import io.getstream.chat.android.models.QueryPollsResult
import io.getstream.chat.android.models.QueryReactionsResult
import io.getstream.chat.android.models.QueryRemindersResult
import io.getstream.chat.android.models.QueryThreadsResult
import io.getstream.chat.android.models.Reaction
Expand Down Expand Up @@ -177,6 +178,15 @@ internal interface ChatApi {
limit: Int,
): Call<List<Reaction>>

@CheckResult
fun queryReactions(
messageId: String,
filter: FilterObject?,
limit: Int?,
next: String?,
sort: QuerySorter<Reaction>?,
): Call<QueryReactionsResult>

@CheckResult
fun sendReaction(reaction: Reaction, enforceUnique: Boolean, skipPush: Boolean): Call<Reaction>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import io.getstream.chat.android.client.api2.model.requests.QueryDraftMessagesRe
import io.getstream.chat.android.client.api2.model.requests.QueryDraftsRequest
import io.getstream.chat.android.client.api2.model.requests.QueryPollVotesRequest
import io.getstream.chat.android.client.api2.model.requests.QueryPollsRequest
import io.getstream.chat.android.client.api2.model.requests.QueryReactionsRequest
import io.getstream.chat.android.client.api2.model.requests.QueryRemindersRequest
import io.getstream.chat.android.client.api2.model.requests.ReactionRequest
import io.getstream.chat.android.client.api2.model.requests.RejectInviteRequest
Expand Down Expand Up @@ -141,6 +142,7 @@ import io.getstream.chat.android.models.PushPreferenceLevel
import io.getstream.chat.android.models.QueryDraftsResult
import io.getstream.chat.android.models.QueryPollVotesResult
import io.getstream.chat.android.models.QueryPollsResult
import io.getstream.chat.android.models.QueryReactionsResult
import io.getstream.chat.android.models.QueryRemindersResult
import io.getstream.chat.android.models.QueryThreadsResult
import io.getstream.chat.android.models.Reaction
Expand Down Expand Up @@ -405,6 +407,27 @@ constructor(
}
}

override fun queryReactions(
messageId: String,
filter: FilterObject?,
limit: Int?,
next: String?,
sort: QuerySorter<Reaction>?,
): Call<QueryReactionsResult> {
val body = QueryReactionsRequest(
filter = filter?.toMap(),
limit = limit,
next = next,
sort = sort?.toDto(),
)
return messageApi.queryReactions(messageId, body).mapDomain {
QueryReactionsResult(
reactions = it.reactions.map { reaction -> reaction.toDomain() },
next = it.next,
)
}
}

override fun sendReaction(reaction: Reaction, enforceUnique: Boolean, skipPush: Boolean): Call<Reaction> {
return messageApi.sendReaction(
messageId = reaction.messageId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.getstream.chat.android.client.api.AuthenticatedApi
import io.getstream.chat.android.client.api2.model.requests.PartialUpdateMessageRequest
import io.getstream.chat.android.client.api2.model.requests.QueryDraftMessagesRequest
import io.getstream.chat.android.client.api2.model.requests.QueryDraftsRequest
import io.getstream.chat.android.client.api2.model.requests.QueryReactionsRequest
import io.getstream.chat.android.client.api2.model.requests.ReactionRequest
import io.getstream.chat.android.client.api2.model.requests.SendActionRequest
import io.getstream.chat.android.client.api2.model.requests.SendMessageRequest
Expand All @@ -29,6 +30,7 @@ import io.getstream.chat.android.client.api2.model.response.DraftMessageResponse
import io.getstream.chat.android.client.api2.model.response.MessageResponse
import io.getstream.chat.android.client.api2.model.response.MessagesResponse
import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesResponse
import io.getstream.chat.android.client.api2.model.response.QueryReactionsResponse
import io.getstream.chat.android.client.api2.model.response.ReactionResponse
import io.getstream.chat.android.client.api2.model.response.ReactionsResponse
import io.getstream.chat.android.client.api2.model.response.TranslateMessageRequest
Expand Down Expand Up @@ -133,6 +135,12 @@ internal interface MessageApi {
@Query("limit") limit: Int,
): RetrofitCall<ReactionsResponse>

@POST("/messages/{id}/reactions")
fun queryReactions(
@Path("id") messageId: String,
@Body body: QueryReactionsRequest,
): RetrofitCall<QueryReactionsResponse>

@POST("/messages/{messageId}/translate")
fun translate(
@Path("messageId") messageId: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2014-2025 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.api2.model.requests

import com.squareup.moshi.JsonClass

/**
* Request for querying reactions on a message.
*
* @property filter The filter criteria.
* @property limit The maximum number of reactions to return.
* @property next The pagination token for fetching the next set of results.
* @property sort The sorting criteria to apply.
*/
@JsonClass(generateAdapter = true)
internal data class QueryReactionsRequest(
val filter: Map<*, *>? = null,
val limit: Int? = null,
val next: String? = null,
val sort: List<Map<String, Any>>? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2014-2025 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.api2.model.response

import com.squareup.moshi.JsonClass
import io.getstream.chat.android.client.api2.model.dto.DownstreamReactionDto

/**
* Response for querying reactions on a message.
*
* @property reactions The list of reactions returned by the query.
* @property next The identifier for the next page of reactions.
*/
@JsonClass(generateAdapter = true)
internal data class QueryReactionsResponse(
val reactions: List<DownstreamReactionDto>,
val next: String?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import io.getstream.chat.android.client.plugin.Plugin
import io.getstream.chat.android.client.utils.RetroError
import io.getstream.chat.android.client.utils.verifyNetworkError
import io.getstream.chat.android.client.utils.verifySuccess
import io.getstream.chat.android.models.Filters
import io.getstream.chat.android.models.Message
import io.getstream.chat.android.models.PendingMessage
import io.getstream.chat.android.models.QueryReactionsResult
import io.getstream.chat.android.models.Reaction
import io.getstream.chat.android.models.User
import io.getstream.chat.android.models.querysort.QuerySortByField
Expand Down Expand Up @@ -615,6 +617,43 @@ internal class ChatClientMessageApiTests : BaseChatClientTest() {
verifyNetworkError(result, errorCode)
}

@Test
fun queryReactionsSuccess() = runTest {
// given
val messageId = randomString()
val filter = Filters.neutral()
val limit = positiveRandomInt()
val next = randomString()
val sort = QuerySortByField<Reaction>()
val reactions = listOf(randomReaction(messageId = messageId))
val queryResult = QueryReactionsResult(reactions = reactions, next = randomString())
val sut = Fixture()
.givenQueryReactionsResult(queryResult.asCall())
.get()
// when
val result = sut.queryReactions(messageId, filter, limit, next, sort).await()
// then
verifySuccess(result, queryResult)
}

@Test
fun queryReactionsError() = runTest {
// given
val messageId = randomString()
val filter = Filters.neutral()
val limit = positiveRandomInt()
val next = randomString()
val sort = QuerySortByField<Reaction>()
val errorCode = positiveRandomInt()
val sut = Fixture()
.givenQueryReactionsResult(RetroError<QueryReactionsResult>(errorCode).toRetrofitCall())
.get()
// when
val result = sut.queryReactions(messageId, filter, limit, next, sort).await()
// then
verifyNetworkError(result, errorCode)
}

@Test
fun updateMessageSuccess() = runTest {
// given
Expand Down Expand Up @@ -892,6 +931,10 @@ internal class ChatClientMessageApiTests : BaseChatClientTest() {
whenever(api.getReactions(any(), any(), any())).thenReturn(result)
}

fun givenQueryReactionsResult(result: Call<QueryReactionsResult>) = apply {
whenever(api.queryReactions(any(), any(), any(), any(), any())).thenReturn(result)
}

fun givenUpdateMessageResult(result: Call<Message>) = apply {
whenever(api.updateMessage(any())).thenReturn(result)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import io.getstream.chat.android.client.api2.model.requests.PollVoteRequest
import io.getstream.chat.android.client.api2.model.requests.QueryBannedUsersRequest
import io.getstream.chat.android.client.api2.model.requests.QueryPollVotesRequest
import io.getstream.chat.android.client.api2.model.requests.QueryPollsRequest
import io.getstream.chat.android.client.api2.model.requests.QueryReactionsRequest
import io.getstream.chat.android.client.api2.model.requests.QueryRemindersRequest
import io.getstream.chat.android.client.api2.model.requests.RejectInviteRequest
import io.getstream.chat.android.client.api2.model.requests.ReminderRequest
Expand Down Expand Up @@ -102,6 +103,7 @@ import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesRe
import io.getstream.chat.android.client.api2.model.response.QueryMembersResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollVotesResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollsResponse
import io.getstream.chat.android.client.api2.model.response.QueryReactionsResponse
import io.getstream.chat.android.client.api2.model.response.QueryRemindersResponse
import io.getstream.chat.android.client.api2.model.response.QueryThreadsResponse
import io.getstream.chat.android.client.api2.model.response.ReactionResponse
Expand Down Expand Up @@ -137,6 +139,7 @@ import io.getstream.chat.android.models.NoOpMessageTransformer
import io.getstream.chat.android.models.NoOpUserTransformer
import io.getstream.chat.android.models.Poll
import io.getstream.chat.android.models.PushPreferenceLevel
import io.getstream.chat.android.models.Reaction
import io.getstream.chat.android.models.UnreadCounts
import io.getstream.chat.android.models.UploadedFile
import io.getstream.chat.android.models.Vote
Expand Down Expand Up @@ -413,6 +416,33 @@ internal class MoshiChatApiTest {
verify(api, times(1)).getReactions(messageId, offset, limit)
}

@ParameterizedTest
@MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#queryReactionsInput")
fun testQueryReactions(call: RetrofitCall<QueryReactionsResponse>, expected: KClass<*>) = runTest {
// given
val api = mock<MessageApi>()
whenever(api.queryReactions(any(), any())).doReturn(call)
val sut = Fixture()
.withMessageApi(api)
.get()
// when
val messageId = randomString()
val filter = Filters.neutral()
val limit = randomInt()
val next = randomString()
val sort = QuerySortByField<Reaction>()
val result = sut.queryReactions(messageId, filter, limit, next, sort).await()
// then
val expectedRequest = QueryReactionsRequest(
filter = filter.toMap(),
limit = limit,
next = next,
sort = sort.toDto(),
)
result `should be instance of` expected
verify(api, times(1)).queryReactions(messageId, expectedRequest)
}

@ParameterizedTest
@MethodSource("io.getstream.chat.android.client.api2.MoshiChatApiTestArguments#sendReactionInput")
fun testSendReaction(call: RetrofitCall<ReactionResponse>, expected: KClass<*>) = runTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import io.getstream.chat.android.client.api2.model.response.QueryDraftMessagesRe
import io.getstream.chat.android.client.api2.model.response.QueryMembersResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollVotesResponse
import io.getstream.chat.android.client.api2.model.response.QueryPollsResponse
import io.getstream.chat.android.client.api2.model.response.QueryReactionsResponse
import io.getstream.chat.android.client.api2.model.response.QueryThreadsResponse
import io.getstream.chat.android.client.api2.model.response.ReactionResponse
import io.getstream.chat.android.client.api2.model.response.ReactionsResponse
Expand Down Expand Up @@ -180,6 +181,20 @@ internal object MoshiChatApiTestArguments {
Arguments.of(RetroError<ReactionsResponse>(statusCode = 500).toRetrofitCall(), Result.Failure::class),
)

@JvmStatic
fun queryReactionsInput() = listOf(
Arguments.of(
RetroSuccess(
QueryReactionsResponse(
reactions = listOf(Mother.randomDownstreamReactionDto()),
next = randomString(),
),
).toRetrofitCall(),
Result.Success::class,
),
Arguments.of(RetroError<QueryReactionsResponse>(statusCode = 500).toRetrofitCall(), Result.Failure::class),
)

@JvmStatic
fun sendReactionInput() = listOf(
Arguments.of(
Expand Down
Loading
Loading