mirror of
https://github.com/ouchadam/small-talk.git
synced 2025-02-16 12:10:45 +01:00
abstracting the firebase push service to the push module
This commit is contained in:
parent
7fe3cfa240
commit
b69f2c69c0
@ -16,8 +16,9 @@ import app.dapk.st.home.HomeModule
|
||||
import app.dapk.st.login.LoginModule
|
||||
import app.dapk.st.messenger.MessengerModule
|
||||
import app.dapk.st.notifications.NotificationsModule
|
||||
import app.dapk.st.notifications.PushAndroidService
|
||||
import app.dapk.st.profile.ProfileModule
|
||||
import app.dapk.st.push.FirebasePushService
|
||||
import app.dapk.st.push.PushModule
|
||||
import app.dapk.st.settings.SettingsModule
|
||||
import app.dapk.st.share.ShareEntryModule
|
||||
import app.dapk.st.work.TaskRunnerModule
|
||||
@ -73,6 +74,7 @@ class SmallTalkApplication : Application(), ModuleProvider {
|
||||
SettingsModule::class -> featureModules.settingsModule
|
||||
ProfileModule::class -> featureModules.profileModule
|
||||
NotificationsModule::class -> featureModules.notificationsModule
|
||||
PushModule::class -> featureModules.pushModule
|
||||
MessengerModule::class -> featureModules.messengerModule
|
||||
TaskRunnerModule::class -> appModule.domainModules.taskRunnerModule
|
||||
CoreAndroidModule::class -> appModule.coreAndroidModule
|
||||
@ -85,7 +87,7 @@ class SmallTalkApplication : Application(), ModuleProvider {
|
||||
featureModules.notificationsModule.firebasePushTokenUseCase().unregister()
|
||||
appModule.coroutineDispatchers.io.cancel()
|
||||
applicationScope.cancel()
|
||||
stopService(Intent(this, PushAndroidService::class.java))
|
||||
stopService(Intent(this, FirebasePushService::class.java))
|
||||
lazyAppModule.reset()
|
||||
lazyFeatureModules.reset()
|
||||
|
||||
|
@ -48,6 +48,7 @@ import app.dapk.st.messenger.MessengerActivity
|
||||
import app.dapk.st.messenger.MessengerModule
|
||||
import app.dapk.st.navigator.IntentFactory
|
||||
import app.dapk.st.navigator.MessageAttachment
|
||||
import app.dapk.st.notifications.MatrixPushHandler
|
||||
import app.dapk.st.notifications.NotificationsModule
|
||||
import app.dapk.st.olm.DeviceKeyFactory
|
||||
import app.dapk.st.olm.OlmPersistenceWrapper
|
||||
@ -91,7 +92,7 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
|
||||
private val imageLoaderModule = ImageLoaderModule(context)
|
||||
|
||||
private val matrixModules = MatrixModules(storeModule, trackingModule, workModule, logger, coroutineDispatchers, context.contentResolver)
|
||||
val domainModules = DomainModules(matrixModules, trackingModule.errorTracker)
|
||||
val domainModules = DomainModules(matrixModules, trackingModule.errorTracker, workModule, storeModule)
|
||||
|
||||
val coreAndroidModule = CoreAndroidModule(intentFactory = object : IntentFactory {
|
||||
override fun notificationOpenApp(context: Context) = PendingIntent.getActivity(
|
||||
@ -125,7 +126,6 @@ internal class AppModule(context: Application, logger: MatrixLogger) {
|
||||
matrixModules,
|
||||
domainModules,
|
||||
trackingModule,
|
||||
workModule,
|
||||
coreAndroidModule,
|
||||
imageLoaderModule,
|
||||
context,
|
||||
@ -140,7 +140,6 @@ internal class FeatureModules internal constructor(
|
||||
private val matrixModules: MatrixModules,
|
||||
private val domainModules: DomainModules,
|
||||
private val trackingModule: TrackingModule,
|
||||
private val workModule: WorkModule,
|
||||
private val coreAndroidModule: CoreAndroidModule,
|
||||
imageLoaderModule: ImageLoaderModule,
|
||||
context: Context,
|
||||
@ -191,14 +190,10 @@ internal class FeatureModules internal constructor(
|
||||
val profileModule by unsafeLazy { ProfileModule(matrixModules.profile, matrixModules.sync, matrixModules.room, trackingModule.errorTracker) }
|
||||
val notificationsModule by unsafeLazy {
|
||||
NotificationsModule(
|
||||
matrixModules.push,
|
||||
matrixModules.sync,
|
||||
storeModule.value.credentialsStore(),
|
||||
domainModules.pushModule.registerFirebasePushTokenUseCase(),
|
||||
imageLoaderModule.iconLoader(),
|
||||
storeModule.value.roomStore(),
|
||||
context,
|
||||
workModule.workScheduler(),
|
||||
intentFactory = coreAndroidModule.intentFactory(),
|
||||
dispatchers = coroutineDispatchers,
|
||||
deviceMeta = DeviceMeta(Build.VERSION.SDK_INT)
|
||||
@ -209,6 +204,10 @@ internal class FeatureModules internal constructor(
|
||||
ShareEntryModule(matrixModules.sync, matrixModules.room)
|
||||
}
|
||||
|
||||
val pushModule by unsafeLazy {
|
||||
domainModules.pushModule
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class MatrixModules(
|
||||
@ -423,9 +422,20 @@ internal class MatrixModules(
|
||||
internal class DomainModules(
|
||||
private val matrixModules: MatrixModules,
|
||||
private val errorTracker: ErrorTracker,
|
||||
private val workModule: WorkModule,
|
||||
private val storeModule: Lazy<StoreModule>,
|
||||
) {
|
||||
|
||||
val pushModule by unsafeLazy { PushModule(matrixModules.push, errorTracker) }
|
||||
val pushModule by unsafeLazy {
|
||||
val store = storeModule.value
|
||||
val pushHandler = MatrixPushHandler(
|
||||
workScheduler = workModule.workScheduler(),
|
||||
credentialsStore = store.credentialsStore(),
|
||||
matrixModules.sync,
|
||||
store.roomStore(),
|
||||
)
|
||||
PushModule(matrixModules.push, errorTracker, pushHandler)
|
||||
}
|
||||
val taskRunnerModule by unsafeLazy { TaskRunnerModule(TaskRunnerAdapter(matrixModules.matrix::run, AppTaskRunner(matrixModules.push))) }
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ package app.dapk.st.core
|
||||
|
||||
import android.content.Context
|
||||
|
||||
inline fun <reified T : ProvidableModule> Context.module() =
|
||||
(this.applicationContext as ModuleProvider).provide(T::class)
|
||||
inline fun <reified T : ProvidableModule> Context.module() = (this.applicationContext as ModuleProvider).provide(T::class)
|
||||
|
||||
fun Context.resetModules() = (this.applicationContext as ModuleProvider).reset()
|
@ -2,6 +2,7 @@ applyAndroidLibraryModule(project)
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':domains:android:core')
|
||||
implementation project(':matrix:services:push')
|
||||
implementation platform('com.google.firebase:firebase-bom:29.0.3')
|
||||
implementation 'com.google.firebase:firebase-messaging'
|
||||
|
@ -1,2 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="app.dapk.st.push"/>
|
||||
<manifest package="app.dapk.st.push" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
|
||||
<service
|
||||
android:name=".FirebasePushService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:exported="true" android:enabled="true" android:name=".unifiedpush.CustomR">
|
||||
<intent-filter>
|
||||
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
|
||||
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
|
||||
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT"/>
|
||||
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED"/>
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,23 @@
|
||||
package app.dapk.st.push
|
||||
|
||||
import app.dapk.st.core.extensions.unsafeLazy
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
|
||||
class FirebasePushService : FirebaseMessagingService() {
|
||||
|
||||
private val handler by unsafeLazy { module<PushModule>().pushHandler() }
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
handler.onNewToken(token)
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
val eventId = message.data["event_id"]?.let { EventId(it) }
|
||||
val roomId = message.data["room_id"]?.let { RoomId(it) }
|
||||
handler.onMessageReceived(eventId, roomId)
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package app.dapk.st.push
|
||||
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
|
||||
interface PushHandler {
|
||||
fun onNewToken(token: String)
|
||||
fun onMessageReceived(eventId: EventId?, roomId: RoomId?)
|
||||
}
|
@ -1,16 +1,20 @@
|
||||
package app.dapk.st.push
|
||||
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.matrix.push.PushService
|
||||
|
||||
class PushModule(
|
||||
private val pushService: PushService,
|
||||
private val errorTracker: ErrorTracker,
|
||||
) {
|
||||
private val pushHandler: PushHandler,
|
||||
) : ProvidableModule {
|
||||
|
||||
fun registerFirebasePushTokenUseCase() = RegisterFirebasePushTokenUseCase(
|
||||
pushService,
|
||||
errorTracker,
|
||||
)
|
||||
|
||||
fun pushHandler() = pushHandler
|
||||
|
||||
}
|
@ -1,14 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.dapk.st.notifications">
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".PushAndroidService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
<manifest package="app.dapk.st.notifications"/>
|
@ -0,0 +1,83 @@
|
||||
package app.dapk.st.notifications
|
||||
|
||||
import app.dapk.st.core.AppLogTag
|
||||
import app.dapk.st.core.log
|
||||
import app.dapk.st.matrix.common.CredentialsStore
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.matrix.sync.RoomStore
|
||||
import app.dapk.st.matrix.sync.SyncService
|
||||
import app.dapk.st.push.PushHandler
|
||||
import app.dapk.st.work.WorkScheduler
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
private var previousJob: Job? = null
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class MatrixPushHandler(
|
||||
private val workScheduler: WorkScheduler,
|
||||
private val credentialsStore: CredentialsStore,
|
||||
private val syncService: SyncService,
|
||||
private val roomStore: RoomStore,
|
||||
) : PushHandler {
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
log(AppLogTag.PUSH, "new push token received")
|
||||
workScheduler.schedule(
|
||||
WorkScheduler.WorkTask(
|
||||
type = "push_token",
|
||||
jobId = 2,
|
||||
jsonPayload = token
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMessageReceived(eventId: EventId?, roomId: RoomId?) {
|
||||
log(AppLogTag.PUSH, "push received")
|
||||
previousJob?.cancel()
|
||||
previousJob = GlobalScope.launch {
|
||||
when (credentialsStore.credentials()) {
|
||||
null -> log(AppLogTag.PUSH, "push ignored due to missing api credentials")
|
||||
else -> doSync(roomId, eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doSync(roomId: RoomId?, eventId: EventId?) {
|
||||
when (roomId) {
|
||||
null -> {
|
||||
log(AppLogTag.PUSH, "empty push payload - keeping sync alive until unread changes")
|
||||
waitForUnreadChange(60_000) ?: log(AppLogTag.PUSH, "timed out waiting for sync")
|
||||
}
|
||||
|
||||
else -> {
|
||||
log(AppLogTag.PUSH, "push with eventId payload - keeping sync alive until the event shows up in the sync response")
|
||||
waitForEvent(
|
||||
timeout = 60_000,
|
||||
eventId!!,
|
||||
) ?: log(AppLogTag.PUSH, "timed out waiting for sync")
|
||||
}
|
||||
}
|
||||
log(AppLogTag.PUSH, "push sync finished")
|
||||
}
|
||||
|
||||
private suspend fun waitForEvent(timeout: Long, eventId: EventId): EventId? {
|
||||
return withTimeoutOrNull(timeout) {
|
||||
combine(syncService.startSyncing().startInstantly(), syncService.observeEvent(eventId)) { _, event -> event }
|
||||
.firstOrNull {
|
||||
it == eventId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun waitForUnreadChange(timeout: Long): String? {
|
||||
return withTimeoutOrNull(timeout) {
|
||||
combine(syncService.startSyncing().startInstantly(), roomStore.observeUnread()) { _, unread -> unread }
|
||||
.first()
|
||||
"ignored"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Flow<Unit>.startInstantly() = this.onStart { emit(Unit) }
|
||||
}
|
@ -6,33 +6,21 @@ import app.dapk.st.core.CoroutineDispatchers
|
||||
import app.dapk.st.core.DeviceMeta
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.imageloader.IconLoader
|
||||
import app.dapk.st.matrix.common.CredentialsStore
|
||||
import app.dapk.st.matrix.push.PushService
|
||||
import app.dapk.st.matrix.sync.RoomStore
|
||||
import app.dapk.st.matrix.sync.SyncService
|
||||
import app.dapk.st.navigator.IntentFactory
|
||||
import app.dapk.st.push.RegisterFirebasePushTokenUseCase
|
||||
import app.dapk.st.work.WorkScheduler
|
||||
|
||||
class NotificationsModule(
|
||||
private val pushService: PushService,
|
||||
private val syncService: SyncService,
|
||||
private val credentialsStore: CredentialsStore,
|
||||
private val firebasePushTokenUseCase: RegisterFirebasePushTokenUseCase,
|
||||
private val iconLoader: IconLoader,
|
||||
private val roomStore: RoomStore,
|
||||
private val context: Context,
|
||||
private val workScheduler: WorkScheduler,
|
||||
private val intentFactory: IntentFactory,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val deviceMeta: DeviceMeta,
|
||||
) : ProvidableModule {
|
||||
|
||||
fun pushUseCase() = pushService
|
||||
fun syncService() = syncService
|
||||
fun credentialProvider() = credentialsStore
|
||||
fun firebasePushTokenUseCase() = firebasePushTokenUseCase
|
||||
fun roomStore() = roomStore
|
||||
fun notificationsUseCase() = RenderNotificationsUseCase(
|
||||
notificationRenderer = NotificationRenderer(
|
||||
notificationManager(),
|
||||
@ -55,5 +43,4 @@ class NotificationsModule(
|
||||
|
||||
private fun notificationManager() = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
fun workScheduler() = workScheduler
|
||||
}
|
||||
|
@ -1,90 +0,0 @@
|
||||
package app.dapk.st.notifications
|
||||
|
||||
import android.content.Context
|
||||
import app.dapk.st.core.AppLogTag.PUSH
|
||||
import app.dapk.st.core.extensions.unsafeLazy
|
||||
import app.dapk.st.core.log
|
||||
import app.dapk.st.core.module
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import app.dapk.st.work.WorkScheduler
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
private var previousJob: Job? = null
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class PushAndroidService : FirebaseMessagingService() {
|
||||
|
||||
private val module by unsafeLazy { module<NotificationsModule>() }
|
||||
private lateinit var context: Context
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
context = applicationContext
|
||||
}
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
log(PUSH, "new push token received")
|
||||
module.workScheduler().schedule(
|
||||
WorkScheduler.WorkTask(
|
||||
type = "push_token",
|
||||
jobId = 2,
|
||||
jsonPayload = token
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
val eventId = message.data["event_id"]?.let { EventId(it) }
|
||||
val roomId = message.data["room_id"]?.let { RoomId(it) }
|
||||
|
||||
log(PUSH, "push received")
|
||||
previousJob?.cancel()
|
||||
previousJob = GlobalScope.launch {
|
||||
when (module.credentialProvider().credentials()) {
|
||||
null -> log(PUSH, "push ignored due to missing api credentials")
|
||||
else -> doSync(roomId, eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doSync(roomId: RoomId?, eventId: EventId?) {
|
||||
when (roomId) {
|
||||
null -> {
|
||||
log(PUSH, "empty push payload - keeping sync alive until unread changes")
|
||||
waitForUnreadChange(60_000) ?: log(PUSH, "timed out waiting for sync")
|
||||
}
|
||||
else -> {
|
||||
log(PUSH, "push with eventId payload - keeping sync alive until the event shows up in the sync response")
|
||||
waitForEvent(
|
||||
timeout = 60_000,
|
||||
eventId!!,
|
||||
) ?: log(PUSH, "timed out waiting for sync")
|
||||
}
|
||||
}
|
||||
log(PUSH, "push sync finished")
|
||||
}
|
||||
|
||||
private suspend fun waitForEvent(timeout: Long, eventId: EventId): EventId? {
|
||||
return withTimeoutOrNull(timeout) {
|
||||
combine(module.syncService().startSyncing().startInstantly(), module.syncService().observeEvent(eventId)) { _, event -> event }
|
||||
.firstOrNull {
|
||||
it == eventId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun waitForUnreadChange(timeout: Long): String? {
|
||||
return withTimeoutOrNull(timeout) {
|
||||
combine(module.syncService().startSyncing().startInstantly(), module.roomStore().observeUnread()) { _, unread -> unread }
|
||||
.first()
|
||||
"ignored"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Flow<Unit>.startInstantly() = this.onStart { emit(Unit) }
|
Loading…
x
Reference in New Issue
Block a user