porting notifications to chat engine
This commit is contained in:
parent
86ad2a8a32
commit
4d033230e4
|
@ -180,9 +180,8 @@ internal class FeatureModules internal constructor(
|
||||||
val profileModule by unsafeLazy { ProfileModule(chatEngineModule.engine, trackingModule.errorTracker) }
|
val profileModule by unsafeLazy { ProfileModule(chatEngineModule.engine, trackingModule.errorTracker) }
|
||||||
val notificationsModule by unsafeLazy {
|
val notificationsModule by unsafeLazy {
|
||||||
NotificationsModule(
|
NotificationsModule(
|
||||||
|
chatEngineModule.engine,
|
||||||
imageLoaderModule.iconLoader(),
|
imageLoaderModule.iconLoader(),
|
||||||
storeModule.value.roomStore(),
|
|
||||||
storeModule.value.overviewStore(),
|
|
||||||
context,
|
context,
|
||||||
intentFactory = coreAndroidModule.intentFactory(),
|
intentFactory = coreAndroidModule.intentFactory(),
|
||||||
dispatchers = coroutineDispatchers,
|
dispatchers = coroutineDispatchers,
|
||||||
|
|
|
@ -13,6 +13,9 @@ interface ChatEngine : TaskRunner {
|
||||||
fun invites(): Flow<InviteState>
|
fun invites(): Flow<InviteState>
|
||||||
fun messages(roomId: RoomId, disableReadReceipts: Boolean): Flow<MessengerState>
|
fun messages(roomId: RoomId, disableReadReceipts: Boolean): Flow<MessengerState>
|
||||||
|
|
||||||
|
fun notificationsMessages(): Flow<UnreadNotifications>
|
||||||
|
fun notificationsInvites(): Flow<InviteNotification>
|
||||||
|
|
||||||
suspend fun login(request: LoginRequest): LoginResult
|
suspend fun login(request: LoginRequest): LoginResult
|
||||||
|
|
||||||
suspend fun me(forceRefresh: Boolean): Me
|
suspend fun me(forceRefresh: Boolean): Me
|
||||||
|
@ -63,3 +66,17 @@ interface PushHandler {
|
||||||
fun onNewToken(payload: JsonString)
|
fun onNewToken(payload: JsonString)
|
||||||
fun onMessageReceived(eventId: EventId?, roomId: RoomId?)
|
fun onMessageReceived(eventId: EventId?, roomId: RoomId?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typealias UnreadNotifications = Pair<Map<RoomOverview, List<RoomEvent>>, NotificationDiff>
|
||||||
|
|
||||||
|
data class NotificationDiff(
|
||||||
|
val unchanged: Map<RoomId, List<EventId>>,
|
||||||
|
val changedOrNew: Map<RoomId, List<EventId>>,
|
||||||
|
val removed: Map<RoomId, List<EventId>>,
|
||||||
|
val newRooms: Set<RoomId>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class InviteNotification(
|
||||||
|
val content: String,
|
||||||
|
val roomId: RoomId
|
||||||
|
)
|
|
@ -2,7 +2,7 @@ package fixture
|
||||||
|
|
||||||
import app.dapk.st.matrix.common.EventId
|
import app.dapk.st.matrix.common.EventId
|
||||||
import app.dapk.st.matrix.common.RoomId
|
import app.dapk.st.matrix.common.RoomId
|
||||||
import app.dapk.st.notifications.NotificationDiff
|
import app.dapk.st.engine.NotificationDiff
|
||||||
|
|
||||||
object NotificationDiffFixtures {
|
object NotificationDiffFixtures {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
applyAndroidLibraryModule(project)
|
applyAndroidLibraryModule(project)
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(":chat-engine")
|
||||||
implementation project(':domains:store')
|
implementation project(':domains:store')
|
||||||
implementation project(":domains:android:work")
|
implementation project(":domains:android:work")
|
||||||
implementation project(':domains:android:push')
|
implementation project(':domains:android:push')
|
||||||
|
@ -10,12 +11,13 @@ dependencies {
|
||||||
implementation project(":features:messenger")
|
implementation project(":features:messenger")
|
||||||
implementation project(":features:navigator")
|
implementation project(":features:navigator")
|
||||||
|
|
||||||
|
|
||||||
implementation Dependencies.mavenCentral.kotlinSerializationJson
|
implementation Dependencies.mavenCentral.kotlinSerializationJson
|
||||||
|
|
||||||
kotlinTest(it)
|
kotlinTest(it)
|
||||||
|
|
||||||
androidImportFixturesWorkaround(project, project(":core"))
|
androidImportFixturesWorkaround(project, project(":core"))
|
||||||
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
androidImportFixturesWorkaround(project, project(":matrix:common"))
|
||||||
androidImportFixturesWorkaround(project, project(":matrix:services:sync"))
|
androidImportFixturesWorkaround(project, project(":chat-engine"))
|
||||||
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
androidImportFixturesWorkaround(project, project(":domains:android:stub"))
|
||||||
}
|
}
|
|
@ -4,9 +4,9 @@ import android.app.Notification
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.dapk.st.core.DeviceMeta
|
import app.dapk.st.core.DeviceMeta
|
||||||
import app.dapk.st.core.whenPOrHigher
|
import app.dapk.st.core.whenPOrHigher
|
||||||
|
import app.dapk.st.engine.RoomOverview
|
||||||
import app.dapk.st.imageloader.IconLoader
|
import app.dapk.st.imageloader.IconLoader
|
||||||
import app.dapk.st.matrix.common.RoomId
|
import app.dapk.st.matrix.common.RoomId
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
|
||||||
import app.dapk.st.navigator.IntentFactory
|
import app.dapk.st.navigator.IntentFactory
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ class NotificationFactory(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createInvite(inviteNotification: InviteNotification): AndroidNotification {
|
fun createInvite(inviteNotification: app.dapk.st.engine.InviteNotification): AndroidNotification {
|
||||||
val openAppIntent = intentFactory.notificationOpenApp(context)
|
val openAppIntent = intentFactory.notificationOpenApp(context)
|
||||||
return AndroidNotification(
|
return AndroidNotification(
|
||||||
channelId = INVITE_CHANNEL_ID,
|
channelId = INVITE_CHANNEL_ID,
|
||||||
|
|
|
@ -10,7 +10,7 @@ class NotificationInviteRenderer(
|
||||||
private val androidNotificationBuilder: AndroidNotificationBuilder,
|
private val androidNotificationBuilder: AndroidNotificationBuilder,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun render(inviteNotification: InviteNotification) {
|
fun render(inviteNotification: app.dapk.st.engine.InviteNotification) {
|
||||||
notificationManager.notify(
|
notificationManager.notify(
|
||||||
inviteNotification.roomId.value,
|
inviteNotification.roomId.value,
|
||||||
INVITE_NOTIFICATION_ID,
|
INVITE_NOTIFICATION_ID,
|
||||||
|
@ -18,7 +18,7 @@ class NotificationInviteRenderer(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun InviteNotification.toAndroidNotification() = androidNotificationBuilder.build(
|
private fun app.dapk.st.engine.InviteNotification.toAndroidNotification() = androidNotificationBuilder.build(
|
||||||
notificationFactory.createInvite(this)
|
notificationFactory.createInvite(this)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ import app.dapk.st.core.AppLogTag
|
||||||
import app.dapk.st.core.CoroutineDispatchers
|
import app.dapk.st.core.CoroutineDispatchers
|
||||||
import app.dapk.st.core.extensions.ifNull
|
import app.dapk.st.core.extensions.ifNull
|
||||||
import app.dapk.st.core.log
|
import app.dapk.st.core.log
|
||||||
|
import app.dapk.st.engine.RoomEvent
|
||||||
|
import app.dapk.st.engine.RoomOverview
|
||||||
import app.dapk.st.matrix.common.RoomId
|
import app.dapk.st.matrix.common.RoomId
|
||||||
import app.dapk.st.matrix.sync.RoomEvent
|
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
private const val SUMMARY_NOTIFICATION_ID = 101
|
private const val SUMMARY_NOTIFICATION_ID = 101
|
||||||
|
|
|
@ -3,8 +3,8 @@ package app.dapk.st.notifications
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import app.dapk.st.core.DeviceMeta
|
import app.dapk.st.core.DeviceMeta
|
||||||
import app.dapk.st.core.whenPOrHigher
|
import app.dapk.st.core.whenPOrHigher
|
||||||
|
import app.dapk.st.engine.RoomOverview
|
||||||
import app.dapk.st.imageloader.IconLoader
|
import app.dapk.st.imageloader.IconLoader
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
|
||||||
import app.dapk.st.notifications.AndroidNotificationStyle.Inbox
|
import app.dapk.st.notifications.AndroidNotificationStyle.Inbox
|
||||||
import app.dapk.st.notifications.AndroidNotificationStyle.Messaging
|
import app.dapk.st.notifications.AndroidNotificationStyle.Messaging
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,14 @@ import android.content.Context
|
||||||
import app.dapk.st.core.CoroutineDispatchers
|
import app.dapk.st.core.CoroutineDispatchers
|
||||||
import app.dapk.st.core.DeviceMeta
|
import app.dapk.st.core.DeviceMeta
|
||||||
import app.dapk.st.core.ProvidableModule
|
import app.dapk.st.core.ProvidableModule
|
||||||
|
import app.dapk.st.engine.ChatEngine
|
||||||
import app.dapk.st.imageloader.IconLoader
|
import app.dapk.st.imageloader.IconLoader
|
||||||
import app.dapk.st.matrix.sync.OverviewStore
|
|
||||||
import app.dapk.st.matrix.sync.RoomStore
|
|
||||||
import app.dapk.st.navigator.IntentFactory
|
import app.dapk.st.navigator.IntentFactory
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
|
|
||||||
class NotificationsModule(
|
class NotificationsModule(
|
||||||
|
private val chatEngine: ChatEngine,
|
||||||
private val iconLoader: IconLoader,
|
private val iconLoader: IconLoader,
|
||||||
private val roomStore: RoomStore,
|
|
||||||
private val overviewStore: OverviewStore,
|
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val intentFactory: IntentFactory,
|
private val intentFactory: IntentFactory,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
|
@ -40,10 +38,9 @@ class NotificationsModule(
|
||||||
)
|
)
|
||||||
return RenderNotificationsUseCase(
|
return RenderNotificationsUseCase(
|
||||||
notificationRenderer = notificationMessageRenderer,
|
notificationRenderer = notificationMessageRenderer,
|
||||||
observeRenderableUnreadEventsUseCase = ObserveUnreadNotificationsUseCaseImpl(roomStore),
|
|
||||||
notificationChannels = NotificationChannels(notificationManager),
|
notificationChannels = NotificationChannels(notificationManager),
|
||||||
observeInviteNotificationsUseCase = ObserveInviteNotificationsUseCaseImpl(overviewStore),
|
inviteRenderer = NotificationInviteRenderer(notificationManager, notificationFactory, androidNotificationBuilder),
|
||||||
inviteRenderer = NotificationInviteRenderer(notificationManager, notificationFactory, androidNotificationBuilder)
|
chatEngine = chatEngine,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package app.dapk.st.notifications
|
package app.dapk.st.notifications
|
||||||
|
|
||||||
import app.dapk.st.matrix.sync.RoomEvent
|
import app.dapk.st.engine.ChatEngine
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
import app.dapk.st.engine.NotificationDiff
|
||||||
|
import app.dapk.st.engine.RoomEvent
|
||||||
|
import app.dapk.st.engine.RoomOverview
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
@ -9,18 +11,17 @@ import kotlinx.coroutines.flow.onEach
|
||||||
class RenderNotificationsUseCase(
|
class RenderNotificationsUseCase(
|
||||||
private val notificationRenderer: NotificationMessageRenderer,
|
private val notificationRenderer: NotificationMessageRenderer,
|
||||||
private val inviteRenderer: NotificationInviteRenderer,
|
private val inviteRenderer: NotificationInviteRenderer,
|
||||||
private val observeRenderableUnreadEventsUseCase: ObserveUnreadNotificationsUseCase,
|
private val chatEngine: ChatEngine,
|
||||||
private val observeInviteNotificationsUseCase: ObserveInviteNotificationsUseCase,
|
|
||||||
private val notificationChannels: NotificationChannels,
|
private val notificationChannels: NotificationChannels,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun listenForNotificationChanges(scope: CoroutineScope) {
|
suspend fun listenForNotificationChanges(scope: CoroutineScope) {
|
||||||
notificationChannels.initChannels()
|
notificationChannels.initChannels()
|
||||||
observeRenderableUnreadEventsUseCase()
|
chatEngine.notificationsMessages()
|
||||||
.onEach { (each, diff) -> renderUnreadChange(each, diff) }
|
.onEach { (each, diff) -> renderUnreadChange(each, diff) }
|
||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
|
|
||||||
observeInviteNotificationsUseCase()
|
chatEngine.notificationsInvites()
|
||||||
.onEach { inviteRenderer.render(it) }
|
.onEach { inviteRenderer.render(it) }
|
||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package app.dapk.st.notifications
|
package app.dapk.st.notifications
|
||||||
|
|
||||||
|
import app.dapk.st.engine.RoomEvent
|
||||||
import app.dapk.st.matrix.common.RoomMember
|
import app.dapk.st.matrix.common.RoomMember
|
||||||
import app.dapk.st.matrix.sync.RoomEvent
|
|
||||||
|
|
||||||
class RoomEventsToNotifiableMapper {
|
class RoomEventsToNotifiableMapper {
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@ class NotificationFactoryTest {
|
||||||
fakeIntentFactory.givenNotificationOpenApp(fakeContext.instance).returns(AN_OPEN_APP_INTENT)
|
fakeIntentFactory.givenNotificationOpenApp(fakeContext.instance).returns(AN_OPEN_APP_INTENT)
|
||||||
val content = "Content message"
|
val content = "Content message"
|
||||||
val result = notificationFactory.createInvite(
|
val result = notificationFactory.createInvite(
|
||||||
InviteNotification(
|
app.dapk.st.engine.InviteNotification(
|
||||||
content = content,
|
content = content,
|
||||||
A_ROOM_ID,
|
A_ROOM_ID,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package app.dapk.st.notifications
|
package app.dapk.st.notifications
|
||||||
|
|
||||||
|
import app.dapk.st.engine.UnreadNotifications
|
||||||
import fake.*
|
import fake.*
|
||||||
import fixture.NotificationDiffFixtures.aNotificationDiff
|
import fixture.NotificationDiffFixtures.aNotificationDiff
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
@ -19,12 +20,12 @@ class RenderNotificationsUseCaseTest {
|
||||||
private val fakeNotificationChannels = FakeNotificationChannels().also {
|
private val fakeNotificationChannels = FakeNotificationChannels().also {
|
||||||
it.instance.expect { it.initChannels() }
|
it.instance.expect { it.initChannels() }
|
||||||
}
|
}
|
||||||
|
private val fakeChatEngine = FakeChatEngine()
|
||||||
|
|
||||||
private val renderNotificationsUseCase = RenderNotificationsUseCase(
|
private val renderNotificationsUseCase = RenderNotificationsUseCase(
|
||||||
fakeNotificationMessageRenderer.instance,
|
fakeNotificationMessageRenderer.instance,
|
||||||
fakeNotificationInviteRenderer.instance,
|
fakeNotificationInviteRenderer.instance,
|
||||||
fakeObserveUnreadNotificationsUseCase,
|
fakeChatEngine,
|
||||||
fakeObserveInviteNotificationsUseCase,
|
|
||||||
fakeNotificationChannels.instance,
|
fakeNotificationChannels.instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,14 @@ package fake
|
||||||
|
|
||||||
import app.dapk.st.notifications.NotificationMessageRenderer
|
import app.dapk.st.notifications.NotificationMessageRenderer
|
||||||
import app.dapk.st.notifications.NotificationState
|
import app.dapk.st.notifications.NotificationState
|
||||||
import app.dapk.st.notifications.UnreadNotifications
|
import app.dapk.st.engine.UnreadNotifications
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
|
||||||
class FakeNotificationMessageRenderer {
|
class FakeNotificationMessageRenderer {
|
||||||
val instance = mockk<NotificationMessageRenderer>()
|
val instance = mockk<NotificationMessageRenderer>()
|
||||||
|
|
||||||
fun verifyRenders(vararg unreadNotifications: UnreadNotifications) {
|
fun verifyRenders(vararg unreadNotifications: app.dapk.st.engine.UnreadNotifications) {
|
||||||
unreadNotifications.forEach { unread ->
|
unreadNotifications.forEach { unread ->
|
||||||
coVerify {
|
coVerify {
|
||||||
instance.render(
|
instance.render(
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package fake
|
package fake
|
||||||
|
|
||||||
import app.dapk.st.notifications.ObserveInviteNotificationsUseCase
|
import app.dapk.st.engine.ObserveInviteNotificationsUseCase
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import test.delegateEmit
|
import test.delegateEmit
|
||||||
|
|
||||||
class FakeObserveInviteNotificationsUseCase : ObserveInviteNotificationsUseCase by mockk() {
|
class FakeObserveInviteNotificationsUseCase : app.dapk.st.engine.ObserveInviteNotificationsUseCase by mockk() {
|
||||||
fun given() = coEvery { this@FakeObserveInviteNotificationsUseCase.invoke() }.delegateEmit()
|
fun given() = coEvery { this@FakeObserveInviteNotificationsUseCase.invoke() }.delegateEmit()
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package fake
|
package fake
|
||||||
|
|
||||||
import app.dapk.st.notifications.ObserveUnreadNotificationsUseCase
|
import app.dapk.st.engine.ObserveUnreadNotificationsUseCase
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import test.delegateEmit
|
import test.delegateEmit
|
||||||
|
|
||||||
class FakeObserveUnreadNotificationsUseCase : ObserveUnreadNotificationsUseCase by mockk() {
|
class FakeObserveUnreadNotificationsUseCase : app.dapk.st.engine.ObserveUnreadNotificationsUseCase by mockk() {
|
||||||
fun given() = coEvery { this@FakeObserveUnreadNotificationsUseCase.invoke() }.delegateEmit()
|
fun given() = coEvery { this@FakeObserveUnreadNotificationsUseCase.invoke() }.delegateEmit()
|
||||||
}
|
}
|
|
@ -41,6 +41,8 @@ class MatrixEngine internal constructor(
|
||||||
private val matrixMediaDecrypter: Lazy<MatrixMediaDecrypter>,
|
private val matrixMediaDecrypter: Lazy<MatrixMediaDecrypter>,
|
||||||
private val matrixPushHandler: Lazy<MatrixPushHandler>,
|
private val matrixPushHandler: Lazy<MatrixPushHandler>,
|
||||||
private val inviteUseCase: Lazy<InviteUseCase>,
|
private val inviteUseCase: Lazy<InviteUseCase>,
|
||||||
|
private val notificationMessagesUseCase: Lazy<ObserveUnreadNotificationsUseCase>,
|
||||||
|
private val notificationInvitesUseCase: Lazy<ObserveInviteNotificationsUseCase>,
|
||||||
) : ChatEngine {
|
) : ChatEngine {
|
||||||
|
|
||||||
override fun directory() = directoryUseCase.value.state()
|
override fun directory() = directoryUseCase.value.state()
|
||||||
|
@ -50,6 +52,14 @@ class MatrixEngine internal constructor(
|
||||||
return timelineUseCase.value.fetch(roomId, isReadReceiptsDisabled = disableReadReceipts)
|
return timelineUseCase.value.fetch(roomId, isReadReceiptsDisabled = disableReadReceipts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun notificationsMessages(): Flow<UnreadNotifications> {
|
||||||
|
return notificationMessagesUseCase.value.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun notificationsInvites(): Flow<InviteNotification> {
|
||||||
|
return notificationInvitesUseCase.value.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun login(request: LoginRequest): LoginResult {
|
override suspend fun login(request: LoginRequest): LoginResult {
|
||||||
return matrix.value.authService().login(request.engine()).engine()
|
return matrix.value.authService().login(request.engine()).engine()
|
||||||
}
|
}
|
||||||
|
@ -190,6 +200,8 @@ class MatrixEngine internal constructor(
|
||||||
mediaDecrypter,
|
mediaDecrypter,
|
||||||
pushHandler,
|
pushHandler,
|
||||||
invitesUseCase,
|
invitesUseCase,
|
||||||
|
unsafeLazy { ObserveUnreadNotificationsUseCaseImpl(roomStore) },
|
||||||
|
unsafeLazy { ObserveInviteNotificationsUseCaseImpl(overviewStore) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
package app.dapk.st.notifications
|
package app.dapk.st.engine
|
||||||
|
|
||||||
import app.dapk.st.matrix.common.RoomId
|
|
||||||
import app.dapk.st.matrix.sync.InviteMeta
|
import app.dapk.st.matrix.sync.InviteMeta
|
||||||
import app.dapk.st.matrix.sync.OverviewStore
|
import app.dapk.st.matrix.sync.OverviewStore
|
||||||
import app.dapk.st.matrix.sync.RoomInvite
|
import app.dapk.st.matrix.sync.RoomInvite
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
internal typealias ObserveInviteNotificationsUseCase = suspend () -> Flow<InviteNotification>
|
internal typealias ObserveInviteNotificationsUseCase = () -> Flow<InviteNotification>
|
||||||
|
|
||||||
class ObserveInviteNotificationsUseCaseImpl(private val overviewStore: OverviewStore) : ObserveInviteNotificationsUseCase {
|
class ObserveInviteNotificationsUseCaseImpl(private val overviewStore: OverviewStore) : ObserveInviteNotificationsUseCase {
|
||||||
|
|
||||||
override suspend fun invoke(): Flow<InviteNotification> {
|
override fun invoke(): Flow<InviteNotification> {
|
||||||
return overviewStore.latestInvites()
|
return overviewStore.latestInvites()
|
||||||
.diff()
|
.diff()
|
||||||
.drop(1)
|
.drop(1)
|
||||||
|
@ -43,8 +42,3 @@ class ObserveInviteNotificationsUseCaseImpl(private val overviewStore: OverviewS
|
||||||
private fun <T> Flow<Set<T>>.flatten() = this.flatMapConcat { items ->
|
private fun <T> Flow<Set<T>>.flatten() = this.flatMapConcat { items ->
|
||||||
flow { items.forEach { this.emit(it) } }
|
flow { items.forEach { this.emit(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
data class InviteNotification(
|
|
||||||
val content: String,
|
|
||||||
val roomId: RoomId
|
|
||||||
)
|
|
|
@ -1,4 +1,4 @@
|
||||||
package app.dapk.st.notifications
|
package app.dapk.st.engine
|
||||||
|
|
||||||
import app.dapk.st.core.AppLogTag
|
import app.dapk.st.core.AppLogTag
|
||||||
import app.dapk.st.core.extensions.clearAndPutAll
|
import app.dapk.st.core.extensions.clearAndPutAll
|
||||||
|
@ -6,17 +6,16 @@ import app.dapk.st.core.extensions.containsKey
|
||||||
import app.dapk.st.core.log
|
import app.dapk.st.core.log
|
||||||
import app.dapk.st.matrix.common.EventId
|
import app.dapk.st.matrix.common.EventId
|
||||||
import app.dapk.st.matrix.common.RoomId
|
import app.dapk.st.matrix.common.RoomId
|
||||||
import app.dapk.st.matrix.sync.RoomEvent
|
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
|
||||||
import app.dapk.st.matrix.sync.RoomStore
|
import app.dapk.st.matrix.sync.RoomStore
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import app.dapk.st.matrix.sync.RoomEvent as MatrixRoomEvent
|
||||||
|
import app.dapk.st.matrix.sync.RoomOverview as MatrixRoomOverview
|
||||||
|
|
||||||
typealias UnreadNotifications = Pair<Map<RoomOverview, List<RoomEvent>>, NotificationDiff>
|
internal typealias ObserveUnreadNotificationsUseCase = () -> Flow<UnreadNotifications>
|
||||||
internal typealias ObserveUnreadNotificationsUseCase = suspend () -> Flow<UnreadNotifications>
|
|
||||||
|
|
||||||
class ObserveUnreadNotificationsUseCaseImpl(private val roomStore: RoomStore) : ObserveUnreadNotificationsUseCase {
|
class ObserveUnreadNotificationsUseCaseImpl(private val roomStore: RoomStore) : ObserveUnreadNotificationsUseCase {
|
||||||
|
|
||||||
override suspend fun invoke(): Flow<UnreadNotifications> {
|
override fun invoke(): Flow<UnreadNotifications> {
|
||||||
return roomStore.observeUnread()
|
return roomStore.observeUnread()
|
||||||
.mapWithDiff()
|
.mapWithDiff()
|
||||||
.avoidShowingPreviousNotificationsOnLaunch()
|
.avoidShowingPreviousNotificationsOnLaunch()
|
||||||
|
@ -25,28 +24,7 @@ class ObserveUnreadNotificationsUseCaseImpl(private val roomStore: RoomStore) :
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Flow<UnreadNotifications>.onlyRenderableChanges(): Flow<UnreadNotifications> {
|
private fun Flow<Map<MatrixRoomOverview, List<MatrixRoomEvent>>>.mapWithDiff(): Flow<Pair<Map<MatrixRoomOverview, List<MatrixRoomEvent>>, NotificationDiff>> {
|
||||||
val inferredCurrentNotifications = mutableMapOf<RoomId, List<RoomEvent>>()
|
|
||||||
return this
|
|
||||||
.filter { (_, diff) ->
|
|
||||||
when {
|
|
||||||
diff.changedOrNew.isEmpty() && diff.removed.isEmpty() -> {
|
|
||||||
log(AppLogTag.NOTIFICATION, "Ignoring unread change due to no renderable changes")
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
inferredCurrentNotifications.isEmpty() && diff.removed.isNotEmpty() -> {
|
|
||||||
log(AppLogTag.NOTIFICATION, "Ignoring unread change due to no currently showing messages and changes are all messages marked as read")
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onEach { (allUnread, _) -> inferredCurrentNotifications.clearAndPutAll(allUnread.mapKeys { it.key.roomId }) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Flow<Map<RoomOverview, List<RoomEvent>>>.mapWithDiff(): Flow<Pair<Map<RoomOverview, List<RoomEvent>>, NotificationDiff>> {
|
|
||||||
val previousUnreadEvents = mutableMapOf<RoomId, List<TimestampedEventId>>()
|
val previousUnreadEvents = mutableMapOf<RoomId, List<TimestampedEventId>>()
|
||||||
return this.map { each ->
|
return this.map { each ->
|
||||||
val allUnreadIds = each.toTimestampedIds()
|
val allUnreadIds = each.toTimestampedIds()
|
||||||
|
@ -83,19 +61,39 @@ private fun Map<RoomId, List<TimestampedEventId>>?.toLatestTimestamps() = this?.
|
||||||
|
|
||||||
private fun Map<RoomId, List<TimestampedEventId>>.toEventIds() = this.mapValues { it.value.map { it.first } }
|
private fun Map<RoomId, List<TimestampedEventId>>.toEventIds() = this.mapValues { it.value.map { it.first } }
|
||||||
|
|
||||||
private fun Map<RoomOverview, List<RoomEvent>>.toTimestampedIds() = this
|
private fun Map<MatrixRoomOverview, List<MatrixRoomEvent>>.toTimestampedIds() = this
|
||||||
.mapValues { it.value.toEventIds() }
|
.mapValues { it.value.toEventIds() }
|
||||||
.mapKeys { it.key.roomId }
|
.mapKeys { it.key.roomId }
|
||||||
|
|
||||||
private fun List<RoomEvent>.toEventIds() = this.map { it.eventId to it.utcTimestamp }
|
private fun List<MatrixRoomEvent>.toEventIds() = this.map { it.eventId to it.utcTimestamp }
|
||||||
|
|
||||||
private fun <T> Flow<T>.avoidShowingPreviousNotificationsOnLaunch() = drop(1)
|
private fun <T> Flow<T>.avoidShowingPreviousNotificationsOnLaunch() = drop(1)
|
||||||
|
|
||||||
data class NotificationDiff(
|
private fun Flow<Pair<Map<MatrixRoomOverview, List<MatrixRoomEvent>>, NotificationDiff>>.onlyRenderableChanges(): Flow<UnreadNotifications> {
|
||||||
val unchanged: Map<RoomId, List<EventId>>,
|
val inferredCurrentNotifications = mutableMapOf<RoomId, List<MatrixRoomEvent>>()
|
||||||
val changedOrNew: Map<RoomId, List<EventId>>,
|
return this
|
||||||
val removed: Map<RoomId, List<EventId>>,
|
.filter { (_, diff) ->
|
||||||
val newRooms: Set<RoomId>
|
when {
|
||||||
)
|
diff.changedOrNew.isEmpty() && diff.removed.isEmpty() -> {
|
||||||
|
log(AppLogTag.NOTIFICATION, "Ignoring unread change due to no renderable changes")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
inferredCurrentNotifications.isEmpty() && diff.removed.isNotEmpty() -> {
|
||||||
|
log(AppLogTag.NOTIFICATION, "Ignoring unread change due to no currently showing messages and changes are all messages marked as read")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEach { (allUnread, _) -> inferredCurrentNotifications.clearAndPutAll(allUnread.mapKeys { it.key.roomId }) }
|
||||||
|
.map {
|
||||||
|
val engineModels = it.first
|
||||||
|
.mapKeys { it.key.engine() }
|
||||||
|
.mapValues { it.value.map { it.engine() } }
|
||||||
|
engineModels to it.second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
typealias TimestampedEventId = Pair<EventId, Long>
|
typealias TimestampedEventId = Pair<EventId, Long>
|
|
@ -1,24 +1,27 @@
|
||||||
package app.dapk.st.notifications
|
package app.dapk.st.engine
|
||||||
|
|
||||||
import app.dapk.st.matrix.sync.RoomEvent
|
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
|
||||||
import fake.FakeRoomStore
|
import fake.FakeRoomStore
|
||||||
import fixture.NotificationDiffFixtures.aNotificationDiff
|
import fixture.NotificationDiffFixtures.aNotificationDiff
|
||||||
|
import fixture.aMatrixRoomOverview
|
||||||
import fixture.aRoomId
|
import fixture.aRoomId
|
||||||
import fixture.aRoomMessageEvent
|
import fixture.aRoomMessageEvent
|
||||||
import fixture.aRoomOverview
|
|
||||||
import fixture.anEventId
|
import fixture.anEventId
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import app.dapk.st.matrix.sync.RoomEvent as MatrixRoomEvent
|
||||||
|
import app.dapk.st.matrix.sync.RoomOverview as MatrixRoomOverview
|
||||||
|
|
||||||
private val NO_UNREADS = emptyMap<RoomOverview, List<RoomEvent>>()
|
private val NO_UNREADS = emptyMap<MatrixRoomOverview, List<MatrixRoomEvent>>()
|
||||||
private val A_MESSAGE = aRoomMessageEvent(eventId = anEventId("1"), content = "hello", utcTimestamp = 1000)
|
private val A_MESSAGE = aRoomMessageEvent(eventId = anEventId("1"), content = "hello", utcTimestamp = 1000)
|
||||||
private val A_MESSAGE_2 = aRoomMessageEvent(eventId = anEventId("2"), content = "world", utcTimestamp = 2000)
|
private val A_MESSAGE_2 = aRoomMessageEvent(eventId = anEventId("2"), content = "world", utcTimestamp = 2000)
|
||||||
private val A_ROOM_OVERVIEW = aRoomOverview(roomId = aRoomId("1"))
|
private val A_ROOM_OVERVIEW = aMatrixRoomOverview(roomId = aRoomId("1"))
|
||||||
private val A_ROOM_OVERVIEW_2 = aRoomOverview(roomId = aRoomId("2"))
|
private val A_ROOM_OVERVIEW_2 = aMatrixRoomOverview(roomId = aRoomId("2"))
|
||||||
|
|
||||||
|
private fun MatrixRoomOverview.withUnreads(vararg events: MatrixRoomEvent) = mapOf(this to events.toList())
|
||||||
|
private fun MatrixRoomOverview.toDiff(vararg events: MatrixRoomEvent) = mapOf(this.roomId to events.map { it.eventId })
|
||||||
|
|
||||||
class ObserveUnreadRenderNotificationsUseCaseTest {
|
class ObserveUnreadRenderNotificationsUseCaseTest {
|
||||||
|
|
||||||
|
@ -33,7 +36,7 @@ class ObserveUnreadRenderNotificationsUseCaseTest {
|
||||||
val result = useCase.invoke().toList()
|
val result = useCase.invoke().toList()
|
||||||
|
|
||||||
result shouldBeEqualTo listOf(
|
result shouldBeEqualTo listOf(
|
||||||
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE) to aNotificationDiff(
|
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE).engine() to aNotificationDiff(
|
||||||
changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE),
|
changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE),
|
||||||
newRooms = setOf(A_ROOM_OVERVIEW.roomId)
|
newRooms = setOf(A_ROOM_OVERVIEW.roomId)
|
||||||
)
|
)
|
||||||
|
@ -47,11 +50,11 @@ class ObserveUnreadRenderNotificationsUseCaseTest {
|
||||||
val result = useCase.invoke().toList()
|
val result = useCase.invoke().toList()
|
||||||
|
|
||||||
result shouldBeEqualTo listOf(
|
result shouldBeEqualTo listOf(
|
||||||
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE) to aNotificationDiff(
|
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE).engine() to aNotificationDiff(
|
||||||
changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE),
|
changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE),
|
||||||
newRooms = setOf(A_ROOM_OVERVIEW.roomId)
|
newRooms = setOf(A_ROOM_OVERVIEW.roomId)
|
||||||
),
|
),
|
||||||
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE, A_MESSAGE_2) to aNotificationDiff(changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE_2))
|
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE, A_MESSAGE_2).engine() to aNotificationDiff(changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE_2))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +67,7 @@ class ObserveUnreadRenderNotificationsUseCaseTest {
|
||||||
val result = useCase.invoke().toList()
|
val result = useCase.invoke().toList()
|
||||||
|
|
||||||
result shouldBeEqualTo listOf(
|
result shouldBeEqualTo listOf(
|
||||||
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE, A_MESSAGE_2) to aNotificationDiff(changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE_2))
|
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE, A_MESSAGE_2).engine() to aNotificationDiff(changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE_2))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +95,7 @@ class ObserveUnreadRenderNotificationsUseCaseTest {
|
||||||
val result = useCase.invoke().toList()
|
val result = useCase.invoke().toList()
|
||||||
|
|
||||||
result shouldBeEqualTo listOf(
|
result shouldBeEqualTo listOf(
|
||||||
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE) to aNotificationDiff(
|
A_ROOM_OVERVIEW.withUnreads(A_MESSAGE).engine() to aNotificationDiff(
|
||||||
changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE),
|
changedOrNew = A_ROOM_OVERVIEW.toDiff(A_MESSAGE),
|
||||||
newRooms = setOf(A_ROOM_OVERVIEW.roomId)
|
newRooms = setOf(A_ROOM_OVERVIEW.roomId)
|
||||||
),
|
),
|
||||||
|
@ -110,8 +113,10 @@ class ObserveUnreadRenderNotificationsUseCaseTest {
|
||||||
result shouldBeEqualTo emptyList()
|
result shouldBeEqualTo emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun givenNoInitialUnreads(vararg unreads: Map<RoomOverview, List<RoomEvent>>) = fakeRoomStore.givenUnreadEvents(flowOf(NO_UNREADS, *unreads))
|
private fun givenNoInitialUnreads(vararg unreads: Map<MatrixRoomOverview, List<MatrixRoomEvent>>) =
|
||||||
|
fakeRoomStore.givenUnreadEvents(flowOf(NO_UNREADS, *unreads))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RoomOverview.withUnreads(vararg events: RoomEvent) = mapOf(this to events.toList())
|
private fun Map<MatrixRoomOverview, List<MatrixRoomEvent>>.engine() = this
|
||||||
private fun RoomOverview.toDiff(vararg events: RoomEvent) = mapOf(this.roomId to events.map { it.eventId })
|
.mapKeys { it.key.engine() }
|
||||||
|
.mapValues { it.value.map { it.engine() } }
|
|
@ -7,7 +7,7 @@ import app.dapk.st.matrix.common.RoomMember
|
||||||
import app.dapk.st.matrix.sync.LastMessage
|
import app.dapk.st.matrix.sync.LastMessage
|
||||||
import app.dapk.st.matrix.sync.RoomOverview
|
import app.dapk.st.matrix.sync.RoomOverview
|
||||||
|
|
||||||
fun aRoomOverview(
|
fun aMatrixRoomOverview(
|
||||||
roomId: RoomId = aRoomId(),
|
roomId: RoomId = aRoomId(),
|
||||||
roomCreationUtc: Long = 0L,
|
roomCreationUtc: Long = 0L,
|
||||||
roomName: String? = null,
|
roomName: String? = null,
|
||||||
|
|
|
@ -5,6 +5,6 @@ import app.dapk.st.matrix.sync.RoomOverview
|
||||||
import app.dapk.st.matrix.sync.RoomState
|
import app.dapk.st.matrix.sync.RoomState
|
||||||
|
|
||||||
fun aRoomState(
|
fun aRoomState(
|
||||||
roomOverview: RoomOverview = aRoomOverview(),
|
roomOverview: RoomOverview = aMatrixRoomOverview(),
|
||||||
events: List<RoomEvent> = listOf(aRoomMessageEvent()),
|
events: List<RoomEvent> = listOf(aRoomMessageEvent()),
|
||||||
) = RoomState(roomOverview, events)
|
) = RoomState(roomOverview, events)
|
Loading…
Reference in New Issue