From dd9c4de9d1704765f8b61e6751ad852418d61a3e Mon Sep 17 00:00:00 2001 From: VelikovPetar Date: Tue, 7 Apr 2026 12:36:04 +0200 Subject: [PATCH 1/8] Implement new JSON parsers. --- .../client/parser2/event/AnswerAdapter.kt | 85 ++++ .../client/parser2/event/AttachmentAdapter.kt | 103 +++++ .../client/parser2/event/ChannelAdapter.kt | 316 +++++++++++++ .../parser2/event/ChannelInfoAdapter.kt | 62 +++ .../parser2/event/ChannelUserReadAdapter.kt | 80 ++++ .../client/parser2/event/CommandAdapter.kt | 70 +++ .../client/parser2/event/ConfigAdapter.kt | 199 ++++++++ .../client/parser2/event/DeviceAdapter.kt | 62 +++ .../client/parser2/event/LocationAdapter.kt | 88 ++++ .../client/parser2/event/MemberAdapter.kt | 108 +++++ .../client/parser2/event/MessageAdapter.kt | 411 +++++++++++++++++ .../event/MessageModerationDetailsAdapter.kt | 54 +++ .../event/MessageReminderInfoAdapter.kt | 64 +++ .../client/parser2/event/ModerationAdapter.kt | 88 ++++ .../parser2/event/NewMessageEventAdapter.kt | 183 ++++++++ .../client/parser2/event/OptionAdapter.kt | 71 +++ .../client/parser2/event/PollAdapter.kt | 360 +++++++++++++++ .../parser2/event/PrivacySettingsAdapter.kt | 108 +++++ .../parser2/event/PushPreferenceAdapter.kt | 54 +++ .../client/parser2/event/ReactionAdapter.kt | 108 +++++ .../parser2/event/ReactionGroupAdapter.kt | 168 +++++++ .../client/parser2/event/UserAdapter.kt | 197 ++++++++ .../client/parser2/event/VoteAdapter.kt | 88 ++++ .../client/parser2/AnswerParsingTest.kt | 141 ++++++ .../client/parser2/AttachmentParsingTest.kt | 92 ++++ .../client/parser2/ChannelInfoParsingTest.kt | 75 +++ .../client/parser2/ChannelParsingTest.kt | 242 ++++++++++ .../parser2/ChannelUserReadParsingTest.kt | 134 ++++++ .../client/parser2/CommandParsingTest.kt | 112 +++++ .../client/parser2/ConfigParsingTest.kt | 133 ++++++ .../client/parser2/DeviceParsingTest.kt | 105 +++++ .../client/parser2/LocationParsingTest.kt | 152 +++++++ .../client/parser2/MemberParsingTest.kt | 111 +++++ .../MessageModerationDetailsParsingTest.kt | 75 +++ .../client/parser2/MessageParsingTest.kt | 311 +++++++++++++ .../parser2/MessageReminderInfoParsingTest.kt | 108 +++++ .../client/parser2/ModerationParsingTest.kt | 103 +++++ .../parser2/NewMessageEventParsingTest.kt | 231 ++++++++++ .../client/parser2/OptionParsingTest.kt | 103 +++++ .../android/client/parser2/PollParsingTest.kt | 243 ++++++++++ .../parser2/PrivacySettingsParsingTest.kt | 114 +++++ .../parser2/PushPreferenceParsingTest.kt | 81 ++++ .../parser2/ReactionGroupParsingTest.kt | 156 +++++++ .../client/parser2/ReactionParsingTest.kt | 144 ++++++ .../android/client/parser2/UserParsingTest.kt | 140 ++++++ .../android/client/parser2/VoteParsingTest.kt | 152 +++++++ .../client/parser2/testdata/AnswerTestData.kt | 73 +++ .../parser2/testdata/AttachmentTestData.kt | 85 ++++ .../parser2/testdata/ChannelInfoTestData.kt | 49 ++ .../parser2/testdata/ChannelTestData.kt | 427 ++++++++++++++++++ .../testdata/ChannelUserReadTestData.kt | 83 ++++ .../parser2/testdata/CommandTestData.kt | 50 ++ .../client/parser2/testdata/ConfigTestData.kt | 130 ++++++ .../client/parser2/testdata/DeviceTestData.kt | 52 +++ .../parser2/testdata/LocationTestData.kt | 76 ++++ .../client/parser2/testdata/MemberTestData.kt | 91 ++++ .../MessageModerationDetailsTestData.kt | 44 ++ .../testdata/MessageReminderInfoTestData.kt | 52 +++ .../parser2/testdata/MessageTestData.kt | 394 ++++++++++++++++ .../parser2/testdata/ModerationTestData.kt | 60 +++ .../testdata/NewMessageEventTestData.kt | 228 ++++++++++ .../client/parser2/testdata/OptionTestData.kt | 51 +++ .../client/parser2/testdata/PollTestData.kt | 238 ++++++++++ .../testdata/PrivacySettingsTestData.kt | 58 +++ .../testdata/PushPreferenceTestData.kt | 43 ++ .../parser2/testdata/ReactionGroupTestData.kt | 52 +++ .../parser2/testdata/ReactionTestData.kt | 98 ++++ .../client/parser2/testdata/UserTestData.kt | 175 +++++++ .../client/parser2/testdata/VoteTestData.kt | 77 ++++ 69 files changed, 9071 insertions(+) create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AnswerAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AttachmentAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelInfoAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelUserReadAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/CommandAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ConfigAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/DeviceAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/LocationAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MemberAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageModerationDetailsAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageReminderInfoAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ModerationAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/NewMessageEventAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/OptionAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/PollAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/PrivacySettingsAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/PushPreferenceAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ReactionAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ReactionGroupAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/UserAdapter.kt create mode 100644 stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/VoteAdapter.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/AnswerParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/AttachmentParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ChannelInfoParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ChannelParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ChannelUserReadParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/CommandParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ConfigParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/DeviceParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/LocationParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/MemberParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/MessageModerationDetailsParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/MessageParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/MessageReminderInfoParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ModerationParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/NewMessageEventParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/OptionParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/PollParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/PrivacySettingsParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/PushPreferenceParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ReactionGroupParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/ReactionParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/UserParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/VoteParsingTest.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/AnswerTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/AttachmentTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ChannelInfoTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ChannelTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ChannelUserReadTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/CommandTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ConfigTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/DeviceTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/LocationTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MemberTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MessageModerationDetailsTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MessageReminderInfoTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/MessageTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ModerationTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/NewMessageEventTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/OptionTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/PollTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/PrivacySettingsTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/PushPreferenceTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ReactionGroupTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/ReactionTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/UserTestData.kt create mode 100644 stream-chat-android-client/src/test/java/io/getstream/chat/android/client/parser2/testdata/VoteTestData.kt diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AnswerAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AnswerAdapter.kt new file mode 100644 index 00000000000..2fb8382e79d --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AnswerAdapter.kt @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Answer +import io.getstream.chat.android.models.User +import java.util.Date + +internal class AnswerAdapter( + private val userAdapter: JsonAdapter, + private val dateAdapter: JsonAdapter, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): Answer? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + + var id: String? = null + var pollId: String? = null + var text: String? = null + var createdAt: Date? = null + var updatedAt: Date? = null + var user: User? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "id" -> id = reader.nextString() + "poll_id" -> pollId = reader.nextString() + "answer_text" -> text = reader.nextString() + "created_at" -> createdAt = dateAdapter.fromJson(reader) + "updated_at" -> updatedAt = dateAdapter.fromJson(reader) + "user" -> user = userAdapter.fromJson(reader) + "user_id" -> reader.skipValue() + "is_answer" -> reader.skipValue() + "option_id" -> reader.skipValue() + else -> reader.skipValue() + } + } + reader.endObject() + + if (id == null) { + throw JsonDataException("Required value 'id' missing at ${reader.path}") + } + if (pollId == null) { + throw JsonDataException("Required value 'poll_id' missing at ${reader.path}") + } + if (createdAt == null) { + throw JsonDataException("Required value 'created_at' missing at ${reader.path}") + } + if (updatedAt == null) { + throw JsonDataException("Required value 'updated_at' missing at ${reader.path}") + } + + return Answer( + id = id, + pollId = pollId, + text = text ?: "", + createdAt = createdAt, + updatedAt = updatedAt, + user = user, + ) + } + + override fun toJson(p0: JsonWriter, p1: Answer?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AttachmentAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AttachmentAdapter.kt new file mode 100644 index 00000000000..ffeb129b3c2 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/AttachmentAdapter.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Attachment + +internal class AttachmentAdapter : JsonAdapter() { + + @Suppress("LongMethod") + override fun fromJson(reader: JsonReader): Attachment? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var assetUrl: String? = null + var authorName: String? = null + var authorLink: String? = null + var fallback: String? = null + var fileSize: Int = 0 + var image: String? = null + var imageUrl: String? = null + var mimeType: String? = null + var name: String? = null + var ogScrapeUrl: String? = null + var text: String? = null + var thumbUrl: String? = null + var title: String? = null + var titleLink: String? = null + var type: String? = null + var originalHeight: Int? = null + var originalWidth: Int? = null + var extraData: MutableMap? = null + + while (reader.hasNext()) { + val key = reader.nextName() + when (key) { + "asset_url" -> assetUrl = reader.nextString() + "author_name" -> authorName = reader.nextString() + "author_link" -> authorLink = reader.nextString() + "fallback" -> fallback = reader.nextString() + "file_size" -> fileSize = reader.nextInt() + "image" -> image = reader.nextString() + "image_url" -> imageUrl = reader.nextString() + "mime_type" -> mimeType = reader.nextString() + "name" -> name = reader.nextString() + "og_scrape_url" -> ogScrapeUrl = reader.nextString() + "text" -> text = reader.nextString() + "thumb_url" -> thumbUrl = reader.nextString() + "title" -> title = reader.nextString() + "title_link" -> titleLink = reader.nextString() + "type" -> type = reader.nextString() + "original_height" -> originalHeight = reader.nextInt() + "original_width" -> originalWidth = reader.nextInt() + else -> reader.readJsonValue()?.let { value -> + val map = extraData ?: mutableMapOf().also { extraData = it } + map[key] = value + } + } + } + reader.endObject() + + return Attachment( + assetUrl = assetUrl, + authorName = authorName, + authorLink = authorLink, + fallback = fallback, + fileSize = fileSize, + image = image, + imageUrl = imageUrl, + mimeType = mimeType, + name = name, + ogUrl = ogScrapeUrl, + text = text, + thumbUrl = thumbUrl, + title = title, + titleLink = titleLink, + type = type, + originalHeight = originalHeight, + originalWidth = originalWidth, + extraData = extraData?.toMutableMap() ?: mutableMapOf(), + ) + } + + override fun toJson(p0: JsonWriter, p1: Attachment?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelAdapter.kt new file mode 100644 index 00000000000..05e63c50050 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelAdapter.kt @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.client.extensions.syncUnreadCountWithReads +import io.getstream.chat.android.models.Channel +import io.getstream.chat.android.models.ChannelInfo +import io.getstream.chat.android.models.ChannelTransformer +import io.getstream.chat.android.models.ChannelUserRead +import io.getstream.chat.android.models.Config +import io.getstream.chat.android.models.Location +import io.getstream.chat.android.models.Member +import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.User +import io.getstream.chat.android.models.UserId +import java.util.Date + +@Suppress("LongParameterList") +internal class ChannelAdapter( + private val messageAdapter: JsonAdapter, + private val memberAdapter: JsonAdapter, + private val userAdapter: JsonAdapter, + private val configAdapter: JsonAdapter, + private val locationAdapter: JsonAdapter, + private val dateAdapter: JsonAdapter, + private val currentUserIdProvider: () -> UserId?, + private val channelTransformer: ChannelTransformer, +) : JsonAdapter() { + + @Suppress("LongMethod") + override fun fromJson(reader: JsonReader): Channel? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var cid: String? = null + var id: String? = null + var type: String? = null + var name: String? = null + var image: String? = null + var watcherCount: Int? = null + var filterTags: List? = null + var frozen: Boolean? = null + var lastMessageAt: Date? = null + var createdAt: Date? = null + var deletedAt: Date? = null + var updatedAt: Date? = null + var memberCount: Int? = null + var messages: List? = null + var members: List? = null + var watchers: List? = null + var read: List? = null + var config: Config? = null + var createdBy: User? = null + var team: String? = null + var cooldown: Int? = null + var pinnedMessages: List? = null + var ownCapabilities: List? = null + var membership: Member? = null + var activeLiveLocations: List? = null + var messageCount: Int? = null + var extraData: MutableMap? = null + + while (reader.hasNext()) { + val key = reader.nextName() + when (key) { + "cid" -> cid = reader.nextString() + "id" -> id = reader.nextString() + "type" -> type = reader.nextString() + "name" -> name = reader.nextString() + "image" -> image = reader.nextString() + "watcher_count" -> watcherCount = reader.nextInt() + "filter_tags" -> filterTags = parseStringList(reader) + "frozen" -> frozen = reader.nextBoolean() + "last_message_at" -> lastMessageAt = dateAdapter.fromJson(reader) + "created_at" -> createdAt = dateAdapter.fromJson(reader) + "deleted_at" -> deletedAt = dateAdapter.fromJson(reader) + "updated_at" -> updatedAt = dateAdapter.fromJson(reader) + "member_count" -> memberCount = reader.nextInt() + "messages" -> messages = parseMessageList(reader) + "members" -> members = parseMemberList(reader) + "watchers" -> watchers = parseUserList(reader) + "read" -> read = parseChannelUserReadList(reader) + "config" -> config = configAdapter.fromJson(reader) + "created_by" -> createdBy = userAdapter.fromJson(reader) + "team" -> team = reader.nextString() + "cooldown" -> cooldown = reader.nextInt() + "pinned_messages" -> pinnedMessages = parseMessageList(reader) + "own_capabilities" -> ownCapabilities = parseStringList(reader) + "membership" -> membership = memberAdapter.fromJson(reader) + "active_live_locations" -> activeLiveLocations = parseLocationList(reader) + "message_count" -> messageCount = reader.nextInt() + else -> reader.readJsonValue()?.let { value -> + val map = extraData ?: mutableMapOf().also { extraData = it } + map[key] = value + } + } + } + reader.endObject() + + if (cid == null) { + throw JsonDataException("Required value 'cid' missing at ${reader.path}") + } + if (id == null) { + throw JsonDataException("Required value 'id' missing at ${reader.path}") + } + if (type == null) { + throw JsonDataException("Required value 'type' missing at ${reader.path}") + } + if (frozen == null) { + throw JsonDataException("Required value 'frozen' missing at ${reader.path}") + } + if (config == null) { + throw JsonDataException("Required value 'config' missing at ${reader.path}") + } + + // Build ChannelInfo to inject into messages (matching DTO path behavior) + val channelInfo = ChannelInfo( + cid = cid, + id = id, + type = type, + memberCount = memberCount ?: 0, + name = name, + image = image, + ) + + // Post-process read list with correct lastReceivedEventDate (after lastMessageAt is known) + val processedRead = read?.map { channelUserRead -> + channelUserRead.copy( + lastReceivedEventDate = lastMessageAt ?: channelUserRead.lastRead, + ) + } + + return Channel( + id = id, + type = type, + name = name ?: "", + image = image ?: "", + watcherCount = watcherCount ?: 0, + filterTags = filterTags ?: emptyList(), + frozen = frozen, + createdAt = createdAt, + deletedAt = deletedAt, + updatedAt = updatedAt, + memberCount = memberCount ?: 0, + messages = messages?.map { it.copy(channelInfo = channelInfo) } ?: emptyList(), + members = members ?: emptyList(), + watchers = watchers ?: emptyList(), + read = processedRead ?: emptyList(), + config = config, + createdBy = createdBy ?: User(), + team = team ?: "", + cooldown = cooldown ?: 0, + pinnedMessages = pinnedMessages?.map { it.copy(channelInfo = channelInfo) } ?: emptyList(), + ownCapabilities = ownCapabilities?.toSet() ?: emptySet(), + membership = membership, + activeLiveLocations = activeLiveLocations ?: emptyList(), + messageCount = messageCount, + lastMessageAt = lastMessageAt, + extraData = extraData ?: mutableMapOf(), + ).syncUnreadCountWithReads(currentUserIdProvider()) + .let(channelTransformer::transform) + } + + private fun parseMessageList(reader: JsonReader): List? { + return if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + buildList { + while (reader.hasNext()) { + messageAdapter.fromJson(reader)?.let { add(it) } + } + }.also { reader.endArray() } + } else { + reader.skipValue() + null + } + } + + private fun parseMemberList(reader: JsonReader): List? { + return if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + buildList { + while (reader.hasNext()) { + memberAdapter.fromJson(reader)?.let { add(it) } + } + }.also { reader.endArray() } + } else { + reader.skipValue() + null + } + } + + private fun parseUserList(reader: JsonReader): List? { + return if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + buildList { + while (reader.hasNext()) { + userAdapter.fromJson(reader)?.let { add(it) } + } + }.also { reader.endArray() } + } else { + reader.skipValue() + null + } + } + + private fun parseLocationList(reader: JsonReader): List? { + return if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + buildList { + while (reader.hasNext()) { + locationAdapter.fromJson(reader)?.let { add(it) } + } + }.also { reader.endArray() } + } else { + reader.skipValue() + null + } + } + + private fun parseStringList(reader: JsonReader): List? { + return if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + buildList { + while (reader.hasNext()) { + add(reader.nextString()) + } + }.also { reader.endArray() } + } else { + reader.skipValue() + null + } + } + + private fun parseChannelUserReadList(reader: JsonReader): List? { + return if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + buildList { + while (reader.hasNext()) { + parseChannelUserRead(reader)?.let { add(it) } + } + }.also { reader.endArray() } + } else { + reader.skipValue() + null + } + } + + @Suppress("ThrowsCount") + private fun parseChannelUserRead(reader: JsonReader): ChannelUserRead? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var user: User? = null + var lastRead: Date? = null + var unreadMessages: Int? = null + var lastReadMessageId: String? = null + var lastDeliveredAt: Date? = null + var lastDeliveredMessageId: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "user" -> user = userAdapter.fromJson(reader) + "last_read" -> lastRead = dateAdapter.fromJson(reader) + "unread_messages" -> unreadMessages = reader.nextInt() + "last_read_message_id" -> lastReadMessageId = reader.nextString() + "last_delivered_at" -> lastDeliveredAt = dateAdapter.fromJson(reader) + "last_delivered_message_id" -> lastDeliveredMessageId = reader.nextString() + else -> reader.skipValue() + } + } + reader.endObject() + + if (user == null) { + throw JsonDataException("Required value 'user' missing at ${reader.path}") + } + if (lastRead == null) { + throw JsonDataException("Required value 'last_read' missing at ${reader.path}") + } + if (unreadMessages == null) { + throw JsonDataException("Required value 'unread_messages' missing at ${reader.path}") + } + + // Use lastRead as placeholder - will be updated with correct lastReceivedEventDate later + return ChannelUserRead( + user = user, + lastReceivedEventDate = lastRead, + lastRead = lastRead, + unreadMessages = unreadMessages, + lastReadMessageId = lastReadMessageId, + lastDeliveredAt = lastDeliveredAt, + lastDeliveredMessageId = lastDeliveredMessageId, + ) + } + + override fun toJson(p0: JsonWriter, p1: Channel?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelInfoAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelInfoAdapter.kt new file mode 100644 index 00000000000..edfb914b776 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelInfoAdapter.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.ChannelInfo + +internal class ChannelInfoAdapter : JsonAdapter() { + override fun fromJson(reader: JsonReader): ChannelInfo? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var cid: String? = null + var id: String? = null + var memberCount: Int = 0 + var name: String? = null + var type: String? = null + var image: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "cid" -> cid = reader.nextString() + "id" -> id = reader.nextString() + "member_count" -> memberCount = reader.nextInt() + "name" -> name = reader.nextString() + "type" -> type = reader.nextString() + "image" -> image = reader.nextString() + else -> reader.skipValue() + } + } + reader.endObject() + + return ChannelInfo( + cid = cid, + id = id, + memberCount = memberCount, + name = name, + type = type, + image = image, + ) + } + + override fun toJson(p0: JsonWriter, p1: ChannelInfo?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelUserReadAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelUserReadAdapter.kt new file mode 100644 index 00000000000..1180c790161 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ChannelUserReadAdapter.kt @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.ChannelUserRead +import io.getstream.chat.android.models.User +import java.util.Date + +internal class ChannelUserReadAdapter( + private val userAdapter: JsonAdapter, + private val dateAdapter: JsonAdapter, + private val lastReceivedEventDate: Date, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): ChannelUserRead? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var user: User? = null + var lastRead: Date? = null + var unreadMessages: Int? = null + var lastReadMessageId: String? = null + var lastDeliveredAt: Date? = null + var lastDeliveredMessageId: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "user" -> user = userAdapter.fromJson(reader) + "last_read" -> lastRead = dateAdapter.fromJson(reader) + "unread_messages" -> unreadMessages = reader.nextInt() + "last_read_message_id" -> lastReadMessageId = reader.nextString() + "last_delivered_at" -> lastDeliveredAt = dateAdapter.fromJson(reader) + "last_delivered_message_id" -> lastDeliveredMessageId = reader.nextString() + else -> reader.skipValue() + } + } + reader.endObject() + + if (user == null) { + throw JsonDataException("Required value 'user' missing at ${reader.path}") + } + if (lastRead == null) { + throw JsonDataException("Required value 'last_read' missing at ${reader.path}") + } + if (unreadMessages == null) { + throw JsonDataException("Required value 'unread_messages' missing at ${reader.path}") + } + + return ChannelUserRead( + user = user, + lastReceivedEventDate = lastReceivedEventDate, + lastRead = lastRead, + unreadMessages = unreadMessages, + lastReadMessageId = lastReadMessageId, + lastDeliveredAt = lastDeliveredAt, + lastDeliveredMessageId = lastDeliveredMessageId, + ) + } + + override fun toJson(p0: JsonWriter, p1: ChannelUserRead?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/CommandAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/CommandAdapter.kt new file mode 100644 index 00000000000..dfeb83b9a4c --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/CommandAdapter.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Command + +internal class CommandAdapter : JsonAdapter() { + override fun fromJson(reader: JsonReader): Command? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var name: String? = null + var description: String? = null + var args: String? = null + var set: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "name" -> name = reader.nextString() + "description" -> description = reader.nextString() + "args" -> args = reader.nextString() + "set" -> set = reader.nextString() + else -> reader.skipValue() + } + } + reader.endObject() + + if (name == null) { + throw JsonDataException("Required value 'name' missing at ${reader.path}") + } + if (description == null) { + throw JsonDataException("Required value 'description' missing at ${reader.path}") + } + if (args == null) { + throw JsonDataException("Required value 'args' missing at ${reader.path}") + } + if (set == null) { + throw JsonDataException("Required value 'set_' (JSON name 'set') missing at ${reader.path}") + } + + return Command( + name = name, + description = description, + args = args, + set = set, + ) + } + + override fun toJson(p0: JsonWriter, p1: Command?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ConfigAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ConfigAdapter.kt new file mode 100644 index 00000000000..114cc31d259 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ConfigAdapter.kt @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Command +import io.getstream.chat.android.models.Config +import java.util.Date + +internal class ConfigAdapter( + private val dateAdapter: JsonAdapter, + private val commandAdapter: JsonAdapter, +) : JsonAdapter() { + + @Suppress("LongMethod") + override fun fromJson(reader: JsonReader): Config? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var createdAt: Date? = null + var updatedAt: Date? = null + var name: String? = null + var typingEvents: Boolean? = null + var readEvents: Boolean? = null + var deliveryEvents: Boolean = true + var connectEvents: Boolean? = null + var search: Boolean? = null + var reactions: Boolean? = null + var replies: Boolean? = null + var mutes: Boolean? = null + var uploads: Boolean? = null + var urlEnrichment: Boolean? = null + var customEvents: Boolean? = null + var pushNotifications: Boolean? = null + var skipLastMsgUpdateForSystemMsgs: Boolean? = null + var polls: Boolean? = null + var messageRetention: String? = null + var maxMessageLength: Int? = null + var automod: String? = null + var automodBehavior: String? = null + var blocklistBehavior: String? = null + var commands: List? = null + var userMessageReminders: Boolean? = null + var sharedLocations: Boolean? = null + var markMessagesPending: Boolean? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "created_at" -> createdAt = dateAdapter.fromJson(reader) + "updated_at" -> updatedAt = dateAdapter.fromJson(reader) + "name" -> name = reader.nextString() + "typing_events" -> typingEvents = reader.nextBoolean() + "read_events" -> readEvents = reader.nextBoolean() + "delivery_events" -> deliveryEvents = reader.nextBoolean() + "connect_events" -> connectEvents = reader.nextBoolean() + "search" -> search = reader.nextBoolean() + "reactions" -> reactions = reader.nextBoolean() + "replies" -> replies = reader.nextBoolean() + "mutes" -> mutes = reader.nextBoolean() + "uploads" -> uploads = reader.nextBoolean() + "url_enrichment" -> urlEnrichment = reader.nextBoolean() + "custom_events" -> customEvents = reader.nextBoolean() + "push_notifications" -> pushNotifications = reader.nextBoolean() + "skip_last_msg_update_for_system_msgs" -> skipLastMsgUpdateForSystemMsgs = reader.nextBoolean() + "polls" -> polls = reader.nextBoolean() + "message_retention" -> messageRetention = reader.nextString() + "max_message_length" -> maxMessageLength = reader.nextInt() + "automod" -> automod = reader.nextString() + "automod_behavior" -> automodBehavior = reader.nextString() + "blocklist_behavior" -> blocklistBehavior = reader.nextString() + "commands" -> commands = parseCommandList(reader) + "user_message_reminders" -> userMessageReminders = reader.nextBoolean() + "shared_locations" -> sharedLocations = reader.nextBoolean() + "mark_messages_pending" -> markMessagesPending = reader.nextBoolean() + else -> reader.skipValue() + } + } + reader.endObject() + + if (typingEvents == null) { + throw JsonDataException("Required value 'typing_events' missing at ${reader.path}") + } + if (readEvents == null) { + throw JsonDataException("Required value 'read_events' missing at ${reader.path}") + } + if (connectEvents == null) { + throw JsonDataException("Required value 'connect_events' missing at ${reader.path}") + } + if (search == null) { + throw JsonDataException("Required value 'search' missing at ${reader.path}") + } + if (reactions == null) { + throw JsonDataException("Required value 'reactions' missing at ${reader.path}") + } + if (replies == null) { + throw JsonDataException("Required value 'replies' missing at ${reader.path}") + } + if (mutes == null) { + throw JsonDataException("Required value 'mutes' missing at ${reader.path}") + } + if (uploads == null) { + throw JsonDataException("Required value 'uploads' missing at ${reader.path}") + } + if (urlEnrichment == null) { + throw JsonDataException("Required value 'url_enrichment' missing at ${reader.path}") + } + if (customEvents == null) { + throw JsonDataException("Required value 'custom_events' missing at ${reader.path}") + } + if (pushNotifications == null) { + throw JsonDataException("Required value 'push_notifications' missing at ${reader.path}") + } + if (polls == null) { + throw JsonDataException("Required value 'polls' missing at ${reader.path}") + } + if (messageRetention == null) { + throw JsonDataException("Required value 'message_retention' missing at ${reader.path}") + } + if (maxMessageLength == null) { + throw JsonDataException("Required value 'max_message_length' missing at ${reader.path}") + } + if (automod == null) { + throw JsonDataException("Required value 'automod' missing at ${reader.path}") + } + if (automodBehavior == null) { + throw JsonDataException("Required value 'automod_behavior' missing at ${reader.path}") + } + if (commands == null) { + throw JsonDataException("Required value 'commands' missing at ${reader.path}") + } + if (markMessagesPending == null) { + throw JsonDataException("Required value 'mark_messages_pending' missing at ${reader.path}") + } + + return Config( + createdAt = createdAt, + updatedAt = updatedAt, + name = name ?: "", + typingEventsEnabled = typingEvents, + readEventsEnabled = readEvents, + deliveryEventsEnabled = deliveryEvents, + connectEventsEnabled = connectEvents, + searchEnabled = search, + isReactionsEnabled = reactions, + isThreadEnabled = replies, + muteEnabled = mutes, + uploadsEnabled = uploads, + urlEnrichmentEnabled = urlEnrichment, + customEventsEnabled = customEvents, + pushNotificationsEnabled = pushNotifications, + skipLastMsgUpdateForSystemMsgs = skipLastMsgUpdateForSystemMsgs ?: false, + pollsEnabled = polls, + messageRetention = messageRetention, + maxMessageLength = maxMessageLength, + automod = automod, + automodBehavior = automodBehavior, + blocklistBehavior = blocklistBehavior ?: "", + commands = commands, + messageRemindersEnabled = userMessageReminders ?: false, + sharedLocationsEnabled = sharedLocations ?: false, + markMessagesPending = markMessagesPending, + ) + } + + override fun toJson(p0: JsonWriter, p1: Config?) { + error("Serialization not supported for direct-to-domain path") + } + + private fun parseCommandList(reader: JsonReader): MutableList { + val list = mutableListOf() + if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + while (reader.hasNext()) { + commandAdapter.fromJson(reader)?.let { list.add(it) } + } + reader.endArray() + } else { + reader.skipValue() + } + return list + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/DeviceAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/DeviceAdapter.kt new file mode 100644 index 00000000000..540899f0659 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/DeviceAdapter.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Device +import io.getstream.chat.android.models.PushProvider + +internal class DeviceAdapter : JsonAdapter() { + override fun fromJson(reader: JsonReader): Device? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var id: String? = null + var pushProvider: String? = null + var pushProviderName: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "id" -> id = reader.nextString() + "push_provider" -> pushProvider = reader.nextString() + "push_provider_name" -> pushProviderName = reader.nextString() + else -> reader.skipValue() + } + } + reader.endObject() + + if (id == null) { + throw JsonDataException("Required value 'id' missing at ${reader.path}") + } + if (pushProvider == null) { + throw JsonDataException("Required value 'push_provider' missing at ${reader.path}") + } + + return Device( + token = id, + pushProvider = PushProvider.fromKey(pushProvider), + providerName = pushProviderName, + ) + } + + override fun toJson(p0: JsonWriter, p1: Device?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/LocationAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/LocationAdapter.kt new file mode 100644 index 00000000000..1609fa1856a --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/LocationAdapter.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Location +import java.util.Date + +internal class LocationAdapter( + private val dateAdapter: JsonAdapter, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): Location? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var channelCid: String? = null + var messageId: String? = null + var userId: String? = null + var latitude: Double? = null + var longitude: Double? = null + var createdByDeviceId: String? = null + var endAt: Date? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "channel_cid" -> channelCid = reader.nextString() + "message_id" -> messageId = reader.nextString() + "user_id" -> userId = reader.nextString() + "latitude" -> latitude = reader.nextDouble() + "longitude" -> longitude = reader.nextDouble() + "created_by_device_id" -> createdByDeviceId = reader.nextString() + "end_at" -> endAt = dateAdapter.fromJson(reader) + else -> reader.skipValue() + } + } + reader.endObject() + + if (channelCid == null) { + throw JsonDataException("Required value 'channel_cid' missing at ${reader.path}") + } + if (messageId == null) { + throw JsonDataException("Required value 'message_id' missing at ${reader.path}") + } + if (userId == null) { + throw JsonDataException("Required value 'user_id' missing at ${reader.path}") + } + if (latitude == null) { + throw JsonDataException("Required value 'latitude' missing at ${reader.path}") + } + if (longitude == null) { + throw JsonDataException("Required value 'longitude' missing at ${reader.path}") + } + if (createdByDeviceId == null) { + throw JsonDataException("Required value 'created_by_device_id' missing at ${reader.path}") + } + + return Location( + cid = channelCid, + messageId = messageId, + userId = userId, + latitude = latitude, + longitude = longitude, + deviceId = createdByDeviceId, + endAt = endAt, + ) + } + + override fun toJson(p0: JsonWriter, p1: Location?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MemberAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MemberAdapter.kt new file mode 100644 index 00000000000..255027aea23 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MemberAdapter.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Member +import io.getstream.chat.android.models.User +import java.util.Date + +internal class MemberAdapter( + private val userAdapter: JsonAdapter, + private val dateAdapter: JsonAdapter, +) : JsonAdapter() { + + @Suppress("LongMethod") + override fun fromJson(reader: JsonReader): Member? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + + var user: User? = null + var createdAt: Date? = null + var updatedAt: Date? = null + var invited: Boolean? = null + var inviteAcceptedAt: Date? = null + var inviteRejectedAt: Date? = null + var shadowBanned: Boolean? = null + var banned: Boolean? = null + var channelRole: String? = null + var notificationsMuted: Boolean? = null + var status: String? = null + var banExpires: Date? = null + var pinnedAt: Date? = null + var archivedAt: Date? = null + var extraData: MutableMap? = null + + while (reader.hasNext()) { + val key = reader.nextName() + when (key) { + "user" -> user = userAdapter.fromJson(reader) + "created_at" -> createdAt = dateAdapter.fromJson(reader) + "updated_at" -> updatedAt = dateAdapter.fromJson(reader) + "invited" -> invited = reader.nextBoolean() + "invite_accepted_at" -> inviteAcceptedAt = dateAdapter.fromJson(reader) + "invite_rejected_at" -> inviteRejectedAt = dateAdapter.fromJson(reader) + "shadow_banned" -> shadowBanned = reader.nextBoolean() + "banned" -> banned = reader.nextBoolean() + "channel_role" -> channelRole = reader.nextString() + "notifications_muted" -> notificationsMuted = reader.nextBoolean() + "status" -> status = reader.nextString() + "ban_expires" -> banExpires = dateAdapter.fromJson(reader) + "pinned_at" -> pinnedAt = dateAdapter.fromJson(reader) + "archived_at" -> archivedAt = dateAdapter.fromJson(reader) + else -> reader.readJsonValue()?.let { value -> + val map = extraData ?: mutableMapOf().also { extraData = it } + map[key] = value + } + } + } + reader.endObject() + + if (user == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'user' missing at ${reader.path} at ${reader.path}", + ) + } + + return Member( + user = user, + createdAt = createdAt, + updatedAt = updatedAt, + isInvited = invited, + inviteAcceptedAt = inviteAcceptedAt, + inviteRejectedAt = inviteRejectedAt, + shadowBanned = shadowBanned ?: false, + banned = banned ?: false, + channelRole = channelRole, + notificationsMuted = notificationsMuted, + status = status, + banExpires = banExpires, + pinnedAt = pinnedAt, + archivedAt = archivedAt, + extraData = extraData?.toMap() ?: emptyMap(), + ) + } + + override fun toJson(p0: JsonWriter, p1: Member?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageAdapter.kt new file mode 100644 index 00000000000..ca94e30bd05 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageAdapter.kt @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Attachment +import io.getstream.chat.android.models.ChannelInfo +import io.getstream.chat.android.models.Location +import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.MessageModerationDetails +import io.getstream.chat.android.models.MessageReminderInfo +import io.getstream.chat.android.models.MessageTransformer +import io.getstream.chat.android.models.Moderation +import io.getstream.chat.android.models.Poll +import io.getstream.chat.android.models.Reaction +import io.getstream.chat.android.models.ReactionGroup +import io.getstream.chat.android.models.User +import java.util.Date + +@Suppress("LongParameterList") +internal class MessageAdapter( + private val attachmentAdapter: JsonAdapter, + private val channelInfoAdapter: JsonAdapter, + private val reactionAdapter: JsonAdapter, + private val reactionGroupAdapter: ReactionGroupAdapter, + private val userAdapter: JsonAdapter, + private val moderationDetailsAdapter: JsonAdapter, + private val moderationAdapter: JsonAdapter, + private val pollAdapter: JsonAdapter, + private val reminderAdapter: JsonAdapter, + private val locationAdapter: JsonAdapter, + private val dateAdapter: JsonAdapter, + private val messageTransformer: MessageTransformer, +) : JsonAdapter() { + + override fun fromJson(reader: JsonReader): Message? { + return fromJson(reader, fallbackChannelInfo = null) + } + + @Suppress("LongMethod", "ThrowsCount") + fun fromJson(reader: JsonReader, fallbackChannelInfo: ChannelInfo?): Message? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + + var attachments: List? = null + var channel: ChannelInfo? = null + var cid: String? = null + var command: String? = null + var createdAt: Date? = null + var deletedAt: Date? = null + var html: String? = null + var i18n: Map? = null + var id: String? = null + var latestReactions: List? = null + var mentionedUsers: List? = null + var ownReactions: List? = null + var parentId: String? = null + var pinExpires: Date? = null + var pinned: Boolean? = null + var pinnedAt: Date? = null + var messageTextUpdatedAt: Date? = null + var pinnedBy: User? = null + var quotedMessage: Message? = null + var quotedMessageId: String? = null + var reactionCounts: Map? = null + var reactionScores: Map? = null + var reactionGroups: Map? = null + var replyCount: Int? = null + var deletedReplyCount: Int? = null + var shadowed: Boolean? = null + var showInChannel: Boolean? = null + var silent: Boolean? = null + var text: String? = null + var threadParticipants: List? = null + var type: String? = null + var updatedAt: Date? = null + var user: User? = null + var moderationDetails: MessageModerationDetails? = null + var moderation: Moderation? = null + var poll: Poll? = null + var reminder: MessageReminderInfo? = null + var sharedLocation: Location? = null + var channelRole: String? = null + var deletedForMe: Boolean? = null + var extraData: MutableMap? = null + + while (reader.hasNext()) { + val key = reader.nextName() + when (key) { + "attachments" -> attachments = parseAttachmentsList(reader) + "channel" -> channel = channelInfoAdapter.fromJson(reader) + "cid" -> cid = reader.nextString() + "command" -> command = reader.nextString() + "created_at" -> createdAt = dateAdapter.fromJson(reader) + "deleted_at" -> deletedAt = dateAdapter.fromJson(reader) + "html" -> html = reader.nextString() + "i18n" -> i18n = parseStringMap(reader) + "id" -> id = reader.nextString() + "latest_reactions" -> latestReactions = parseReactionsList(reader) + "mentioned_users" -> mentionedUsers = parseUsersList(reader) + "own_reactions" -> ownReactions = parseReactionsList(reader) + "parent_id" -> parentId = reader.nextString() + "pin_expires" -> pinExpires = dateAdapter.fromJson(reader) + "pinned" -> pinned = reader.nextBoolean() + "pinned_at" -> pinnedAt = dateAdapter.fromJson(reader) + "message_text_updated_at" -> messageTextUpdatedAt = dateAdapter.fromJson(reader) + "pinned_by" -> pinnedBy = userAdapter.fromJson(reader) + "quoted_message" -> { + // Recursive parsing: pass the fallback channel info along + val resolvedChannelInfo = channel ?: fallbackChannelInfo + quotedMessage = fromJson(reader, resolvedChannelInfo) + } + + "quoted_message_id" -> quotedMessageId = reader.nextString() + "reaction_counts" -> reactionCounts = parseIntMap(reader) + "reaction_scores" -> reactionScores = parseIntMap(reader) + "reaction_groups" -> reactionGroups = reactionGroupAdapter.parseReactionGroupsMap(reader) + "reply_count" -> replyCount = reader.nextInt() + "deleted_reply_count" -> deletedReplyCount = reader.nextInt() + "shadowed" -> shadowed = reader.nextBoolean() + "show_in_channel" -> showInChannel = reader.nextBoolean() + "silent" -> silent = reader.nextBoolean() + "text" -> text = reader.nextString() + "thread_participants" -> threadParticipants = parseUsersList(reader) + "type" -> type = reader.nextString() + "updated_at" -> updatedAt = dateAdapter.fromJson(reader) + "user" -> user = userAdapter.fromJson(reader) + "moderation_details" -> moderationDetails = moderationDetailsAdapter.fromJson(reader) + "moderation" -> moderation = moderationAdapter.fromJson(reader) + "poll" -> poll = pollAdapter.fromJson(reader) + "reminder" -> reminder = reminderAdapter.fromJson(reader) + "shared_location" -> sharedLocation = locationAdapter.fromJson(reader) + "member" -> channelRole = parseMemberChannelRole(reader) + "deleted_for_me" -> deletedForMe = reader.nextBoolean() + else -> reader.readJsonValue()?.let { value -> + val map = extraData ?: mutableMapOf().also { extraData = it } + map[key] = value + } + } + } + reader.endObject() + + if (attachments == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'attachments' missing at ${reader.path} at ${reader.path}", + ) + } + if (cid == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'cid' missing at ${reader.path} at ${reader.path}", + ) + } + if (createdAt == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'created_at' missing at ${reader.path} at ${reader.path}", + ) + } + if (html == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'html' missing at ${reader.path} at ${reader.path}", + ) + } + if (id == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'id' missing at ${reader.path} at ${reader.path}", + ) + } + if (latestReactions == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'latest_reactions' missing at ${reader.path} at ${reader.path}", + ) + } + if (mentionedUsers == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'mentioned_users' missing at ${reader.path} at ${reader.path}", + ) + } + if (ownReactions == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'own_reactions' missing at ${reader.path} at ${reader.path}", + ) + } + if (replyCount == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'reply_count' missing at ${reader.path} at ${reader.path}", + ) + } + if (deletedReplyCount == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'deleted_reply_count' missing at ${reader.path} at ${reader.path}", + ) + } + if (silent == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'silent' missing at ${reader.path} at ${reader.path}", + ) + } + if (text == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'text' missing at ${reader.path} at ${reader.path}", + ) + } + if (type == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'type' missing at ${reader.path} at ${reader.path}", + ) + } + if (updatedAt == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'updated_at' missing at ${reader.path} at ${reader.path}", + ) + } + if (user == null) { + throw JsonDataException( + "com.squareup.moshi.JsonDataException: " + + "Required value 'user' missing at ${reader.path} at ${reader.path}", + ) + } + + val resolvedChannelInfo = channel ?: fallbackChannelInfo + + // Filter reactions by messageId (matching DomainMapping behavior) + val filteredLatestReactions = latestReactions.filter { it.messageId == id } + val filteredOwnReactions = ownReactions.filter { it.messageId == id } + + // Calculate last update time: max of updated_at and poll?.updatedAt + val lastUpdateTime = listOfNotNull( + updatedAt, + poll?.updatedAt, + ).maxByOrNull { it.time } ?: updatedAt + + return Message( + attachments = attachments, + channelInfo = resolvedChannelInfo, + cid = cid, + command = command, + createdAt = createdAt, + deletedAt = deletedAt, + html = html, + i18n = i18n ?: emptyMap(), + id = id, + latestReactions = filteredLatestReactions, + mentionedUsersIds = mentionedUsers.map { it.id }, + mentionedUsers = mentionedUsers, + ownReactions = filteredOwnReactions, + parentId = parentId, + pinExpires = pinExpires, + pinned = pinned ?: false, + pinnedAt = pinnedAt, + pinnedBy = pinnedBy, + reactionCounts = reactionCounts ?: mutableMapOf(), + reactionScores = reactionScores ?: mutableMapOf(), + reactionGroups = reactionGroups ?: emptyMap(), + replyCount = replyCount, + deletedReplyCount = deletedReplyCount, + replyMessageId = quotedMessageId, + replyTo = quotedMessage, + shadowed = shadowed ?: false, + showInChannel = showInChannel ?: false, + silent = silent, + text = text, + threadParticipants = threadParticipants ?: emptyList(), + type = type, + updatedAt = lastUpdateTime, + user = user, + moderationDetails = moderationDetails, + moderation = moderation, + messageTextUpdatedAt = messageTextUpdatedAt, + poll = poll, + restrictedVisibility = emptyList(), + reminder = reminder, + sharedLocation = sharedLocation, + channelRole = channelRole, + deletedForMe = deletedForMe ?: false, + extraData = extraData ?: emptyMap(), + ).let(messageTransformer::transform) + } + + private fun parseAttachmentsList(reader: JsonReader): List? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) { + reader.skipValue() + return null + } + reader.beginArray() + val list = mutableListOf() + while (reader.hasNext()) { + attachmentAdapter.fromJson(reader)?.let { list.add(it) } + } + reader.endArray() + return list + } + + private fun parseReactionsList(reader: JsonReader): List? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) { + reader.skipValue() + return null + } + reader.beginArray() + val list = mutableListOf() + while (reader.hasNext()) { + reactionAdapter.fromJson(reader)?.let { list.add(it) } + } + reader.endArray() + return list + } + + private fun parseUsersList(reader: JsonReader): List? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) { + reader.skipValue() + return null + } + reader.beginArray() + val list = mutableListOf() + while (reader.hasNext()) { + userAdapter.fromJson(reader)?.let { list.add(it) } + } + reader.endArray() + return list + } + + private fun parseIntMap(reader: JsonReader): Map? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + if (reader.peek() != JsonReader.Token.BEGIN_OBJECT) { + reader.skipValue() + return null + } + reader.beginObject() + val map = mutableMapOf() + while (reader.hasNext()) { + val key = reader.nextName() + map[key] = reader.nextInt() + } + reader.endObject() + return map + } + + private fun parseStringMap(reader: JsonReader): Map? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + if (reader.peek() != JsonReader.Token.BEGIN_OBJECT) { + reader.skipValue() + return null + } + reader.beginObject() + val map = mutableMapOf() + while (reader.hasNext()) { + val key = reader.nextName() + map[key] = reader.nextString() + } + reader.endObject() + return map + } + + private fun parseMemberChannelRole(reader: JsonReader): String? { + if (reader.peek() != JsonReader.Token.BEGIN_OBJECT) { + reader.skipValue() + return null + } + + reader.beginObject() + var channelRole: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "channel_role" -> channelRole = reader.nextString() + else -> reader.skipValue() + } + } + reader.endObject() + + return channelRole + } + + override fun toJson(p0: JsonWriter, p1: Message?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageModerationDetailsAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageModerationDetailsAdapter.kt new file mode 100644 index 00000000000..4bf0e346d41 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageModerationDetailsAdapter.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.MessageModerationAction +import io.getstream.chat.android.models.MessageModerationDetails + +internal class MessageModerationDetailsAdapter : JsonAdapter() { + override fun fromJson(reader: JsonReader): MessageModerationDetails? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var originalText: String? = null + var action: String? = null + var errorMsg: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "original_text" -> originalText = reader.nextString() + "action" -> action = reader.nextString() + "error_msg" -> errorMsg = reader.nextString() + else -> reader.skipValue() + } + } + reader.endObject() + + return MessageModerationDetails( + originalText = originalText.orEmpty(), + action = MessageModerationAction.fromRawValue(action.orEmpty()), + errorMsg = errorMsg.orEmpty(), + ) + } + + override fun toJson(p0: JsonWriter, p1: MessageModerationDetails?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageReminderInfoAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageReminderInfoAdapter.kt new file mode 100644 index 00000000000..aee6f9fd6ae --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/MessageReminderInfoAdapter.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.MessageReminderInfo +import java.util.Date + +internal class MessageReminderInfoAdapter( + private val dateAdapter: JsonAdapter, +) : JsonAdapter() { + override fun fromJson(reader: JsonReader): MessageReminderInfo? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var remindAt: Date? = null + var createdAt: Date? = null + var updatedAt: Date? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "remind_at" -> remindAt = dateAdapter.fromJson(reader) + "created_at" -> createdAt = dateAdapter.fromJson(reader) + "updated_at" -> updatedAt = dateAdapter.fromJson(reader) + else -> reader.skipValue() + } + } + reader.endObject() + + if (createdAt == null) { + throw JsonDataException("Required value 'created_at' missing at ${reader.path}") + } + if (updatedAt == null) { + throw JsonDataException("Required value 'updated_at' missing at ${reader.path}") + } + + return MessageReminderInfo( + remindAt = remindAt, + createdAt = createdAt, + updatedAt = updatedAt, + ) + } + + override fun toJson(p0: JsonWriter, p1: MessageReminderInfo?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ModerationAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ModerationAdapter.kt new file mode 100644 index 00000000000..ebaad242a67 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/ModerationAdapter.kt @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Moderation +import io.getstream.chat.android.models.ModerationAction + +internal class ModerationAdapter : JsonAdapter() { + override fun fromJson(reader: JsonReader): Moderation? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + var action: String? = null + var originalText: String? = null + var textHarms: List? = null + var imageHarms: List? = null + var blocklistMatched: String? = null + var semanticFilterMatched: String? = null + var platformCircumvented: Boolean? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "action" -> action = reader.nextString() + "original_text" -> originalText = reader.nextString() + "text_harms" -> textHarms = parseStringList(reader) + "image_harms" -> imageHarms = parseStringList(reader) + "blocklist_matched" -> blocklistMatched = reader.nextString() + "semantic_filter_matched" -> semanticFilterMatched = reader.nextString() + "platform_circumvented" -> platformCircumvented = reader.nextBoolean() + else -> reader.skipValue() + } + } + reader.endObject() + + if (action == null) { + throw JsonDataException("Required value 'action' missing at ${reader.path}") + } + if (originalText == null) { + throw JsonDataException("Required value 'original_text' missing at ${reader.path}") + } + + return Moderation( + action = ModerationAction.fromValue(action), + originalText = originalText, + textHarms = textHarms.orEmpty(), + imageHarms = imageHarms.orEmpty(), + blocklistMatched = blocklistMatched, + semanticFilterMatched = semanticFilterMatched, + platformCircumvented = platformCircumvented ?: false, + ) + } + + private fun parseStringList(reader: JsonReader): List? { + return if (reader.peek() == JsonReader.Token.BEGIN_ARRAY) { + reader.beginArray() + buildList { + while (reader.hasNext()) { + add(reader.nextString()) + } + }.also { reader.endArray() } + } else { + reader.skipValue() + null + } + } + + override fun toJson(p0: JsonWriter, p1: Moderation?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/NewMessageEventAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/NewMessageEventAdapter.kt new file mode 100644 index 00000000000..c4950d01d15 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/NewMessageEventAdapter.kt @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.client.events.NewMessageEvent +import io.getstream.chat.android.client.parser2.adapters.internal.StreamDateFormatter +import io.getstream.chat.android.models.ChannelInfo +import io.getstream.chat.android.models.Message +import io.getstream.chat.android.models.User +import java.util.Date + +internal class NewMessageEventAdapter( + private val messageAdapter: JsonAdapter, + private val userAdapter: JsonAdapter, +) : JsonAdapter() { + + private val streamDateFormatter = StreamDateFormatter("NewMessageEventAdapter") + + @Suppress("LongMethod") + override fun fromJson(reader: JsonReader): NewMessageEvent? { + if (reader.peek() == JsonReader.Token.NULL) return reader.nextNull() + + reader.beginObject() + + var type: String? = null + var createdAt: Date? = null + var rawCreatedAt: String? = null + var user: User? = null + var cid: String? = null + var channelMemberCount: Int? = null + var channelCustomName: String? = null + var channelCustomImage: String? = null + var channelType: String? = null + var channelId: String? = null + var message: Message? = null + var watcherCount: Int = 0 + var totalUnreadCount: Int = 0 + var unreadChannels: Int = 0 + var channelMessageCount: Int? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "type" -> type = reader.nextString() + "created_at" -> { + if (reader.peek() != JsonReader.Token.NULL) { + val rawValue = reader.nextString() + rawCreatedAt = rawValue + createdAt = streamDateFormatter.parse(rawValue) + } else { + reader.skipValue() + } + } + "user" -> user = userAdapter.fromJson(reader) + "cid" -> cid = reader.nextString() + "channel_member_count" -> { + channelMemberCount = if (reader.peek() == JsonReader.Token.NULL) { + reader.nextNull() + } else { + reader.nextInt() + } + } + "channel_custom" -> { + val (name, image) = parseChannelCustom(reader) + channelCustomName = name + channelCustomImage = image + } + "channel_type" -> channelType = reader.nextString() + "channel_id" -> channelId = reader.nextString() + "message" -> message = messageAdapter.fromJson(reader) + "watcher_count" -> watcherCount = reader.nextInt() + "total_unread_count" -> totalUnreadCount = reader.nextInt() + "unread_channels" -> unreadChannels = reader.nextInt() + "channel_message_count" -> { + channelMessageCount = if (reader.peek() == JsonReader.Token.NULL) { + reader.nextNull() + } else { + reader.nextInt() + } + } + else -> reader.skipValue() + } + } + reader.endObject() + + if (type == null) { + throw JsonDataException("Required value 'type' missing at ${reader.path}") + } + if (rawCreatedAt == null) { + throw JsonDataException("Required value 'created_at' missing at ${reader.path}") + } + if (user == null) { + throw JsonDataException("Required value 'user' missing at ${reader.path}") + } + if (cid == null) { + throw JsonDataException("Required value 'cid' missing at ${reader.path}") + } + if (channelType == null) { + throw JsonDataException("Required value 'channel_type' missing at ${reader.path}") + } + if (channelId == null) { + throw JsonDataException("Required value 'channel_id' missing at ${reader.path}") + } + if (message == null) { + throw JsonDataException("Required value 'message' missing at ${reader.path}") + } + + val channelInfo = ChannelInfo( + cid = cid, + id = channelId, + type = channelType, + memberCount = channelMemberCount ?: 0, + name = channelCustomName, + image = channelCustomImage, + ) + + return NewMessageEvent( + type = type, + createdAt = createdAt ?: Date(0), + rawCreatedAt = rawCreatedAt, + user = user, + cid = cid, + channelType = channelType, + channelId = channelId, + message = message.copy(channelInfo = channelInfo), + watcherCount = watcherCount, + totalUnreadCount = totalUnreadCount, + unreadChannels = unreadChannels, + channelMessageCount = channelMessageCount, + ) + } + + private fun parseChannelCustom(reader: JsonReader): Pair { + if (reader.peek() != JsonReader.Token.BEGIN_OBJECT) { + reader.skipValue() + return null to null + } + + reader.beginObject() + var name: String? = null + var image: String? = null + + while (reader.hasNext()) { + when (reader.nextName()) { + "name" -> name = if (reader.peek() == JsonReader.Token.NULL) { + reader.nextNull() + } else { + reader.nextString() + } + "image" -> image = if (reader.peek() == JsonReader.Token.NULL) { + reader.nextNull() + } else { + reader.nextString() + } + else -> reader.skipValue() + } + } + reader.endObject() + + return name to image + } + + override fun toJson(p0: JsonWriter, p1: NewMessageEvent?) { + error("Serialization not supported for direct-to-domain path") + } +} diff --git a/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/OptionAdapter.kt b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/OptionAdapter.kt new file mode 100644 index 00000000000..69376a66e73 --- /dev/null +++ b/stream-chat-android-client/src/main/java/io/getstream/chat/android/client/parser2/event/OptionAdapter.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014-2026 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.parser2.event + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import io.getstream.chat.android.models.Option + +internal class OptionAdapter : JsonAdapter