adding support for unified push

This commit is contained in:
Adam Brown 2022-08-18 19:42:25 +01:00
parent 41da7a2af9
commit 0c35481bda
26 changed files with 450 additions and 63 deletions

View File

@ -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))) }
}

View File

@ -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")
}
}
}
}

View File

@ -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
}

View File

@ -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"/>

View File

@ -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,
)

View File

@ -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
}
}

View File

@ -3,4 +3,4 @@ package app.dapk.st.push
interface PushTokenRegistrar {
suspend fun registerCurrentToken()
fun unregister()
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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,
)
}
}

View File

@ -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))
}
}

View File

@ -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?,
)
}
}

View File

@ -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,
)
}
}

View File

@ -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())

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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)
)
)
}

View File

@ -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")

View File

@ -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"),

View File

@ -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 {

View File

@ -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() })
}

View File

@ -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,

View File

@ -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 {

View File

@ -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(

View File

@ -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)
}
}

View File

@ -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,
),
)
)