-
Notifications
You must be signed in to change notification settings - Fork 305
Introduce message delivery receipts #5979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
f223b1c
6242795
e9b54ad
02e4e2b
dc90cd8
296ae95
3b5384d
d503cf1
3be042a
1e1c964
5bae534
6e0e715
245c894
e561d09
3746f56
52c0bba
0b2d1cc
d508454
ebb75b8
83cd287
469a2b6
79a7024
b3d205a
45633af
73f2034
b4cb0fa
f57e0ab
f2a0c8e
3b59aba
991cff0
e68f57f
d072a12
ca74d26
8ee562f
9a55a6d
321b21b
4c7d597
ed6eb51
92c8c55
59fe832
3905140
9a3add6
e9dc48a
e809ce3
9c2874b
b984fe6
a938667
ead2502
e1b8dfc
1368d52
2589129
af695b0
9c0f88a
c0207c4
058f11d
6fcc75d
b07222f
c2c968e
e390768
4d1bef6
13b7ede
0b2aae6
3056ddb
64e9e05
edbdfc7
d3d8110
fec9268
39a4624
07e39e4
128a0fc
716b933
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,12 +122,17 @@ import io.getstream.chat.android.client.parser2.adapters.internal.StreamDateForm | |
| import io.getstream.chat.android.client.persistance.repository.RepositoryFacade | ||
| import io.getstream.chat.android.client.persistance.repository.factory.RepositoryFactory | ||
| import io.getstream.chat.android.client.persistance.repository.noop.NoOpRepositoryFactory | ||
| import io.getstream.chat.android.client.persistence.db.ChatClientDatabase | ||
| import io.getstream.chat.android.client.persistence.repository.ChatClientRepository | ||
| import io.getstream.chat.android.client.plugin.DependencyResolver | ||
| import io.getstream.chat.android.client.plugin.MessageDeliveredPluginFactory | ||
| import io.getstream.chat.android.client.plugin.Plugin | ||
| import io.getstream.chat.android.client.plugin.factory.PluginFactory | ||
| import io.getstream.chat.android.client.plugin.factory.ThrottlingPluginFactory | ||
| import io.getstream.chat.android.client.query.AddMembersParams | ||
| import io.getstream.chat.android.client.query.CreateChannelParams | ||
| import io.getstream.chat.android.client.receipts.MessageReceiptManager | ||
| import io.getstream.chat.android.client.receipts.MessageReceiptReporter | ||
| import io.getstream.chat.android.client.scope.ClientScope | ||
| import io.getstream.chat.android.client.scope.UserScope | ||
| import io.getstream.chat.android.client.setup.state.ClientState | ||
|
|
@@ -275,6 +280,9 @@ internal constructor( | |
| @InternalStreamChatApi | ||
| public val audioPlayer: AudioPlayer, | ||
| private val now: () -> Date = ::Date, | ||
| private val repository: ChatClientRepository, | ||
| private val messageReceiptReporter: MessageReceiptReporter, | ||
| internal val messageReceiptManager: MessageReceiptManager, | ||
| ) { | ||
| private val logger by taggedLogger(TAG) | ||
| private val waitConnection = MutableSharedFlow<Result<ConnectionData>>() | ||
|
|
@@ -447,13 +455,13 @@ internal constructor( | |
| mutableClientState.setUser(user) | ||
| } | ||
|
|
||
| is NewMessageEvent, | ||
| is NotificationReminderDueEvent, | ||
| -> { | ||
| // No other events should potentially show notifications | ||
| is NewMessageEvent -> { | ||
| notifications.onChatEvent(event) | ||
| messageReceiptManager.markMessageAsDelivered(event.message) | ||
| } | ||
|
|
||
| is NotificationReminderDueEvent -> notifications.onChatEvent(event) | ||
|
|
||
| is ConnectingEvent -> { | ||
| logger.i { "[handleEvent] event: ConnectingEvent" } | ||
| mutableClientState.setConnectionState(ConnectionState.Connecting) | ||
|
|
@@ -642,6 +650,7 @@ internal constructor( | |
| tokenManager.setTokenProvider(tokenProvider) | ||
| appSettingsManager.loadAppSettings() | ||
| warmUp() | ||
| messageReceiptReporter.start() | ||
| logger.i { "[initializeClientWithUser] user.id: '${user.id}'completed" } | ||
| } | ||
|
|
||
|
|
@@ -737,9 +746,11 @@ internal constructor( | |
| ): Call<ConnectionData> { | ||
| return CoroutineCall(clientScope) { | ||
| logger.d { "[switchUser] user.id: '${user.id}'" } | ||
| userScope.userId.value = user.id | ||
| notifications.deleteDevice() // always delete device if switching users | ||
| disconnectUserSuspend(flushPersistence = true) | ||
| // change userId only after disconnect, | ||
| // otherwise the userScope won't cancel coroutines related to the previous user. | ||
| userScope.userId.value = user.id | ||
| onDisconnectionComplete() | ||
| connectUserSuspend(user, tokenProvider, timeoutMilliseconds).also { | ||
| logger.v { "[switchUser] completed('${user.id}')" } | ||
|
|
@@ -1506,6 +1517,8 @@ internal constructor( | |
| userCredentialStorage.clear() | ||
| } | ||
|
|
||
| repository.clear() | ||
|
|
||
| _repositoryFacade = null | ||
| attachmentsSender.cancelJobs() | ||
| appSettingsManager.clear() | ||
|
|
@@ -2870,6 +2883,33 @@ internal constructor( | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Request to mark the message with the given id as delivered if: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be good to document that this method might attempt to internally call |
||
| * | ||
| * - Delivery receipts are enabled for the current user. | ||
| * - Delivery events are enabled in the channel config. | ||
| * | ||
| * and if all of the following conditions are met for the message: | ||
| * | ||
| * - Not sent by the current user. | ||
| * - Not shadow banned. | ||
| * - Not sent by a muted user. | ||
| * - Not yet marked as read by the current user. | ||
| * - Not yet marked as delivered by the current user. | ||
| * | ||
| * @param messageId The ID of the message to mark as delivered. | ||
| */ | ||
| @CheckResult | ||
| public fun markMessageAsDelivered(messageId: String): Call<Unit> = | ||
| CoroutineCall(userScope) { | ||
| messageReceiptManager.markMessageAsDelivered(messageId) | ||
| Result.Success(Unit) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we maybe return |
||
| }.doOnStart(userScope) { | ||
| logger.d { "[markMessageAsDelivered] #doOnStart; messageId: $messageId" } | ||
| }.doOnResult(userScope) { | ||
| logger.v { "[markMessageAsDelivered] #doOnResult; completed($messageId)" } | ||
| } | ||
|
|
||
| /** | ||
| * Marks the given message as read. | ||
| * | ||
|
|
@@ -4717,7 +4757,8 @@ internal constructor( | |
| appVersion = this.appVersion, | ||
| ) | ||
|
|
||
| val appSettingsManager = AppSettingManager(module.api()) | ||
| val api = module.api() | ||
| val appSettingsManager = AppSettingManager(api) | ||
|
|
||
| val audioPlayer: AudioPlayer = StreamMediaPlayer( | ||
| mediaPlayer = NativeMediaPlayerImpl { | ||
|
|
@@ -4732,12 +4773,15 @@ internal constructor( | |
| isMarshmallowOrHigher = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M, | ||
| ) | ||
|
|
||
| val database = ChatClientDatabase.build(appContext) | ||
| val repository = ChatClientRepository.from(database) | ||
|
|
||
| return ChatClient( | ||
| config, | ||
| module.api(), | ||
| module.dtoMapping, | ||
| module.notifications(), | ||
| tokenManager, | ||
| config = config, | ||
| api = api, | ||
| dtoMapping = module.dtoMapping, | ||
| notifications = module.notifications(), | ||
| tokenManager = tokenManager, | ||
| userCredentialStorage = userCredentialStorage ?: SharedPreferencesCredentialStorage(appContext), | ||
| userStateService = module.userStateService, | ||
| clientDebugger = clientDebugger ?: StubChatClientDebugger, | ||
|
|
@@ -4755,6 +4799,18 @@ internal constructor( | |
| mutableClientState = MutableClientState(module.networkStateProvider), | ||
| currentUserFetcher = module.currentUserFetcher, | ||
| audioPlayer = audioPlayer, | ||
| repository = repository, | ||
| messageReceiptReporter = MessageReceiptReporter( | ||
| scope = userScope, | ||
| messageReceiptRepository = repository, | ||
| api = api, | ||
| ), | ||
| messageReceiptManager = MessageReceiptManager( | ||
| now = ::Date, | ||
| getRepositoryFacade = { instance().repositoryFacade }, | ||
| messageReceiptRepository = repository, | ||
| api = api, | ||
| ), | ||
| ).apply { | ||
| attachmentsSender = AttachmentsSender( | ||
| context = appContext, | ||
|
|
@@ -4799,7 +4855,10 @@ internal constructor( | |
| * @see [Plugin] | ||
| * @see [PluginFactory] | ||
| */ | ||
| protected val pluginFactories: MutableList<PluginFactory> = mutableListOf(ThrottlingPluginFactory) | ||
| protected val pluginFactories: MutableList<PluginFactory> = mutableListOf( | ||
| ThrottlingPluginFactory, | ||
| MessageDeliveredPluginFactory, | ||
| ) | ||
|
|
||
| /** | ||
| * Create a [ChatClient] instance based on the current configuration | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also have a corresponding
stop()method? Or do we rely on cancelling the scope to cancel the running job?