mirror of
https://github.com/ouchadam/small-talk.git
synced 2025-02-01 20:16:44 +01:00
adding support for unified push
This commit is contained in:
parent
41da7a2af9
commit
0c35481bda
@ -92,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, workModule, storeModule, context)
|
||||
val domainModules = DomainModules(matrixModules, trackingModule.errorTracker, workModule, storeModule, context, coroutineDispatchers)
|
||||
|
||||
val coreAndroidModule = CoreAndroidModule(intentFactory = object : IntentFactory {
|
||||
override fun notificationOpenApp(context: Context) = PendingIntent.getActivity(
|
||||
@ -180,6 +180,7 @@ internal class FeatureModules internal constructor(
|
||||
val settingsModule by unsafeLazy {
|
||||
SettingsModule(
|
||||
storeModule.value,
|
||||
pushModule,
|
||||
matrixModules.crypto,
|
||||
matrixModules.sync,
|
||||
context.contentResolver,
|
||||
@ -424,6 +425,7 @@ internal class DomainModules(
|
||||
private val workModule: WorkModule,
|
||||
private val storeModule: Lazy<StoreModule>,
|
||||
private val context: Application,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
|
||||
val pushModule by unsafeLazy {
|
||||
@ -434,7 +436,13 @@ internal class DomainModules(
|
||||
matrixModules.sync,
|
||||
store.roomStore(),
|
||||
)
|
||||
PushModule(matrixModules.push, errorTracker, pushHandler, context)
|
||||
PushModule(
|
||||
errorTracker,
|
||||
pushHandler,
|
||||
context,
|
||||
dispatchers,
|
||||
SharedPreferencesDelegate(context.applicationContext, fileName = "dapk-user-preferences", dispatchers)
|
||||
)
|
||||
}
|
||||
val taskRunnerModule by unsafeLazy { TaskRunnerModule(TaskRunnerAdapter(matrixModules.matrix::run, AppTaskRunner(matrixModules.push))) }
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package app.dapk.st.graph
|
||||
|
||||
import app.dapk.st.matrix.push.PushService
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import app.dapk.st.work.TaskRunner
|
||||
import io.ktor.client.plugins.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class AppTaskRunner(
|
||||
private val pushService: PushService,
|
||||
@ -12,7 +14,8 @@ class AppTaskRunner(
|
||||
return when (val type = workTask.task.type) {
|
||||
"push_token" -> {
|
||||
runCatching {
|
||||
pushService.registerPush(workTask.task.jsonPayload)
|
||||
val payload = Json.decodeFromString(PushTokenPayload.serializer(), workTask.task.jsonPayload)
|
||||
pushService.registerPush(payload.token, payload.gatewayUrl)
|
||||
}.fold(
|
||||
onSuccess = { TaskRunner.TaskResult.Success(workTask.source) },
|
||||
onFailure = {
|
||||
@ -25,9 +28,10 @@ class AppTaskRunner(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown work type: $type")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
applyAndroidLibraryModule(project)
|
||||
apply plugin: "org.jetbrains.kotlin.plugin.serialization"
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':domains:android:core')
|
||||
implementation project(':domains:store')
|
||||
implementation project(':matrix:services:push')
|
||||
implementation platform('com.google.firebase:firebase-bom:29.0.3')
|
||||
implementation 'com.google.firebase:firebase-messaging'
|
||||
implementation Dependencies.mavenCentral.kotlinSerializationJson
|
||||
implementation Dependencies.jitPack.unifiedPush
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<receiver android:exported="true" android:enabled="true" android:name=".unifiedpush.CustomR">
|
||||
<receiver android:exported="true" android:enabled="true" android:name=".unifiedpush.UnifiedPushMessageReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="org.unifiedpush.android.connector.MESSAGE"/>
|
||||
<action android:name="org.unifiedpush.android.connector.UNREGISTERED"/>
|
||||
|
@ -2,8 +2,16 @@ package app.dapk.st.push
|
||||
|
||||
import app.dapk.st.matrix.common.EventId
|
||||
import app.dapk.st.matrix.common.RoomId
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
interface PushHandler {
|
||||
fun onNewToken(token: String)
|
||||
fun onNewToken(payload: PushTokenPayload)
|
||||
fun onMessageReceived(eventId: EventId?, roomId: RoomId?)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class PushTokenPayload(
|
||||
@SerialName("token") val token: String,
|
||||
@SerialName("gateway_url") val gatewayUrl: String,
|
||||
)
|
@ -1,24 +1,40 @@
|
||||
package app.dapk.st.push
|
||||
|
||||
import android.content.Context
|
||||
import app.dapk.st.core.CoroutineDispatchers
|
||||
import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.matrix.push.PushService
|
||||
import app.dapk.st.push.firebase.RegisterFirebasePushTokenUseCase
|
||||
import app.dapk.st.core.extensions.unsafeLazy
|
||||
import app.dapk.st.domain.Preferences
|
||||
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
|
||||
import app.dapk.st.push.firebase.FirebasePushTokenRegistrar
|
||||
import app.dapk.st.push.unifiedpush.UnifiedPushRegistrar
|
||||
|
||||
class PushModule(
|
||||
private val pushService: PushService,
|
||||
private val errorTracker: ErrorTracker,
|
||||
private val pushHandler: PushHandler,
|
||||
private val context: Context,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val preferences: Preferences,
|
||||
) : ProvidableModule {
|
||||
|
||||
fun pushTokenRegistrar(): PushTokenRegistrar = RegisterFirebasePushTokenUseCase(
|
||||
pushService,
|
||||
errorTracker,
|
||||
context,
|
||||
)
|
||||
private val registrars by unsafeLazy {
|
||||
PushTokenRegistrars(
|
||||
context,
|
||||
FirebasePushTokenRegistrar(
|
||||
errorTracker,
|
||||
context,
|
||||
pushHandler,
|
||||
),
|
||||
UnifiedPushRegistrar(context),
|
||||
PushTokenRegistrarPreferences(preferences)
|
||||
)
|
||||
}
|
||||
|
||||
fun pushTokenRegistrars() = registrars
|
||||
|
||||
fun pushTokenRegistrar(): PushTokenRegistrar = pushTokenRegistrars()
|
||||
fun pushHandler() = pushHandler
|
||||
fun dispatcher() = dispatchers
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,4 @@ package app.dapk.st.push
|
||||
interface PushTokenRegistrar {
|
||||
suspend fun registerCurrentToken()
|
||||
fun unregister()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
package app.dapk.st.push
|
||||
|
||||
import android.content.Context
|
||||
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
|
||||
import app.dapk.st.push.firebase.FirebasePushTokenRegistrar
|
||||
import app.dapk.st.push.unifiedpush.UnifiedPushRegistrar
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
private val FIREBASE_OPTION = Registrar("Google - Firebase (FCM)")
|
||||
private val NONE = Registrar("None")
|
||||
|
||||
class PushTokenRegistrars(
|
||||
private val context: Context,
|
||||
private val firebasePushTokenRegistrar: FirebasePushTokenRegistrar,
|
||||
private val unifiedPushRegistrar: UnifiedPushRegistrar,
|
||||
private val pushTokenStore: PushTokenRegistrarPreferences,
|
||||
) : PushTokenRegistrar {
|
||||
|
||||
private var selection: Registrar = runBlocking { pushTokenStore.currentSelection()?.let { Registrar(it) } } ?: FIREBASE_OPTION
|
||||
|
||||
fun options(): List<Registrar> {
|
||||
return listOf(NONE, FIREBASE_OPTION) + UnifiedPush.getDistributors(context).map { Registrar(it) }
|
||||
}
|
||||
|
||||
fun currentSelection() = selection
|
||||
|
||||
suspend fun makeSelection(option: Registrar) {
|
||||
selection = option
|
||||
pushTokenStore.store(option.id)
|
||||
when (option) {
|
||||
NONE -> {
|
||||
firebasePushTokenRegistrar.unregister()
|
||||
unifiedPushRegistrar.unregister()
|
||||
}
|
||||
|
||||
FIREBASE_OPTION -> {
|
||||
unifiedPushRegistrar.unregister()
|
||||
firebasePushTokenRegistrar.registerCurrentToken()
|
||||
}
|
||||
|
||||
else -> {
|
||||
firebasePushTokenRegistrar.unregister()
|
||||
unifiedPushRegistrar.registerSelection(option)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun registerCurrentToken() {
|
||||
when (selection) {
|
||||
FIREBASE_OPTION -> firebasePushTokenRegistrar.registerCurrentToken()
|
||||
NONE -> {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
else -> unifiedPushRegistrar.registerCurrentToken()
|
||||
}
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
when (selection) {
|
||||
FIREBASE_OPTION -> firebasePushTokenRegistrar.unregister()
|
||||
NONE -> {
|
||||
runCatching {
|
||||
firebasePushTokenRegistrar.unregister()
|
||||
unifiedPushRegistrar.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
else -> unifiedPushRegistrar.unregister()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class Registrar(val id: String)
|
@ -1,22 +1,34 @@
|
||||
package app.dapk.st.push.firebase
|
||||
|
||||
import app.dapk.st.core.AppLogTag
|
||||
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.push.PushModule
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
|
||||
private const val SYGNAL_GATEWAY = "https://sygnal.dapk.app/_matrix/push/v1/notify"
|
||||
|
||||
class FirebasePushService : FirebaseMessagingService() {
|
||||
|
||||
private val handler by unsafeLazy { module<PushModule>().pushHandler() }
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
handler.onNewToken(token)
|
||||
log(AppLogTag.PUSH, "FCM onNewToken")
|
||||
handler.onNewToken(
|
||||
PushTokenPayload(
|
||||
token = token,
|
||||
gatewayUrl = SYGNAL_GATEWAY,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
log(AppLogTag.PUSH, "FCM onMessage")
|
||||
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,61 @@
|
||||
package app.dapk.st.push.firebase
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import app.dapk.st.core.AppLogTag
|
||||
import app.dapk.st.core.extensions.CrashScope
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.core.log
|
||||
import app.dapk.st.push.PushHandler
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import app.dapk.st.push.PushTokenRegistrar
|
||||
import app.dapk.st.push.unifiedpush.UnifiedPushMessageReceiver
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
|
||||
private const val SYGNAL_GATEWAY = "https://sygnal.dapk.app/_matrix/push/v1/notify"
|
||||
|
||||
class FirebasePushTokenRegistrar(
|
||||
override val errorTracker: ErrorTracker,
|
||||
private val context: Context,
|
||||
private val pushHandler: PushHandler,
|
||||
) : PushTokenRegistrar, CrashScope {
|
||||
|
||||
override suspend fun registerCurrentToken() {
|
||||
log(AppLogTag.PUSH, "FCM - register current token")
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context, FirebasePushService::class.java),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
|
||||
kotlin.runCatching {
|
||||
FirebaseMessaging.getInstance().token().also {
|
||||
pushHandler.onNewToken(
|
||||
PushTokenPayload(
|
||||
token = it,
|
||||
gatewayUrl = SYGNAL_GATEWAY,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
.trackFailure()
|
||||
.onSuccess {
|
||||
log(AppLogTag.PUSH, "registered new push token")
|
||||
}
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
log(AppLogTag.PUSH, "FCM - unregister")
|
||||
FirebaseMessaging.getInstance().deleteToken()
|
||||
context.stopService(Intent(context, FirebasePushService::class.java))
|
||||
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context, FirebasePushService::class.java),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
package app.dapk.st.push.firebase
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import app.dapk.st.core.AppLogTag
|
||||
import app.dapk.st.core.extensions.CrashScope
|
||||
import app.dapk.st.core.extensions.ErrorTracker
|
||||
import app.dapk.st.core.log
|
||||
import app.dapk.st.matrix.push.PushService
|
||||
import app.dapk.st.push.PushTokenRegistrar
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
|
||||
internal class RegisterFirebasePushTokenUseCase(
|
||||
private val pushService: PushService,
|
||||
override val errorTracker: ErrorTracker,
|
||||
private val context: Context,
|
||||
) : PushTokenRegistrar, CrashScope {
|
||||
|
||||
override suspend fun registerCurrentToken() {
|
||||
kotlin.runCatching {
|
||||
FirebaseMessaging.getInstance().token().also {
|
||||
pushService.registerPush(it)
|
||||
}
|
||||
}
|
||||
.trackFailure()
|
||||
.onSuccess {
|
||||
log(AppLogTag.PUSH, "registered new push token")
|
||||
}
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
FirebaseMessaging.getInstance().deleteToken()
|
||||
context.stopService(Intent(context, FirebasePushService::class.java))
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package app.dapk.st.push.unifiedpush
|
||||
|
||||
import android.content.Context
|
||||
import app.dapk.st.core.AppLogTag
|
||||
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.push.PushModule
|
||||
import app.dapk.st.push.PushTokenPayload
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.unifiedpush.android.connector.MessagingReceiver
|
||||
import java.net.URL
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
private const val FALLBACK_UNIFIED_PUSH_GATEWAY = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify"
|
||||
|
||||
class UnifiedPushMessageReceiver : MessagingReceiver() {
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob())
|
||||
|
||||
override fun onMessage(context: Context, message: ByteArray, instance: String) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onMessage, $message")
|
||||
val module = context.module<PushModule>()
|
||||
val handler = module.pushHandler()
|
||||
scope.launch {
|
||||
withContext(module.dispatcher().io) {
|
||||
val payload = json.decodeFromString(UnifiedPushMessagePayload.serializer(), String(message))
|
||||
handler.onMessageReceived(payload.notification.eventId?.let { EventId(it) }, payload.notification.roomId?.let { RoomId(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewEndpoint(context: Context, endpoint: String, instance: String) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onNewEndpoint $endpoint")
|
||||
val module = context.module<PushModule>()
|
||||
val handler = module.pushHandler()
|
||||
scope.launch {
|
||||
withContext(module.dispatcher().io) {
|
||||
val matrixEndpoint = URL(endpoint).let { URL("${it.protocol}://${it.host}/_matrix/push/v1/notify") }
|
||||
val content = matrixEndpoint.openStream().use { String(it.readBytes()) }
|
||||
val gatewayUrl = when {
|
||||
content.contains("\"gateway\":\"matrix\"") -> matrixEndpoint.toString()
|
||||
else -> FALLBACK_UNIFIED_PUSH_GATEWAY
|
||||
}
|
||||
handler.onNewToken(PushTokenPayload(token = endpoint, gatewayUrl = gatewayUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRegistrationFailed(context: Context, instance: String) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onRegistrationFailed")
|
||||
}
|
||||
|
||||
override fun onUnregistered(context: Context, instance: String) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush onUnregistered")
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class UnifiedPushMessagePayload(
|
||||
@SerialName("notification") val notification: Notification,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class Notification(
|
||||
@SerialName("event_id") val eventId: String?,
|
||||
@SerialName("room_id") val roomId: String?,
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package app.dapk.st.push.unifiedpush
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import app.dapk.st.core.AppLogTag
|
||||
import app.dapk.st.core.log
|
||||
import app.dapk.st.push.PushTokenRegistrar
|
||||
import app.dapk.st.push.Registrar
|
||||
import org.unifiedpush.android.connector.UnifiedPush
|
||||
|
||||
class UnifiedPushRegistrar(
|
||||
private val context: Context,
|
||||
) : PushTokenRegistrar {
|
||||
|
||||
fun registerSelection(registrar: Registrar) {
|
||||
log(AppLogTag.PUSH, "UnifiedPush - register: $registrar")
|
||||
UnifiedPush.saveDistributor(context, registrar.id)
|
||||
registerApp()
|
||||
}
|
||||
|
||||
override suspend fun registerCurrentToken() {
|
||||
log(AppLogTag.PUSH, "UnifiedPush - register current token")
|
||||
if (UnifiedPush.getDistributor(context).isNotEmpty()) {
|
||||
registerApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerApp() {
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context, UnifiedPushMessageReceiver::class.java),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
UnifiedPush.registerApp(context)
|
||||
}
|
||||
|
||||
override fun unregister() {
|
||||
UnifiedPush.unregisterApp(context)
|
||||
context.packageManager.setComponentEnabledSetting(
|
||||
ComponentName(context, UnifiedPushMessageReceiver::class.java),
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@ import app.dapk.st.core.extensions.unsafeLazy
|
||||
import app.dapk.st.domain.eventlog.EventLogPersistence
|
||||
import app.dapk.st.domain.localecho.LocalEchoPersistence
|
||||
import app.dapk.st.domain.profile.ProfilePersistence
|
||||
import app.dapk.st.domain.push.PushTokenRegistrarPreferences
|
||||
import app.dapk.st.domain.sync.OverviewPersistence
|
||||
import app.dapk.st.domain.sync.RoomPersistence
|
||||
import app.dapk.st.matrix.common.CredentialsStore
|
||||
@ -34,6 +35,8 @@ class StoreModule(
|
||||
fun filterStore(): FilterStore = FilterPreferences(preferences)
|
||||
val localEchoStore: LocalEchoStore by unsafeLazy { LocalEchoPersistence(errorTracker, database) }
|
||||
|
||||
fun pushStore() = PushTokenRegistrarPreferences(preferences)
|
||||
|
||||
fun applicationStore() = ApplicationPreferences(preferences)
|
||||
|
||||
fun olmStore() = OlmPersistence(database, credentialsStore())
|
||||
|
@ -0,0 +1,16 @@
|
||||
package app.dapk.st.domain.push
|
||||
|
||||
import app.dapk.st.domain.Preferences
|
||||
|
||||
private const val SELECTION_KEY = "push_token_selection"
|
||||
|
||||
class PushTokenRegistrarPreferences(
|
||||
private val preferences: Preferences,
|
||||
) {
|
||||
|
||||
suspend fun currentSelection() = preferences.readString(SELECTION_KEY)
|
||||
|
||||
suspend fun store(registrar: String) {
|
||||
preferences.store(SELECTION_KEY, registrar)
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ dependencies {
|
||||
|
||||
implementation platform('com.google.firebase:firebase-bom:29.0.3')
|
||||
implementation 'com.google.firebase:firebase-messaging'
|
||||
implementation Dependencies.mavenCentral.kotlinSerializationJson
|
||||
|
||||
kotlinTest(it)
|
||||
|
||||
|
@ -8,9 +8,11 @@ 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.push.PushTokenPayload
|
||||
import app.dapk.st.work.WorkScheduler
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private var previousJob: Job? = null
|
||||
|
||||
@ -22,13 +24,13 @@ class MatrixPushHandler(
|
||||
private val roomStore: RoomStore,
|
||||
) : PushHandler {
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
override fun onNewToken(payload: PushTokenPayload) {
|
||||
log(AppLogTag.PUSH, "new push token received")
|
||||
workScheduler.schedule(
|
||||
WorkScheduler.WorkTask(
|
||||
type = "push_token",
|
||||
jobId = 2,
|
||||
jsonPayload = token
|
||||
jsonPayload = Json.encodeToString(PushTokenPayload.serializer(), payload)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ dependencies {
|
||||
implementation project(":matrix:services:crypto")
|
||||
implementation project(":features:navigator")
|
||||
implementation project(':domains:store')
|
||||
implementation project(':domains:android:push')
|
||||
implementation project(":domains:android:compose-core")
|
||||
implementation project(":domains:android:viewmodel")
|
||||
implementation project(":design-library")
|
||||
|
@ -1,13 +1,18 @@
|
||||
package app.dapk.st.settings
|
||||
|
||||
import app.dapk.st.core.BuildMeta
|
||||
import app.dapk.st.push.PushTokenRegistrars
|
||||
|
||||
internal class SettingsItemFactory(private val buildMeta: BuildMeta) {
|
||||
internal class SettingsItemFactory(
|
||||
private val buildMeta: BuildMeta,
|
||||
private val pushTokenRegistrars: PushTokenRegistrars,
|
||||
) {
|
||||
|
||||
fun root() = listOf(
|
||||
SettingItem.Header("General"),
|
||||
SettingItem.Text(SettingItem.Id.Encryption, "Encryption"),
|
||||
SettingItem.Text(SettingItem.Id.EventLog, "Event log"),
|
||||
SettingItem.Text(SettingItem.Id.PushProvider, "Push provider", pushTokenRegistrars.currentSelection().id),
|
||||
SettingItem.Header("Data"),
|
||||
SettingItem.Text(SettingItem.Id.ClearCache, "Clear cache"),
|
||||
SettingItem.Header("Account"),
|
||||
|
@ -7,10 +7,12 @@ import app.dapk.st.core.ProvidableModule
|
||||
import app.dapk.st.domain.StoreModule
|
||||
import app.dapk.st.matrix.crypto.CryptoService
|
||||
import app.dapk.st.matrix.sync.SyncService
|
||||
import app.dapk.st.push.PushModule
|
||||
import app.dapk.st.settings.eventlogger.EventLoggerViewModel
|
||||
|
||||
class SettingsModule(
|
||||
private val storeModule: StoreModule,
|
||||
private val pushModule: PushModule,
|
||||
private val cryptoService: CryptoService,
|
||||
private val syncService: SyncService,
|
||||
private val contentResolver: ContentResolver,
|
||||
@ -24,7 +26,8 @@ class SettingsModule(
|
||||
cryptoService,
|
||||
syncService,
|
||||
UriFilenameResolver(contentResolver, coroutineDispatchers),
|
||||
SettingsItemFactory(buildMeta),
|
||||
SettingsItemFactory(buildMeta, pushModule.pushTokenRegistrars()),
|
||||
pushModule.pushTokenRegistrars(),
|
||||
)
|
||||
|
||||
internal fun eventLogViewModel(): EventLoggerViewModel {
|
||||
|
@ -68,6 +68,9 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
|
||||
item(Page.Routes.encryption) {
|
||||
Encryption(viewModel, it)
|
||||
}
|
||||
item(Page.Routes.pushProviders) {
|
||||
PushProviders(viewModel, it)
|
||||
}
|
||||
item(Page.Routes.importRoomKeys) {
|
||||
when (it.importProgress) {
|
||||
null -> {
|
||||
@ -132,6 +135,7 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Lce.Content -> {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
@ -142,6 +146,7 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Lce.Error -> {
|
||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
@ -152,6 +157,7 @@ internal fun SettingsScreen(viewModel: SettingsViewModel, onSignOut: () -> Unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Lce.Loading -> CenteredLoading()
|
||||
}
|
||||
}
|
||||
@ -176,6 +182,7 @@ private fun RootSettings(page: Page.Root, onClick: (SettingItem) -> Unit) {
|
||||
|
||||
SettingsTextRow(item.content, item.subtitle, itemOnClick)
|
||||
}
|
||||
|
||||
is SettingItem.AccessToken -> {
|
||||
Row(
|
||||
Modifier
|
||||
@ -193,6 +200,7 @@ private fun RootSettings(page: Page.Root, onClick: (SettingItem) -> Unit) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is SettingItem.Header -> Header(item.label)
|
||||
}
|
||||
}
|
||||
@ -203,6 +211,7 @@ private fun RootSettings(page: Page.Root, onClick: (SettingItem) -> Unit) {
|
||||
is Lce.Error -> {
|
||||
// TODO
|
||||
}
|
||||
|
||||
is Lce.Loading -> {
|
||||
// TODO
|
||||
}
|
||||
@ -216,6 +225,32 @@ private fun Encryption(viewModel: SettingsViewModel, page: Page.Security) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun PushProviders(viewModel: SettingsViewModel, state: Page.PushProviders) {
|
||||
LaunchedEffect(true) {
|
||||
viewModel.fetchPushProviders()
|
||||
}
|
||||
|
||||
when (val lce = state.options) {
|
||||
null -> {}
|
||||
is Lce.Loading -> CenteredLoading()
|
||||
is Lce.Content -> {
|
||||
LazyColumn {
|
||||
items(lce.value) {
|
||||
Row(Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
RadioButton(selected = it == state.selection, onClick = { viewModel.selectPushProvider(it) })
|
||||
Text(it.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is Lce.Error -> TODO()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun SettingsViewModel.ObserveEvents(onSignOut: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
@ -228,10 +263,12 @@ private fun SettingsViewModel.ObserveEvents(onSignOut: () -> Unit) {
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("dapk token", it.content))
|
||||
Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
is SettingsEvent.Toast -> Toast.makeText(context, it.message, Toast.LENGTH_SHORT).show()
|
||||
OpenEventLog -> {
|
||||
context.startActivity(Intent(context, EventLogActivity::class.java))
|
||||
}
|
||||
|
||||
is OpenUrl -> {
|
||||
context.startActivity(Intent(Intent.ACTION_VIEW).apply { data = it.url.toUri() })
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import android.net.Uri
|
||||
import app.dapk.st.core.Lce
|
||||
import app.dapk.st.design.components.Route
|
||||
import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.push.Registrar
|
||||
|
||||
internal data class SettingsScreenState(
|
||||
val page: SpiderPage<out Page>,
|
||||
@ -17,9 +18,15 @@ internal sealed interface Page {
|
||||
val importProgress: Lce<Unit>? = null,
|
||||
) : Page
|
||||
|
||||
data class PushProviders(
|
||||
val selection: Registrar? = null,
|
||||
val options: Lce<List<Registrar>>? = Lce.Loading()
|
||||
) : Page
|
||||
|
||||
object Routes {
|
||||
val root = Route<Root>("Settings")
|
||||
val encryption = Route<Page.Security>("Encryption")
|
||||
val pushProviders = Route<Page.PushProviders>("PushProviders")
|
||||
val importRoomKeys = Route<Page.ImportRoomKey>("ImportRoomKey")
|
||||
}
|
||||
}
|
||||
@ -42,6 +49,7 @@ internal sealed interface SettingItem {
|
||||
AccessToken,
|
||||
ClearCache,
|
||||
EventLog,
|
||||
PushProvider,
|
||||
Encryption,
|
||||
PrivacyPolicy,
|
||||
Ignored,
|
||||
|
@ -8,6 +8,8 @@ import app.dapk.st.design.components.SpiderPage
|
||||
import app.dapk.st.domain.StoreCleaner
|
||||
import app.dapk.st.matrix.crypto.CryptoService
|
||||
import app.dapk.st.matrix.sync.SyncService
|
||||
import app.dapk.st.push.PushTokenRegistrars
|
||||
import app.dapk.st.push.Registrar
|
||||
import app.dapk.st.settings.SettingItem.Id.*
|
||||
import app.dapk.st.settings.SettingsEvent.*
|
||||
import app.dapk.st.viewmodel.DapkViewModel
|
||||
@ -24,6 +26,7 @@ internal class SettingsViewModel(
|
||||
private val syncService: SyncService,
|
||||
private val uriFilenameResolver: UriFilenameResolver,
|
||||
private val settingsItemFactory: SettingsItemFactory,
|
||||
private val pushTokenRegistrars: PushTokenRegistrars,
|
||||
factory: MutableStateFactory<SettingsScreenState> = defaultStateFactory(),
|
||||
) : DapkViewModel<SettingsScreenState, SettingsEvent>(
|
||||
initialState = SettingsScreenState(SpiderPage(Page.Routes.root, "Settings", null, Page.Root(Lce.Loading()))),
|
||||
@ -50,39 +53,70 @@ internal class SettingsViewModel(
|
||||
_events.emit(SignedOut)
|
||||
}
|
||||
}
|
||||
|
||||
AccessToken -> {
|
||||
viewModelScope.launch {
|
||||
require(item is SettingItem.AccessToken)
|
||||
_events.emit(CopyToClipboard("Token copied", item.accessToken))
|
||||
}
|
||||
}
|
||||
|
||||
ClearCache -> {
|
||||
viewModelScope.launch {
|
||||
cacheCleaner.cleanCache(removeCredentials = false)
|
||||
_events.emit(Toast(message = "Cache deleted"))
|
||||
}
|
||||
}
|
||||
|
||||
EventLog -> {
|
||||
viewModelScope.launch {
|
||||
_events.emit(OpenEventLog)
|
||||
}
|
||||
}
|
||||
|
||||
Encryption -> {
|
||||
updateState {
|
||||
copy(page = SpiderPage(Page.Routes.encryption, "Encryption", Page.Routes.root, Page.Security))
|
||||
}
|
||||
}
|
||||
|
||||
PrivacyPolicy -> {
|
||||
viewModelScope.launch {
|
||||
_events.emit(OpenUrl(PRIVACY_POLICY_URL))
|
||||
}
|
||||
}
|
||||
|
||||
PushProvider -> {
|
||||
updateState {
|
||||
copy(page = SpiderPage(Page.Routes.pushProviders, "Push providers", Page.Routes.root, Page.PushProviders()))
|
||||
}
|
||||
}
|
||||
|
||||
Ignored -> {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fetchPushProviders() {
|
||||
updatePageState<Page.PushProviders> { copy(options = Lce.Loading()) }
|
||||
viewModelScope.launch {
|
||||
updatePageState<Page.PushProviders> {
|
||||
copy(
|
||||
selection = pushTokenRegistrars.currentSelection(),
|
||||
options = Lce.Content(pushTokenRegistrars.options())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun selectPushProvider(registrar: Registrar) {
|
||||
viewModelScope.launch {
|
||||
pushTokenRegistrars.makeSelection(registrar)
|
||||
fetchPushProviders()
|
||||
}
|
||||
}
|
||||
|
||||
fun importFromFileKeys(file: Uri, passphrase: String) {
|
||||
updatePageState<Page.ImportRoomKey> { copy(importProgress = Lce.Loading()) }
|
||||
viewModelScope.launch {
|
||||
|
@ -12,7 +12,7 @@ private val SERVICE_KEY = PushService::class
|
||||
|
||||
interface PushService : MatrixService {
|
||||
|
||||
suspend fun registerPush(token: String)
|
||||
suspend fun registerPush(token: String, gatewayUrl: String)
|
||||
|
||||
@Serializable
|
||||
data class PushRequest(
|
||||
|
@ -13,8 +13,8 @@ class DefaultPushService(
|
||||
|
||||
private val useCase = RegisterPushUseCase(httpClient, credentialsStore, logger)
|
||||
|
||||
override suspend fun registerPush(token: String) {
|
||||
useCase.registerPushToken(token)
|
||||
override suspend fun registerPush(token: String, gatewayUrl: String) {
|
||||
useCase.registerPushToken(token, gatewayUrl)
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,7 @@ internal class RegisterPushUseCase(
|
||||
private val logger: MatrixLogger,
|
||||
) {
|
||||
|
||||
suspend fun registerPushToken(token: String) {
|
||||
suspend fun registerPushToken(token: String, gatewayUrl: String) {
|
||||
if (credentialsStore.isSignedIn()) {
|
||||
logger.matrixLog("register push token: $token")
|
||||
matrixClient.execute(
|
||||
@ -29,7 +29,7 @@ internal class RegisterPushUseCase(
|
||||
append = false,
|
||||
data = PushRequest.Payload(
|
||||
format = "event_id_only",
|
||||
url = "https://sygnal.dapk.app/_matrix/push/v1/notify",
|
||||
url = gatewayUrl,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user