mirror of
https://github.com/lone-cloud/prism-android
synced 2026-06-03 11:03:10 -07:00
migrate to distributor v0.7.x
This commit is contained in:
parent
b96ced249e
commit
ea9ff8b596
39 changed files with 439 additions and 495 deletions
|
|
@ -97,7 +97,9 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.unifiedpush.distributor)
|
implementation(libs.unifiedpush.distributor)
|
||||||
|
implementation(libs.unifiedpush.distributor.base)
|
||||||
implementation(libs.unifiedpush.distributor.ui)
|
implementation(libs.unifiedpush.distributor.ui)
|
||||||
|
implementation(libs.accompanist.permissions)
|
||||||
implementation(libs.tink.android)
|
implementation(libs.tink.android)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
|
android:process=":ui"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -33,7 +34,16 @@
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:foregroundServiceType="specialUse">
|
android:foregroundServiceType="specialUse">
|
||||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||||
android:value="This service needs to be constantly connected to the server, to get Server-Sent Events messages from it."/>
|
android:value="This service needs to be constantly connected to the server via WebSocket to receive push notifications."/>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".services.PrismInternalService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.unifiedpush.distributor.internal.service"/>
|
||||||
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
|
|
@ -45,6 +55,15 @@
|
||||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
<receiver
|
||||||
|
android:name=".receivers.PrismConfigReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="app.lonecloud.prism.SET_PRISM_SERVER_URL" />
|
||||||
|
<action android:name="app.lonecloud.prism.SET_PRISM_API_KEY" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receivers.RegisterBroadcastReceiver"
|
android:name=".receivers.RegisterBroadcastReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package app.lonecloud.prism
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import org.unifiedpush.distributor.MigrationManager
|
import org.unifiedpush.android.distributor.MigrationManager
|
||||||
import org.unifiedpush.distributor.Store
|
import org.unifiedpush.android.distributor.Store
|
||||||
|
|
||||||
class AppStore(context: Context) :
|
class AppStore(context: Context) :
|
||||||
Store(context, PREF_NAME),
|
Store(context, PREF_NAME),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package app.lonecloud.prism
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.lonecloud.prism.services.MainRegistrationCounter
|
import app.lonecloud.prism.services.MainRegistrationCounter
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import org.unifiedpush.distributor.Database as Database
|
import org.unifiedpush.android.distributor.Database as Database
|
||||||
|
|
||||||
object DatabaseFactory {
|
object DatabaseFactory {
|
||||||
class MainDatabase(context: Context) : Database(context) {
|
class MainDatabase(context: Context) : Database(context) {
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,11 @@ package app.lonecloud.prism
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.lonecloud.prism.api.MessageSender
|
import app.lonecloud.prism.api.MessageSender
|
||||||
import app.lonecloud.prism.api.data.ClientMessage
|
import app.lonecloud.prism.api.data.ClientMessage
|
||||||
import org.unifiedpush.distributor.Database
|
import org.unifiedpush.android.distributor.Database
|
||||||
import org.unifiedpush.distributor.UnifiedPushDistributor
|
import org.unifiedpush.android.distributor.UnifiedPushDistributor
|
||||||
|
|
||||||
/**
|
|
||||||
* These functions are used to send messages to other apps
|
|
||||||
*/
|
|
||||||
object Distributor : UnifiedPushDistributor() {
|
object Distributor : UnifiedPushDistributor() {
|
||||||
override val receiverComponentName = "app.lonecloud.prism.receivers.RegisterBroadcastReceiver"
|
override val receiverComponent = app.lonecloud.prism.receivers.RegisterBroadcastReceiver::class.java
|
||||||
|
|
||||||
override fun getDb(context: Context): Database = DatabaseFactory.getDb(context)
|
override fun getDb(context: Context): Database = DatabaseFactory.getDb(context)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package app.lonecloud.prism
|
|
||||||
|
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
import kotlinx.coroutines.ensureActive
|
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.filterIsInstance
|
|
||||||
|
|
||||||
object EventBus {
|
|
||||||
val mutEvents: MutableSharedFlow<Any> = MutableSharedFlow()
|
|
||||||
val events = mutEvents.asSharedFlow()
|
|
||||||
|
|
||||||
suspend inline fun <reified T : Any> publish(event: T) {
|
|
||||||
if (mutEvents.subscriptionCount.value > 0) {
|
|
||||||
mutEvents.emit(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun <reified T> subscribe(crossinline onEvent: (T) -> Unit) {
|
|
||||||
events.filterIsInstance<T>()
|
|
||||||
.collectLatest { event ->
|
|
||||||
coroutineContext.ensureActive()
|
|
||||||
onEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
app/src/main/java/app/lonecloud/prism/PrismConfig.kt
Normal file
18
app/src/main/java/app/lonecloud/prism/PrismConfig.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package app.lonecloud.prism
|
||||||
|
|
||||||
|
import org.unifiedpush.android.distributor.ui.AppConfig
|
||||||
|
import org.unifiedpush.android.distributor.ui.InAppNotifsConfig
|
||||||
|
import org.unifiedpush.android.distributor.ui.MigrationConfig
|
||||||
|
import org.unifiedpush.android.distributor.ui.NoLoginConfig
|
||||||
|
import org.unifiedpush.android.distributor.ui.PrivacyPolicy
|
||||||
|
|
||||||
|
object PrismConfig : AppConfig {
|
||||||
|
override val appName = R.string.app_name
|
||||||
|
override val restartableService = true
|
||||||
|
override val privacyPolicy: PrivacyPolicy? = null
|
||||||
|
override val loginConfig = NoLoginConfig(canChangeUrl = true)
|
||||||
|
override val migrationConfig = object : MigrationConfig {
|
||||||
|
override val supportTempFallback = true
|
||||||
|
}
|
||||||
|
override val inAppNotifsConfig: InAppNotifsConfig? = null
|
||||||
|
}
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
package app.lonecloud.prism.activities
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import app.lonecloud.prism.AppStore
|
|
||||||
import app.lonecloud.prism.DatabaseFactory
|
|
||||||
import app.lonecloud.prism.Distributor
|
|
||||||
import app.lonecloud.prism.EventBus
|
|
||||||
import app.lonecloud.prism.PrismServerClient
|
|
||||||
import app.lonecloud.prism.services.FgService
|
|
||||||
import app.lonecloud.prism.services.MigrationManager
|
|
||||||
import app.lonecloud.prism.services.RestartWorker
|
|
||||||
import app.lonecloud.prism.services.SourceManager
|
|
||||||
import app.lonecloud.prism.utils.TAG
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class AppAction(private val action: Action) {
|
|
||||||
sealed class Action {
|
|
||||||
data object RestartService : Action()
|
|
||||||
class ShowToasts(val enable: Boolean) : Action()
|
|
||||||
class DeleteRegistration(val registrations: List<String>) : Action()
|
|
||||||
data object FallbackIntroShown : Action()
|
|
||||||
class FallbackDistribSelected(val distributor: String?) : Action()
|
|
||||||
class MigrateToDistrib(val distributor: String) : Action()
|
|
||||||
data object ReactivateUnifiedPush : Action()
|
|
||||||
data object RegisterPrismServer : Action()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handle(context: Context) {
|
|
||||||
when (action) {
|
|
||||||
is Action.RestartService -> restartService(context)
|
|
||||||
is Action.ShowToasts -> showToasts(context, action)
|
|
||||||
is Action.DeleteRegistration -> deleteRegistration(context, action)
|
|
||||||
is Action.FallbackIntroShown -> fallbackIntroShown(context)
|
|
||||||
is Action.FallbackDistribSelected -> fallbackDistribSelected(context, action)
|
|
||||||
is Action.MigrateToDistrib -> migrateToDistrib(context, action)
|
|
||||||
is Action.ReactivateUnifiedPush -> reactivateUnifiedPush(context)
|
|
||||||
is Action.RegisterPrismServer -> registerPrismServer(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun restartService(context: Context) {
|
|
||||||
Log.d(TAG, "Restarting the Listener")
|
|
||||||
SourceManager.clearFails()
|
|
||||||
FgService.stopService {
|
|
||||||
RestartWorker.run(context, delay = 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showToasts(context: Context, action: Action.ShowToasts) {
|
|
||||||
AppStore(context).showToasts = action.enable
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun deleteRegistration(context: Context, action: Action.DeleteRegistration) {
|
|
||||||
action.registrations.forEach { token ->
|
|
||||||
val db = DatabaseFactory.getDb(context)
|
|
||||||
val dbApp = db.listApps().find { it.connectorToken == token }
|
|
||||||
|
|
||||||
if (dbApp?.description?.startsWith("target:") == true) {
|
|
||||||
val appName = dbApp.title ?: dbApp.packageName
|
|
||||||
PrismServerClient.deleteApp(context, appName)
|
|
||||||
}
|
|
||||||
|
|
||||||
Distributor.deleteApp(context, token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fallbackIntroShown(context: Context) {
|
|
||||||
MigrationManager().setFallbackIntroShown(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save fallback service
|
|
||||||
*
|
|
||||||
* If fallback is disabled and we have already send TEMP_UNAVAILABLE:
|
|
||||||
* we send the endpoint again
|
|
||||||
*/
|
|
||||||
private fun fallbackDistribSelected(context: Context, action: Action.FallbackDistribSelected) {
|
|
||||||
MigrationManager()
|
|
||||||
.selectFallbackService(
|
|
||||||
context,
|
|
||||||
action.distributor,
|
|
||||||
SourceManager.shouldSendFallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun migrateToDistrib(context: Context, action: Action.MigrateToDistrib) {
|
|
||||||
MigrationManager().migrate(context, action.distributor)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reactivateUnifiedPush(context: Context) {
|
|
||||||
MigrationManager().reactivate(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun registerPrismServer(context: Context) {
|
|
||||||
app.lonecloud.prism.PrismServerClient.registerAllApps(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ViewModel.publishAction(action: AppAction) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
EventBus.publish(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package app.lonecloud.prism.activities
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import org.unifiedpush.android.distributor.ui.compose.state.ApplicationRowState
|
|
||||||
import org.unifiedpush.distributor.utils.appInfoForMetadata
|
|
||||||
import org.unifiedpush.distributor.utils.getApplicationIcon
|
|
||||||
import org.unifiedpush.distributor.utils.getApplicationName
|
|
||||||
|
|
||||||
fun Context.applicationRowState(packageName: String, description: String? = null): ApplicationRowState {
|
|
||||||
val ai = appInfoForMetadata(packageName)
|
|
||||||
val title = ai?.let { getApplicationName(it) } ?: packageName
|
|
||||||
val icon = getApplicationIcon(packageName)
|
|
||||||
val description = if (title == packageName) {
|
|
||||||
description ?: ""
|
|
||||||
} else {
|
|
||||||
description?.let { "$it — $packageName" }
|
|
||||||
?: packageName
|
|
||||||
}
|
|
||||||
return ApplicationRowState(
|
|
||||||
icon = icon,
|
|
||||||
title = title,
|
|
||||||
packageName = packageName,
|
|
||||||
description = description
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
package app.lonecloud.prism.activities
|
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.Context
|
|
||||||
import app.lonecloud.prism.AppStore
|
|
||||||
import org.unifiedpush.android.distributor.ui.compose.DistribMigrationViewModel as UPDistribMigrationViewModel
|
|
||||||
import org.unifiedpush.android.distributor.ui.compose.state.DistribMigrationState
|
|
||||||
import org.unifiedpush.distributor.utils.listOtherDistributors
|
|
||||||
|
|
||||||
class DistribMigrationViewModel(state: DistribMigrationState, val application: Application? = null) : UPDistribMigrationViewModel(state) {
|
|
||||||
constructor(application: Application) : this(
|
|
||||||
stateFrom(application),
|
|
||||||
application
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onFallbackDistribSelected(distributor: String?) {
|
|
||||||
publishAction(
|
|
||||||
AppAction(AppAction.Action.FallbackDistribSelected(distributor))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMigrationDistributorSelected(distributor: String) {
|
|
||||||
publishAction(
|
|
||||||
AppAction(AppAction.Action.MigrateToDistrib(distributor))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFallbackIntroShown() {
|
|
||||||
publishAction(
|
|
||||||
AppAction(AppAction.Action.FallbackIntroShown)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceReactivated() {
|
|
||||||
publishAction(
|
|
||||||
AppAction(AppAction.Action.ReactivateUnifiedPush)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun refreshDistributors() {
|
|
||||||
application?.let { context ->
|
|
||||||
refreshDistributors {
|
|
||||||
val store = AppStore(context)
|
|
||||||
val fallbackDistrib = store.fallbackService
|
|
||||||
return@refreshDistributors context.listOtherDistributors()
|
|
||||||
.map { packageName ->
|
|
||||||
context.applicationRowState(packageName).copy(
|
|
||||||
selected = fallbackDistrib == packageName
|
|
||||||
)
|
|
||||||
}.toSet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun stateFrom(context: Context): DistribMigrationState {
|
|
||||||
val store = AppStore(context)
|
|
||||||
val fallbackDistrib = store.fallbackService
|
|
||||||
val distributors = context.listOtherDistributors().map { packageName ->
|
|
||||||
context.applicationRowState(packageName).copy(
|
|
||||||
selected = fallbackDistrib == packageName
|
|
||||||
)
|
|
||||||
}.toSet()
|
|
||||||
return DistribMigrationState(
|
|
||||||
distributors,
|
|
||||||
store.fallbackIntroShown,
|
|
||||||
migrated = store.migrated,
|
|
||||||
featureEnabled = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +1,36 @@
|
||||||
package app.lonecloud.prism.activities
|
package app.lonecloud.prism.activities
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import app.lonecloud.prism.EventBus
|
|
||||||
import app.lonecloud.prism.activities.ThemeViewModel
|
|
||||||
import app.lonecloud.prism.activities.ui.App
|
import app.lonecloud.prism.activities.ui.App
|
||||||
import app.lonecloud.prism.activities.ui.theme.AppTheme
|
import app.lonecloud.prism.activities.ui.theme.AppTheme
|
||||||
import app.lonecloud.prism.services.RestartWorker
|
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||||
import app.lonecloud.prism.utils.TAG
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private var jobs: MutableList<Job> = emptyList<Job>().toMutableList()
|
private lateinit var messenger: InternalMessenger
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
RestartWorker.startPeriodic(this)
|
|
||||||
|
messenger = InternalMessenger(this)
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val factory = ViewModelFactory(this.application)
|
val factory = ViewModelFactory(this.application, messenger)
|
||||||
val themeViewModel = viewModel<ThemeViewModel>(factory = factory)
|
val themeViewModel = viewModel<ThemeViewModel>(factory = factory)
|
||||||
AppTheme(
|
AppTheme(
|
||||||
dynamicColor = themeViewModel.dynamicColors
|
dynamicColor = themeViewModel.dynamicColors
|
||||||
) {
|
) {
|
||||||
App(factory, themeViewModel)
|
App(factory, themeViewModel)
|
||||||
}
|
}
|
||||||
subscribeActions()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subscribeActions() {
|
|
||||||
Log.d(TAG, "Subscribing to actions")
|
|
||||||
jobs += CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
EventBus.subscribe<AppAction> { it.handle(this@MainActivity) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "Destroy")
|
|
||||||
jobs.removeAll {
|
|
||||||
it.cancel()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,25 +25,31 @@ import app.lonecloud.prism.utils.VapidKeyGenerator
|
||||||
import app.lonecloud.prism.utils.WebPushEncryptionKeys
|
import app.lonecloud.prism.utils.WebPushEncryptionKeys
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.unifiedpush.android.distributor.ui.compose.BatteryOptimisationViewModel
|
import org.unifiedpush.android.distributor.data.App
|
||||||
import org.unifiedpush.android.distributor.ui.compose.RegistrationsViewModel
|
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||||
import org.unifiedpush.android.distributor.ui.compose.state.RegistrationListState
|
import org.unifiedpush.android.distributor.ipc.InternalOpcode
|
||||||
|
import org.unifiedpush.android.distributor.ui.state.RegistrationListState
|
||||||
|
import org.unifiedpush.android.distributor.ui.vm.BatteryOptimisationViewModel
|
||||||
|
import org.unifiedpush.android.distributor.ui.vm.RegistrationsViewModel
|
||||||
|
|
||||||
class MainViewModel(
|
class MainViewModel(
|
||||||
mainUiState: MainUiState,
|
mainUiState: MainUiState,
|
||||||
val batteryOptimisationViewModel: BatteryOptimisationViewModel,
|
val batteryOptimisationViewModel: BatteryOptimisationViewModel,
|
||||||
val registrationsViewModel: RegistrationsViewModel,
|
val registrationsViewModel: RegistrationsViewModel,
|
||||||
|
val messenger: InternalMessenger?,
|
||||||
val application: Application? = null
|
val application: Application? = null
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
constructor(application: Application) : this(
|
constructor(requireBatteryOpt: Boolean, messenger: InternalMessenger?, application: Application) : this(
|
||||||
mainUiState = MainUiState(
|
mainUiState = MainUiState(
|
||||||
prismServerConfigured = !AppStore(application).prismServerUrl.isNullOrBlank() &&
|
prismServerConfigured = !AppStore(application).prismServerUrl.isNullOrBlank() &&
|
||||||
!AppStore(application).prismApiKey.isNullOrBlank()
|
!AppStore(application).prismApiKey.isNullOrBlank()
|
||||||
),
|
),
|
||||||
batteryOptimisationViewModel = BatteryOptimisationViewModel(application),
|
batteryOptimisationViewModel = BatteryOptimisationViewModel(requireBatteryOpt, messenger),
|
||||||
registrationsViewModel = RegistrationsViewModel(
|
registrationsViewModel = RegistrationsViewModel(
|
||||||
getRegistrationListState(application)
|
RegistrationListState(emptyList<App>()),
|
||||||
|
messenger
|
||||||
),
|
),
|
||||||
|
messenger,
|
||||||
application
|
application
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -69,9 +75,8 @@ class MainViewModel(
|
||||||
|
|
||||||
fun refreshRegistrations() {
|
fun refreshRegistrations() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
application?.let {
|
val apps = messenger?.sendIMessageL(InternalOpcode.REG_LIST, "apps", App::class.java)
|
||||||
registrationsViewModel.state = getRegistrationListState(it)
|
registrationsViewModel.state = RegistrationListState(apps ?: emptyList())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,9 +84,7 @@ class MainViewModel(
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val state = registrationsViewModel.state
|
val state = registrationsViewModel.state
|
||||||
val tokenList = state.list.filter { it.selected }.map { it.token }
|
val tokenList = state.list.filter { it.selected }.map { it.token }
|
||||||
publishAction(
|
messenger?.sendIMessage(InternalOpcode.REG_DELETE, "regs" to tokenList)
|
||||||
AppAction(AppAction.Action.DeleteRegistration(tokenList))
|
|
||||||
)
|
|
||||||
registrationsViewModel.state = RegistrationListState(
|
registrationsViewModel.state = RegistrationListState(
|
||||||
list = state.list.filter {
|
list = state.list.filter {
|
||||||
!it.selected
|
!it.selected
|
||||||
|
|
@ -130,7 +133,9 @@ class MainViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restartService() {
|
fun restartService() {
|
||||||
publishAction(AppAction(AppAction.Action.RestartService))
|
viewModelScope.launch {
|
||||||
|
messenger?.sendIMessage(InternalOpcode.WORKER_RESTART, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hasUnifiedPushSupport(pm: PackageManager, packageName: String): Boolean {
|
private fun hasUnifiedPushSupport(pm: PackageManager, packageName: String): Boolean {
|
||||||
|
|
@ -238,7 +243,6 @@ class MainViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timeout
|
|
||||||
Log.e(TAG, "Endpoint timeout after 30 seconds for token: $connectorToken")
|
Log.e(TAG, "Endpoint timeout after 30 seconds for token: $connectorToken")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
package app.lonecloud.prism.activities
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import app.lonecloud.prism.DatabaseFactory
|
|
||||||
import org.unifiedpush.android.distributor.ui.compose.state.RegistrationListState
|
|
||||||
import org.unifiedpush.android.distributor.ui.compose.state.RegistrationState
|
|
||||||
import org.unifiedpush.distributor.Database
|
|
||||||
|
|
||||||
fun getRegistrationListState(context: Context): RegistrationListState = RegistrationListState(
|
|
||||||
list = DatabaseFactory.getDb(context).listApps().map { app ->
|
|
||||||
getRegistrationState(context, app)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
fun getRegistrationState(context: Context, app: Database.App): RegistrationState {
|
|
||||||
// Parse manual app format: "target:package.name|optional_description"
|
|
||||||
val isManualApp = app.description?.startsWith("target:") == true
|
|
||||||
val targetPackage = app.description?.takeIf { isManualApp }
|
|
||||||
?.substringAfter("target:")?.substringBefore("|")
|
|
||||||
?.takeIf { it.isNotBlank() }
|
|
||||||
|
|
||||||
val cleanDescription = if (isManualApp) {
|
|
||||||
app.description?.substringAfter("|", "")?.takeIf { it.isNotBlank() }
|
|
||||||
} else {
|
|
||||||
app.description
|
|
||||||
}
|
|
||||||
|
|
||||||
val displayPackage = targetPackage ?: app.packageName
|
|
||||||
val displayDescription = if (isManualApp && targetPackage == null) null else cleanDescription
|
|
||||||
|
|
||||||
val baseAppState = context.applicationRowState(displayPackage, displayDescription)
|
|
||||||
|
|
||||||
// For manual apps without target, use custom title instead of package-derived one
|
|
||||||
val displayTitle = if (isManualApp && targetPackage == null) {
|
|
||||||
app.title ?: baseAppState.title
|
|
||||||
} else {
|
|
||||||
baseAppState.title
|
|
||||||
}
|
|
||||||
|
|
||||||
val finalAppState = baseAppState.copy(
|
|
||||||
title = displayTitle
|
|
||||||
)
|
|
||||||
|
|
||||||
return RegistrationState(
|
|
||||||
app = finalAppState,
|
|
||||||
msgCount = app.msgCount,
|
|
||||||
token = app.connectorToken,
|
|
||||||
copyable = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +1,29 @@
|
||||||
package app.lonecloud.prism.activities
|
package app.lonecloud.prism.activities
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Intent
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.lonecloud.prism.AppStore
|
import app.lonecloud.prism.AppStore
|
||||||
|
import app.lonecloud.prism.PrismServerClient
|
||||||
import app.lonecloud.prism.activities.ui.SettingsState
|
import app.lonecloud.prism.activities.ui.SettingsState
|
||||||
|
import app.lonecloud.prism.receivers.PrismConfigReceiver
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||||
|
import org.unifiedpush.android.distributor.ipc.InternalOpcode
|
||||||
|
import org.unifiedpush.android.distributor.ipc.sendUiAction
|
||||||
|
|
||||||
class SettingsViewModel(state: SettingsState, val application: Application? = null) : ViewModel() {
|
class SettingsViewModel(
|
||||||
constructor(application: Application) : this(
|
state: SettingsState,
|
||||||
|
val messenger: InternalMessenger?,
|
||||||
|
val application: Application? = null
|
||||||
|
) : ViewModel() {
|
||||||
|
constructor(messenger: InternalMessenger?, application: Application) : this(
|
||||||
SettingsState.from(application),
|
SettingsState.from(application),
|
||||||
|
messenger,
|
||||||
application
|
application
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,7 +33,8 @@ class SettingsViewModel(state: SettingsState, val application: Application? = nu
|
||||||
fun toggleShowToasts() {
|
fun toggleShowToasts() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
state = state.copy(showToasts = !state.showToasts)
|
state = state.copy(showToasts = !state.showToasts)
|
||||||
publishAction(AppAction(AppAction.Action.ShowToasts(state.showToasts)))
|
application?.let { AppStore(it).showToasts = state.showToasts }
|
||||||
|
messenger?.sendIMessage(InternalOpcode.SHOW_TOASTS_SET, if (state.showToasts) 1 else 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,11 +45,17 @@ class SettingsViewModel(state: SettingsState, val application: Application? = nu
|
||||||
application?.let {
|
application?.let {
|
||||||
AppStore(it).prismServerUrl = trimmedUrl.ifBlank { null }
|
AppStore(it).prismServerUrl = trimmedUrl.ifBlank { null }
|
||||||
|
|
||||||
|
val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_SERVER_URL).apply {
|
||||||
|
putExtra(PrismConfigReceiver.EXTRA_URL, trimmedUrl)
|
||||||
|
setPackage(it.packageName)
|
||||||
|
}
|
||||||
|
it.sendBroadcast(intent)
|
||||||
|
|
||||||
if (trimmedUrl.isNotBlank() && state.prismApiKey.isNotBlank()) {
|
if (trimmedUrl.isNotBlank() && state.prismApiKey.isNotBlank()) {
|
||||||
publishAction(AppAction(AppAction.Action.RegisterPrismServer))
|
PrismServerClient.registerAllApps(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
UiAction.publish(UiAction.Action.UpdatePrismServerConfigured)
|
sendUiAction(it, "UpdatePrismServerConfigured")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -49,16 +67,24 @@ class SettingsViewModel(state: SettingsState, val application: Application? = nu
|
||||||
application?.let {
|
application?.let {
|
||||||
AppStore(it).prismApiKey = trimmedKey.ifBlank { null }
|
AppStore(it).prismApiKey = trimmedKey.ifBlank { null }
|
||||||
|
|
||||||
|
val intent = Intent(PrismConfigReceiver.ACTION_SET_PRISM_API_KEY).apply {
|
||||||
|
putExtra(PrismConfigReceiver.EXTRA_API_KEY, trimmedKey)
|
||||||
|
setPackage(it.packageName)
|
||||||
|
}
|
||||||
|
it.sendBroadcast(intent)
|
||||||
|
|
||||||
if (state.prismServerUrl.isNotBlank() && trimmedKey.isNotBlank()) {
|
if (state.prismServerUrl.isNotBlank() && trimmedKey.isNotBlank()) {
|
||||||
publishAction(AppAction(AppAction.Action.RegisterPrismServer))
|
PrismServerClient.registerAllApps(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
UiAction.publish(UiAction.Action.UpdatePrismServerConfigured)
|
sendUiAction(it, "UpdatePrismServerConfigured")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restartService() {
|
fun restartService() {
|
||||||
publishAction(AppAction(AppAction.Action.RestartService))
|
viewModelScope.launch {
|
||||||
|
messenger?.sendIMessage(InternalOpcode.WORKER_RESTART, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,17 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.lonecloud.prism.AppStore
|
import app.lonecloud.prism.AppStore
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||||
|
import org.unifiedpush.android.distributor.ipc.InternalOpcode
|
||||||
|
|
||||||
class ThemeViewModel(val application: Application? = null) : ViewModel() {
|
class ThemeViewModel(val messenger: InternalMessenger?, val application: Application?) : ViewModel() {
|
||||||
var dynamicColors by mutableStateOf(
|
var dynamicColors by mutableStateOf(application?.let { AppStore(it).dynamicColors } ?: false)
|
||||||
application?.let { AppStore(it).dynamicColors } ?: false
|
|
||||||
)
|
|
||||||
|
|
||||||
fun toggleDynamicColors() {
|
fun toggleDynamicColors() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
dynamicColors = !dynamicColors
|
dynamicColors = !dynamicColors
|
||||||
application?.run {
|
application?.let { AppStore(it).dynamicColors = dynamicColors }
|
||||||
AppStore(this).dynamicColors = dynamicColors
|
messenger?.sendIMessage(InternalOpcode.THEME_DYN_SET, if (dynamicColors) 1 else 0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
package app.lonecloud.prism.activities
|
|
||||||
|
|
||||||
import app.lonecloud.prism.EventBus
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class UiAction(val action: Action) {
|
|
||||||
enum class Action {
|
|
||||||
RefreshRegistrations,
|
|
||||||
UpdatePrismServerConfigured
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handle(action: (Action) -> Unit) {
|
|
||||||
action(this.action)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun publish(type: Action) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
EventBus.publish(UiAction(type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,24 +2,31 @@ package app.lonecloud.prism.activities
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.PowerManager
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import app.lonecloud.prism.activities.ThemeViewModel
|
import app.lonecloud.prism.PrismConfig
|
||||||
import app.lonecloud.prism.activities.ui.MainUiState
|
import app.lonecloud.prism.activities.ui.MainUiState
|
||||||
import app.lonecloud.prism.activities.ui.SettingsState
|
import app.lonecloud.prism.activities.ui.SettingsState
|
||||||
import org.unifiedpush.android.distributor.ui.compose.BatteryOptimisationViewModel
|
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||||
import org.unifiedpush.android.distributor.ui.compose.previewRegistrationsViewModel
|
import org.unifiedpush.android.distributor.ui.state.DistribMigrationState
|
||||||
import org.unifiedpush.android.distributor.ui.compose.state.DistribMigrationState
|
import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel
|
||||||
|
|
||||||
|
class ViewModelFactory(val application: Application, val messenger: InternalMessenger) : ViewModelProvider.Factory {
|
||||||
|
private val requireBatteryOptimization =
|
||||||
|
!(application.getSystemService(Context.POWER_SERVICE) as PowerManager)
|
||||||
|
.isIgnoringBatteryOptimizations(application.packageName)
|
||||||
|
|
||||||
class ViewModelFactory(val application: Application) : ViewModelProvider.Factory {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = when {
|
override fun <T : ViewModel> create(modelClass: Class<T>): T = when {
|
||||||
modelClass.isAssignableFrom(MainViewModel::class.java) -> MainViewModel(application)
|
modelClass.isAssignableFrom(MainViewModel::class.java) -> MainViewModel(requireBatteryOptimization, messenger, application)
|
||||||
modelClass.isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel(
|
modelClass.isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel(messenger, application)
|
||||||
application
|
modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel(messenger, application)
|
||||||
|
modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> DistribMigrationViewModel(
|
||||||
|
DistribMigrationState(),
|
||||||
|
PrismConfig,
|
||||||
|
messenger
|
||||||
)
|
)
|
||||||
modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel(application)
|
|
||||||
modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> DistribMigrationViewModel(application)
|
|
||||||
else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
|
else -> throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
|
||||||
} as T
|
} as T
|
||||||
}
|
}
|
||||||
|
|
@ -30,8 +37,15 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory {
|
||||||
modelClass.isAssignableFrom(MainViewModel::class.java) -> {
|
modelClass.isAssignableFrom(MainViewModel::class.java) -> {
|
||||||
MainViewModel(
|
MainViewModel(
|
||||||
MainUiState(),
|
MainUiState(),
|
||||||
BatteryOptimisationViewModel(true),
|
org.unifiedpush.android.distributor.ui.vm.BatteryOptimisationViewModel(false, null),
|
||||||
previewRegistrationsViewModel(context)
|
org.unifiedpush.android.distributor.ui.vm.RegistrationsViewModel(
|
||||||
|
org.unifiedpush.android.distributor.ui.state.RegistrationListState(
|
||||||
|
emptyList<org.unifiedpush.android.distributor.data.App>()
|
||||||
|
),
|
||||||
|
null
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
modelClass.isAssignableFrom(SettingsViewModel::class.java) -> {
|
modelClass.isAssignableFrom(SettingsViewModel::class.java) -> {
|
||||||
|
|
@ -40,12 +54,18 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory {
|
||||||
showToasts = false,
|
showToasts = false,
|
||||||
prismServerUrl = "",
|
prismServerUrl = "",
|
||||||
prismApiKey = ""
|
prismApiKey = ""
|
||||||
)
|
),
|
||||||
|
null,
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel()
|
modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel(null, null)
|
||||||
modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> {
|
modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> {
|
||||||
DistribMigrationViewModel(DistribMigrationState())
|
DistribMigrationViewModel(
|
||||||
|
DistribMigrationState(),
|
||||||
|
PrismConfig,
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
} as T
|
} as T
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,17 @@ import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import app.lonecloud.prism.R
|
import app.lonecloud.prism.R
|
||||||
import app.lonecloud.prism.activities.DistribMigrationViewModel
|
|
||||||
import app.lonecloud.prism.activities.MainViewModel
|
import app.lonecloud.prism.activities.MainViewModel
|
||||||
import app.lonecloud.prism.activities.PreviewFactory
|
import app.lonecloud.prism.activities.PreviewFactory
|
||||||
import app.lonecloud.prism.activities.SettingsViewModel
|
import app.lonecloud.prism.activities.SettingsViewModel
|
||||||
import app.lonecloud.prism.activities.ThemeViewModel
|
import app.lonecloud.prism.activities.ThemeViewModel
|
||||||
import org.unifiedpush.android.distributor.ui.R as LibR
|
import org.unifiedpush.android.distributor.ipc.subscribeUiActions
|
||||||
import org.unifiedpush.android.distributor.ui.compose.AppBar
|
import org.unifiedpush.android.distributor.ui.compose.AppBar
|
||||||
|
import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel
|
||||||
|
|
||||||
enum class AppScreen(@param:StringRes val title: Int) {
|
enum class AppScreen(@param:StringRes val title: Int) {
|
||||||
Main(R.string.app_name),
|
Main(R.string.app_name),
|
||||||
Settings(LibR.string.settings)
|
Settings(R.string.settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -84,11 +84,13 @@ fun App(
|
||||||
themeViewModel: ThemeViewModel = viewModel<ThemeViewModel>(factory = factory),
|
themeViewModel: ThemeViewModel = viewModel<ThemeViewModel>(factory = factory),
|
||||||
navController: NavHostController = rememberNavController()
|
navController: NavHostController = rememberNavController()
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val uiActionsFlow = subscribeUiActions(context)
|
||||||
|
|
||||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentScreen = AppScreen.valueOf(
|
val currentScreen = AppScreen.valueOf(
|
||||||
backStackEntry?.destination?.route ?: AppScreen.Main.name
|
backStackEntry?.destination?.route ?: AppScreen.Main.name
|
||||||
)
|
)
|
||||||
// shared with all views, no need to scope it
|
|
||||||
val migrationViewModel = viewModel<DistribMigrationViewModel>(factory = factory)
|
val migrationViewModel = viewModel<DistribMigrationViewModel>(factory = factory)
|
||||||
val mainViewModel = viewModel<MainViewModel>(factory = factory)
|
val mainViewModel = viewModel<MainViewModel>(factory = factory)
|
||||||
|
|
||||||
|
|
@ -147,7 +149,8 @@ fun App(
|
||||||
) {
|
) {
|
||||||
MainScreen(
|
MainScreen(
|
||||||
mainViewModel,
|
mainViewModel,
|
||||||
migrationViewModel
|
migrationViewModel,
|
||||||
|
uiActionsFlow
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(
|
composable(
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,9 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import app.lonecloud.prism.AppStore
|
import app.lonecloud.prism.AppStore
|
||||||
import app.lonecloud.prism.EventBus
|
|
||||||
import app.lonecloud.prism.R
|
import app.lonecloud.prism.R
|
||||||
import app.lonecloud.prism.activities.DistribMigrationViewModel
|
|
||||||
import app.lonecloud.prism.activities.MainViewModel
|
import app.lonecloud.prism.activities.MainViewModel
|
||||||
import app.lonecloud.prism.activities.PreviewFactory
|
import app.lonecloud.prism.activities.PreviewFactory
|
||||||
import app.lonecloud.prism.activities.UiAction
|
|
||||||
import org.unifiedpush.android.distributor.ui.R as LibR
|
|
||||||
import org.unifiedpush.android.distributor.ui.compose.AppBar
|
import org.unifiedpush.android.distributor.ui.compose.AppBar
|
||||||
import org.unifiedpush.android.distributor.ui.compose.CardDisableBatteryOptimisation
|
import org.unifiedpush.android.distributor.ui.compose.CardDisableBatteryOptimisation
|
||||||
import org.unifiedpush.android.distributor.ui.compose.CardDisabledForMigration
|
import org.unifiedpush.android.distributor.ui.compose.CardDisabledForMigration
|
||||||
|
|
@ -40,6 +36,7 @@ import org.unifiedpush.android.distributor.ui.compose.PermissionsUi
|
||||||
import org.unifiedpush.android.distributor.ui.compose.RegistrationList
|
import org.unifiedpush.android.distributor.ui.compose.RegistrationList
|
||||||
import org.unifiedpush.android.distributor.ui.compose.RegistrationListHeading
|
import org.unifiedpush.android.distributor.ui.compose.RegistrationListHeading
|
||||||
import org.unifiedpush.android.distributor.ui.compose.UnregisterBarUi
|
import org.unifiedpush.android.distributor.ui.compose.UnregisterBarUi
|
||||||
|
import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -69,7 +66,7 @@ fun MainAppBar(onGoToSettings: () -> Unit) {
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Settings,
|
imageVector = Icons.Default.Settings,
|
||||||
contentDescription = stringResource(LibR.string.settings)
|
contentDescription = stringResource(R.string.settings)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,14 +74,17 @@ fun MainAppBar(onGoToSettings: () -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationViewModel) {
|
fun MainScreen(
|
||||||
|
viewModel: MainViewModel,
|
||||||
|
migrationViewModel: DistribMigrationViewModel,
|
||||||
|
uiActionsFlow: kotlinx.coroutines.flow.Flow<String>?
|
||||||
|
) {
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
EventBus.subscribe<UiAction> {
|
uiActionsFlow?.collect { action ->
|
||||||
it.handle { type ->
|
when (action) {
|
||||||
when (type) {
|
"RefreshRegistrations" -> viewModel.refreshRegistrations()
|
||||||
UiAction.Action.RefreshRegistrations -> viewModel.refreshRegistrations()
|
"UpdatePrismServerConfigured" -> {
|
||||||
UiAction.Action.UpdatePrismServerConfigured -> {
|
|
||||||
viewModel.application?.let { app ->
|
viewModel.application?.let { app ->
|
||||||
val store = AppStore(app)
|
val store = AppStore(app)
|
||||||
viewModel.updatePrismServerConfigured(
|
viewModel.updatePrismServerConfigured(
|
||||||
|
|
@ -96,7 +96,6 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||||
viewModel.refreshRegistrations()
|
viewModel.refreshRegistrations()
|
||||||
|
|
@ -133,7 +132,7 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
RegistrationList(viewModel.registrationsViewModel) {}
|
RegistrationList(viewModel.registrationsViewModel)
|
||||||
}
|
}
|
||||||
if (viewModel.mainUiState.showPermissionDialog) {
|
if (viewModel.mainUiState.showPermissionDialog) {
|
||||||
PermissionsUi {
|
PermissionsUi {
|
||||||
|
|
@ -155,7 +154,7 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (migrationViewModel.state.showMigrations) {
|
if (migrationViewModel.state.canMigrate) {
|
||||||
DistribMigrationUi(migrationViewModel)
|
DistribMigrationUi(migrationViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -166,5 +165,5 @@ fun MainPreview() {
|
||||||
val factory = PreviewFactory(LocalContext.current)
|
val factory = PreviewFactory(LocalContext.current)
|
||||||
val mainVM = viewModel<MainViewModel>(factory = factory)
|
val mainVM = viewModel<MainViewModel>(factory = factory)
|
||||||
val migrationVM = viewModel<DistribMigrationViewModel>(factory = factory)
|
val migrationVM = viewModel<DistribMigrationViewModel>(factory = factory)
|
||||||
MainScreen(mainVM, migrationVM)
|
MainScreen(mainVM, migrationVM, null)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,11 @@ import androidx.compose.ui.unit.dp
|
||||||
import app.lonecloud.prism.R
|
import app.lonecloud.prism.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKey: String) -> Unit) {
|
fun PrismServerConfigButton(
|
||||||
|
currentUrl: String,
|
||||||
|
currentApiKey: String,
|
||||||
|
onConfigure: (url: String, apiKey: String) -> Unit
|
||||||
|
) {
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
|
|
@ -61,6 +65,7 @@ fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKe
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
PrismServerConfigDialog(
|
PrismServerConfigDialog(
|
||||||
initialUrl = currentUrl,
|
initialUrl = currentUrl,
|
||||||
|
initialApiKey = currentApiKey,
|
||||||
onDismiss = { showDialog = false },
|
onDismiss = { showDialog = false },
|
||||||
onSave = { url, apiKey ->
|
onSave = { url, apiKey ->
|
||||||
onConfigure(url, apiKey)
|
onConfigure(url, apiKey)
|
||||||
|
|
@ -73,11 +78,12 @@ fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKe
|
||||||
@Composable
|
@Composable
|
||||||
fun PrismServerConfigDialog(
|
fun PrismServerConfigDialog(
|
||||||
initialUrl: String,
|
initialUrl: String,
|
||||||
|
initialApiKey: String,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onSave: (url: String, apiKey: String) -> Unit
|
onSave: (url: String, apiKey: String) -> Unit
|
||||||
) {
|
) {
|
||||||
var url by remember { mutableStateOf(initialUrl) }
|
var url by remember { mutableStateOf(initialUrl) }
|
||||||
var apiKey by remember { mutableStateOf("") }
|
var apiKey by remember { mutableStateOf(initialApiKey) }
|
||||||
var isTesting by remember { mutableStateOf(false) }
|
var isTesting by remember { mutableStateOf(false) }
|
||||||
var testResult by remember { mutableStateOf<String?>(null) }
|
var testResult by remember { mutableStateOf<String?>(null) }
|
||||||
var showServerChangeWarning by remember { mutableStateOf(false) }
|
var showServerChangeWarning by remember { mutableStateOf(false) }
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,11 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import app.lonecloud.prism.R
|
import app.lonecloud.prism.R
|
||||||
import app.lonecloud.prism.activities.DistribMigrationViewModel
|
|
||||||
import app.lonecloud.prism.activities.PreviewFactory
|
import app.lonecloud.prism.activities.PreviewFactory
|
||||||
import app.lonecloud.prism.activities.SettingsViewModel
|
import app.lonecloud.prism.activities.SettingsViewModel
|
||||||
import app.lonecloud.prism.activities.ThemeViewModel
|
import app.lonecloud.prism.activities.ThemeViewModel
|
||||||
import org.unifiedpush.android.distributor.ui.compose.DistribMigrationUi
|
import org.unifiedpush.android.distributor.ui.compose.DistribMigrationUi
|
||||||
import org.unifiedpush.android.distributor.ui.compose.MigrationPreferences
|
import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
|
|
@ -39,6 +38,7 @@ fun SettingsScreen(
|
||||||
) {
|
) {
|
||||||
PrismServerConfigButton(
|
PrismServerConfigButton(
|
||||||
currentUrl = viewModel.state.prismServerUrl,
|
currentUrl = viewModel.state.prismServerUrl,
|
||||||
|
currentApiKey = viewModel.state.prismApiKey,
|
||||||
onConfigure = { url, apiKey ->
|
onConfigure = { url, apiKey ->
|
||||||
viewModel.updatePrismServerUrl(url)
|
viewModel.updatePrismServerUrl(url)
|
||||||
viewModel.updatePrismApiKey(apiKey)
|
viewModel.updatePrismApiKey(apiKey)
|
||||||
|
|
@ -57,13 +57,11 @@ fun SettingsScreen(
|
||||||
onCheckedChange = { themeViewModel.toggleDynamicColors() }
|
onCheckedChange = { themeViewModel.toggleDynamicColors() }
|
||||||
)
|
)
|
||||||
|
|
||||||
MigrationPreferences(migrationViewModel)
|
|
||||||
|
|
||||||
RestartServicesPreference {
|
RestartServicesPreference {
|
||||||
viewModel.restartService()
|
viewModel.restartService()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (migrationViewModel.state.showMigrations) {
|
if (migrationViewModel.state.canMigrate) {
|
||||||
DistribMigrationUi(migrationViewModel)
|
DistribMigrationUi(migrationViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ sealed class ApiUrlCandidate {
|
||||||
instance.set(New(url))
|
instance.set(New(url))
|
||||||
DatabaseFactory.getDb(context).run {
|
DatabaseFactory.getDb(context).run {
|
||||||
if (countApps() == 0) {
|
if (countApps() == 0) {
|
||||||
// registerApp update the counter which restart the service
|
|
||||||
registerApp(
|
registerApp(
|
||||||
context.packageName,
|
context.packageName,
|
||||||
FAKE_TOKEN,
|
FAKE_TOKEN,
|
||||||
|
|
@ -44,9 +43,7 @@ sealed class ApiUrlCandidate {
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Else
|
|
||||||
SourceManager.setFailOnce()
|
SourceManager.setFailOnce()
|
||||||
// We restart in 1sec if it hasn't been replaced until then, as setFailOnce should call RestartWorker.run
|
|
||||||
RestartWorker.run(context, delay = 1_000)
|
RestartWorker.run(context, delay = 1_000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ object MessageSender {
|
||||||
fun send(context: Context, message: ClientMessage) {
|
fun send(context: Context, message: ClientMessage) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
websocket?.let {
|
websocket?.let {
|
||||||
// Log.d(TAG, "Sending: ${message.serialize()}")
|
|
||||||
Log.d(TAG, "Sending: ${message::class.java.simpleName}")
|
Log.d(TAG, "Sending: ${message::class.java.simpleName}")
|
||||||
message.send(it)
|
message.send(it)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import app.lonecloud.prism.DatabaseFactory
|
||||||
import app.lonecloud.prism.Distributor
|
import app.lonecloud.prism.Distributor
|
||||||
import app.lonecloud.prism.Distributor.sendMessage
|
import app.lonecloud.prism.Distributor.sendMessage
|
||||||
import app.lonecloud.prism.EncryptionKeyStore
|
import app.lonecloud.prism.EncryptionKeyStore
|
||||||
|
import app.lonecloud.prism.R
|
||||||
import app.lonecloud.prism.api.data.ClientMessage
|
import app.lonecloud.prism.api.data.ClientMessage
|
||||||
import app.lonecloud.prism.api.data.ServerMessage
|
import app.lonecloud.prism.api.data.ServerMessage
|
||||||
import app.lonecloud.prism.callback.NetworkCallbackFactory
|
import app.lonecloud.prism.callback.NetworkCallbackFactory
|
||||||
|
|
@ -27,8 +28,7 @@ import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okhttp3.WebSocket
|
import okhttp3.WebSocket
|
||||||
import okhttp3.WebSocketListener
|
import okhttp3.WebSocketListener
|
||||||
import org.unifiedpush.android.distributor.ui.R as LibR
|
import org.unifiedpush.android.distributor.ChannelCreationStatus
|
||||||
import org.unifiedpush.distributor.ChannelCreationStatus
|
|
||||||
|
|
||||||
class ServerConnection(private val context: Context, private val releaseLock: () -> Unit) : WebSocketListener() {
|
class ServerConnection(private val context: Context, private val releaseLock: () -> Unit) : WebSocketListener() {
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ class ServerConnection(private val context: Context, private val releaseLock: ()
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(LibR.string.toast_url_candidate_success, it),
|
context.getString(R.string.toast_url_candidate_success, it),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
@ -239,7 +239,7 @@ class ServerConnection(private val context: Context, private val releaseLock: ()
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
context,
|
||||||
context.getString(LibR.string.toast_url_candidate_fail, url),
|
context.getString(R.string.toast_url_candidate_fail, url),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package app.lonecloud.prism.callback
|
package app.lonecloud.prism.callback
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.unifiedpush.distributor.callback.BatteryCallback
|
import org.unifiedpush.android.distributor.callback.BatteryCallback
|
||||||
import org.unifiedpush.distributor.callback.CallbackFactory
|
import org.unifiedpush.android.distributor.callback.CallbackFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Battery callback - disabled since URGENCY feature is not supported by the Mozilla server
|
* Battery callback - disabled since URGENCY feature is not supported by the Mozilla server
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import android.content.Context
|
||||||
import app.lonecloud.prism.services.MainRegistrationCounter
|
import app.lonecloud.prism.services.MainRegistrationCounter
|
||||||
import app.lonecloud.prism.services.RestartWorker
|
import app.lonecloud.prism.services.RestartWorker
|
||||||
import app.lonecloud.prism.services.SourceManager
|
import app.lonecloud.prism.services.SourceManager
|
||||||
import org.unifiedpush.distributor.callback.CallbackFactory
|
import org.unifiedpush.android.distributor.callback.CallbackFactory
|
||||||
import org.unifiedpush.distributor.callback.NetworkCallback
|
import org.unifiedpush.android.distributor.callback.NetworkCallback
|
||||||
|
|
||||||
object NetworkCallbackFactory : CallbackFactory<NetworkCallbackFactory.MainNetworkCallback>() {
|
object NetworkCallbackFactory : CallbackFactory<NetworkCallbackFactory.MainNetworkCallback>() {
|
||||||
class MainNetworkCallback(val context: Context) : NetworkCallback() {
|
class MainNetworkCallback(val context: Context) : NetworkCallback() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package app.lonecloud.prism.receivers
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import app.lonecloud.prism.AppStore
|
||||||
|
|
||||||
|
class PrismConfigReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val store = AppStore(context)
|
||||||
|
|
||||||
|
when (intent.action) {
|
||||||
|
ACTION_SET_PRISM_SERVER_URL -> {
|
||||||
|
val url = intent.getStringExtra(EXTRA_URL) ?: ""
|
||||||
|
store.prismServerUrl = url
|
||||||
|
}
|
||||||
|
ACTION_SET_PRISM_API_KEY -> {
|
||||||
|
val apiKey = intent.getStringExtra(EXTRA_API_KEY) ?: ""
|
||||||
|
store.prismApiKey = apiKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ACTION_SET_PRISM_SERVER_URL = "app.lonecloud.prism.SET_PRISM_SERVER_URL"
|
||||||
|
const val ACTION_SET_PRISM_API_KEY = "app.lonecloud.prism.SET_PRISM_API_KEY"
|
||||||
|
const val EXTRA_URL = "url"
|
||||||
|
const val EXTRA_API_KEY = "api_key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,19 +6,13 @@ import android.content.Context
|
||||||
import app.lonecloud.prism.AppStore
|
import app.lonecloud.prism.AppStore
|
||||||
import app.lonecloud.prism.Distributor
|
import app.lonecloud.prism.Distributor
|
||||||
import app.lonecloud.prism.callback.NetworkCallbackFactory
|
import app.lonecloud.prism.callback.NetworkCallbackFactory
|
||||||
import org.unifiedpush.distributor.receiver.DistributorReceiver
|
import org.unifiedpush.android.distributor.receiver.DistributorReceiver
|
||||||
|
|
||||||
/**
|
|
||||||
* THIS SERVICE IS USED BY OTHER APPS TO REGISTER
|
|
||||||
*/
|
|
||||||
class RegisterBroadcastReceiver : DistributorReceiver() {
|
class RegisterBroadcastReceiver : DistributorReceiver() {
|
||||||
|
|
||||||
override val distributor = Distributor
|
override val distributor = Distributor
|
||||||
|
|
||||||
override fun isConnected(context: Context): Boolean {
|
override fun isConnected(context: Context): Boolean = true
|
||||||
// We don't have to care about login
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hasInternet(context: Context): Boolean = NetworkCallbackFactory.hasInternet()
|
override fun hasInternet(context: Context): Boolean = NetworkCallbackFactory.hasInternet()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import app.lonecloud.prism.utils.ForegroundNotification
|
||||||
import app.lonecloud.prism.utils.NOTIFICATION_ID_FOREGROUND
|
import app.lonecloud.prism.utils.NOTIFICATION_ID_FOREGROUND
|
||||||
import app.lonecloud.prism.utils.TAG
|
import app.lonecloud.prism.utils.TAG
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import org.unifiedpush.distributor.service.ForegroundService
|
import org.unifiedpush.android.distributor.service.ForegroundService
|
||||||
import org.unifiedpush.distributor.service.ForegroundServiceFactory
|
import org.unifiedpush.android.distributor.service.ForegroundServiceFactory
|
||||||
|
|
||||||
class FgService : ForegroundService() {
|
class FgService : ForegroundService() {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ package app.lonecloud.prism.services
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.lonecloud.prism.DatabaseFactory
|
import app.lonecloud.prism.DatabaseFactory
|
||||||
import app.lonecloud.prism.activities.UiAction
|
|
||||||
import app.lonecloud.prism.utils.ForegroundNotification
|
import app.lonecloud.prism.utils.ForegroundNotification
|
||||||
import org.unifiedpush.distributor.Database
|
import org.unifiedpush.android.distributor.Database
|
||||||
import org.unifiedpush.distributor.RegistrationCounter
|
import org.unifiedpush.android.distributor.RegistrationCounter
|
||||||
|
import org.unifiedpush.android.distributor.ipc.sendUiAction
|
||||||
|
|
||||||
object MainRegistrationCounter : RegistrationCounter() {
|
object MainRegistrationCounter : RegistrationCounter() {
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ object MainRegistrationCounter : RegistrationCounter() {
|
||||||
|
|
||||||
override fun onCountRefreshed(context: Context) {
|
override fun onCountRefreshed(context: Context) {
|
||||||
ForegroundNotification(context).update()
|
ForegroundNotification(context).update()
|
||||||
UiAction.publish(UiAction.Action.RefreshRegistrations)
|
sendUiAction(context, "RefreshRegistrations")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDb(context: Context): Database = DatabaseFactory.getDb(context)
|
override fun getDb(context: Context): Database = DatabaseFactory.getDb(context)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package app.lonecloud.prism.services
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import app.lonecloud.prism.AppStore
|
import app.lonecloud.prism.AppStore
|
||||||
import app.lonecloud.prism.Distributor
|
import app.lonecloud.prism.Distributor
|
||||||
import org.unifiedpush.distributor.MigrationManager as MManager
|
import org.unifiedpush.android.distributor.MigrationManager as MManager
|
||||||
|
|
||||||
class MigrationManager : MManager() {
|
class MigrationManager : MManager() {
|
||||||
override val distrib = Distributor
|
override val distrib = Distributor
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
package app.lonecloud.prism.services
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import app.lonecloud.prism.AppStore
|
||||||
|
import app.lonecloud.prism.DatabaseFactory
|
||||||
|
import app.lonecloud.prism.Distributor
|
||||||
|
import app.lonecloud.prism.PrismServerClient
|
||||||
|
import org.unifiedpush.android.distributor.Database
|
||||||
|
import org.unifiedpush.android.distributor.MigrationManager
|
||||||
|
import org.unifiedpush.android.distributor.SourceManager
|
||||||
|
import org.unifiedpush.android.distributor.UnifiedPushDistributor
|
||||||
|
import org.unifiedpush.android.distributor.WorkerCompanion
|
||||||
|
import org.unifiedpush.android.distributor.data.App
|
||||||
|
import org.unifiedpush.android.distributor.data.Description
|
||||||
|
import org.unifiedpush.android.distributor.ipc.handler.IAccount
|
||||||
|
import org.unifiedpush.android.distributor.ipc.handler.IApi
|
||||||
|
import org.unifiedpush.android.distributor.ipc.handler.IRegistrations
|
||||||
|
import org.unifiedpush.android.distributor.service.ForegroundServiceFactory
|
||||||
|
import org.unifiedpush.android.distributor.service.InternalService
|
||||||
|
import org.unifiedpush.android.distributor.utils.getApplicationIcon
|
||||||
|
|
||||||
|
class PrismInternalService : InternalService() {
|
||||||
|
override val sourceManager: SourceManager<*> = SourceManager
|
||||||
|
override val restartWorker: WorkerCompanion = RestartWorker
|
||||||
|
override val startService: ForegroundServiceFactory = FgService
|
||||||
|
override val migrationManager: org.unifiedpush.android.distributor.MigrationManager = app.lonecloud.prism.services.MigrationManager()
|
||||||
|
override val distributor: UnifiedPushDistributor = Distributor
|
||||||
|
override val db: Database by lazy { DatabaseFactory.getDb(this) }
|
||||||
|
|
||||||
|
private val appStore by lazy { AppStore(this) }
|
||||||
|
|
||||||
|
override var themeDynamicColors: Boolean
|
||||||
|
get() = appStore.dynamicColors
|
||||||
|
set(value) {
|
||||||
|
appStore.dynamicColors = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override var showToasts: Boolean
|
||||||
|
get() = appStore.showToasts
|
||||||
|
set(value) {
|
||||||
|
appStore.showToasts = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDebugInfo(): String = "Prism Distributor"
|
||||||
|
|
||||||
|
override fun runAppMigration() {
|
||||||
|
// No app migration needed for Prism currently
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun account(): IAccount = object : IAccount {
|
||||||
|
override fun get(): String? = null
|
||||||
|
override fun logout() {}
|
||||||
|
override fun login(data: Bundle) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun api(): IApi = object : IApi {
|
||||||
|
override fun newPushServer(url: String?) {
|
||||||
|
// Prism uses fixed Mozilla server, but can be customized
|
||||||
|
}
|
||||||
|
override fun getUrl(): String = appStore.apiUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun registrations() = object : IRegistrations {
|
||||||
|
override fun delete(registrations: List<String>) {
|
||||||
|
registrations.forEach { token ->
|
||||||
|
// Prism's custom logic: delete from Prism server if it's a "target:" app
|
||||||
|
val dbApp = db.listApps().find { it.connectorToken == token }
|
||||||
|
if (dbApp?.description?.startsWith("target:") == true) {
|
||||||
|
val appName = dbApp.title ?: dbApp.packageName
|
||||||
|
PrismServerClient.deleteApp(context, appName)
|
||||||
|
}
|
||||||
|
|
||||||
|
distributor.deleteApp(context, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun list(): List<App> = db
|
||||||
|
.listApps().map {
|
||||||
|
val pm = context.packageManager
|
||||||
|
|
||||||
|
val isManualApp = it.description?.startsWith("target:") == true
|
||||||
|
val targetPackage = if (isManualApp) {
|
||||||
|
it.description?.substringAfter("target:")?.substringBefore("|")?.takeIf { pkg -> pkg.isNotBlank() }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageToResolve = (targetPackage ?: it.packageName) ?: ""
|
||||||
|
val appName = try {
|
||||||
|
val appInfo = pm.getApplicationInfo(packageToResolve, PackageManager.GET_META_DATA)
|
||||||
|
pm.getApplicationLabel(appInfo).toString()
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
packageToResolve
|
||||||
|
}
|
||||||
|
|
||||||
|
val displayTitle = it.title ?: appName
|
||||||
|
|
||||||
|
App(
|
||||||
|
connectorToken = it.connectorToken,
|
||||||
|
packageName = it.packageName,
|
||||||
|
endpoint = it.endpoint,
|
||||||
|
vapidKey = it.vapidKey,
|
||||||
|
title = displayTitle,
|
||||||
|
msgCount = it.msgCount,
|
||||||
|
description = if (it.packageName == context.packageName) {
|
||||||
|
Description.LocalChannel
|
||||||
|
} else {
|
||||||
|
Description.StringDescription(packageToResolve)
|
||||||
|
},
|
||||||
|
icon = getApplicationIcon(packageToResolve)?.toBitmap(),
|
||||||
|
isLocal = it.packageName == context.packageName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun copyEndpoint(token: String?) {
|
||||||
|
super@PrismInternalService.registrations().copyEndpoint(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addLocal(title: String) {
|
||||||
|
super@PrismInternalService.registrations().addLocal(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ import app.lonecloud.prism.Distributor
|
||||||
import app.lonecloud.prism.api.MessageSender
|
import app.lonecloud.prism.api.MessageSender
|
||||||
import app.lonecloud.prism.callback.NetworkCallbackFactory
|
import app.lonecloud.prism.callback.NetworkCallbackFactory
|
||||||
import app.lonecloud.prism.utils.TAG
|
import app.lonecloud.prism.utils.TAG
|
||||||
import org.unifiedpush.distributor.WorkerCompanion
|
import org.unifiedpush.android.distributor.WorkerCompanion
|
||||||
|
|
||||||
class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
|
class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import app.lonecloud.prism.utils.DisconnectedNotification
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.WebSocket
|
import okhttp3.WebSocket
|
||||||
import okio.ByteString
|
import okio.ByteString
|
||||||
import org.unifiedpush.distributor.AppNotification
|
import org.unifiedpush.android.distributor.AppNotification
|
||||||
import org.unifiedpush.distributor.SourceManager as SManager
|
import org.unifiedpush.android.distributor.SourceManager as SManager
|
||||||
|
|
||||||
object SourceManager : SManager<WebSocket>() {
|
object SourceManager : SManager<WebSocket>() {
|
||||||
override val foregroundService = FgService.service
|
override val foregroundService = FgService.service
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,9 @@ import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import app.lonecloud.prism.R
|
import app.lonecloud.prism.R
|
||||||
import app.lonecloud.prism.activities.MainActivity
|
|
||||||
import app.lonecloud.prism.services.MainRegistrationCounter
|
import app.lonecloud.prism.services.MainRegistrationCounter
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import org.unifiedpush.android.distributor.ui.R as LibR
|
import org.unifiedpush.android.distributor.AppNotification
|
||||||
import org.unifiedpush.distributor.AppNotification
|
|
||||||
|
|
||||||
const val NOTIFICATION_ID_FOREGROUND = 51115
|
const val NOTIFICATION_ID_FOREGROUND = 51115
|
||||||
private const val NOTIFICATION_ID_WARNING = 51215
|
private const val NOTIFICATION_ID_WARNING = 51215
|
||||||
|
|
@ -25,16 +23,15 @@ class MainNotificationData(
|
||||||
text = text,
|
text = text,
|
||||||
ticker = ticker,
|
ticker = ticker,
|
||||||
priority = priority,
|
priority = priority,
|
||||||
ongoing = ongoing,
|
ongoing = ongoing
|
||||||
activity = MainActivity::class.java
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val Context.warningChannelData: AppNotification.ChannelData
|
private val Context.warningChannelData: AppNotification.ChannelData
|
||||||
get() = AppNotification.ChannelData(
|
get() = AppNotification.ChannelData(
|
||||||
"Warning",
|
"Warning",
|
||||||
this.getString(LibR.string.warning),
|
this.getString(R.string.warning),
|
||||||
NotificationManager.IMPORTANCE_HIGH,
|
NotificationManager.IMPORTANCE_HIGH,
|
||||||
this.resources.getString(LibR.string.warning_notif_description).format(this.getString(R.string.app_name))
|
this.resources.getString(R.string.warning_notif_description).format(this.getString(R.string.app_name))
|
||||||
)
|
)
|
||||||
|
|
||||||
class DisconnectedNotification(context: Context) :
|
class DisconnectedNotification(context: Context) :
|
||||||
|
|
@ -44,10 +41,10 @@ class DisconnectedNotification(context: Context) :
|
||||||
NOTIFICATION_ID_WARNING,
|
NOTIFICATION_ID_WARNING,
|
||||||
MainNotificationData(
|
MainNotificationData(
|
||||||
context.getString(R.string.app_name),
|
context.getString(R.string.app_name),
|
||||||
context.getString(LibR.string.warning_notif_content).format(
|
context.getString(R.string.warning_notif_content).format(
|
||||||
context.getString(R.string.app_name)
|
context.getString(R.string.app_name)
|
||||||
),
|
),
|
||||||
context.getString(LibR.string.warning),
|
context.getString(R.string.warning),
|
||||||
NotificationCompat.PRIORITY_HIGH,
|
NotificationCompat.PRIORITY_HIGH,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
|
|
@ -63,20 +60,20 @@ class ForegroundNotification(context: Context) :
|
||||||
context.getString(R.string.app_name),
|
context.getString(R.string.app_name),
|
||||||
if (MainRegistrationCounter.oneOrMore(context)) {
|
if (MainRegistrationCounter.oneOrMore(context)) {
|
||||||
MainRegistrationCounter.getCount(context).let {
|
MainRegistrationCounter.getCount(context).let {
|
||||||
context.resources.getQuantityString(LibR.plurals.foreground_notif_content_with_reg, it, it)
|
context.resources.getQuantityString(R.plurals.foreground_notif_content_with_reg, it, it)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
context.getString(LibR.string.foreground_notif_content_no_reg)
|
context.getString(R.string.foreground_notif_content_no_reg)
|
||||||
},
|
},
|
||||||
context.getString(LibR.string.foreground_service),
|
context.getString(R.string.foreground_service),
|
||||||
NotificationCompat.PRIORITY_LOW,
|
NotificationCompat.PRIORITY_LOW,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
ChannelData(
|
ChannelData(
|
||||||
"Foreground",
|
"Foreground",
|
||||||
context.getString(LibR.string.foreground_service),
|
context.getString(R.string.foreground_service),
|
||||||
NotificationManager.IMPORTANCE_LOW,
|
NotificationManager.IMPORTANCE_LOW,
|
||||||
context.getString(LibR.string.foreground_notif_description)
|
context.getString(R.string.foreground_notif_description)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
5
app/src/main/res/drawable/baseline_content_copy_24.xml
Normal file
5
app/src/main/res/drawable/baseline_content_copy_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_android_24dp.xml
Normal file
5
app/src/main/res/drawable/ic_android_24dp.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#999999" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
|
|
@ -1,33 +1,23 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="prism_server_url_label">Prism Server URL</string>
|
<!-- Preview strings for distributor-ui library -->
|
||||||
<string name="prism_server_url_placeholder">https://your-prism-server.com</string>
|
<string name="preview_app_name" translatable="false">Distrib name</string>
|
||||||
<string name="prism_api_key_label">Prism API Key</string>
|
<string name="preview_privacy_policy" translatable="false">Distrib privacy policy\ </string>
|
||||||
<string name="prism_api_key_placeholder">Your API key</string>
|
<string name="preview_in_app_notif_link" translatable="false">on the Internet.</string>
|
||||||
<string name="restart_service_button">Restart Push Service</string>
|
|
||||||
<string name="configure_server">Configure Server</string>
|
|
||||||
<string name="prism_server_configured">Prism Server: %s</string>
|
|
||||||
<string name="prism_server_not_configured">Not configured</string>
|
|
||||||
<string name="debug_title">Debug</string>
|
|
||||||
<string name="add_custom_app_title">Add Custom App</string>
|
|
||||||
<string name="app_name_label">App Name</string>
|
|
||||||
<string name="app_name_placeholder">e.g., notifications</string>
|
|
||||||
<string name="add_button">Add</string>
|
|
||||||
<string name="cancel_button">Cancel</string>
|
|
||||||
<string name="select_target_app_title">Select Target App</string>
|
|
||||||
<string name="search_apps_label">Search apps</string>
|
|
||||||
<string name="search_apps_placeholder">App name or package</string>
|
|
||||||
<string name="testing_connection">Testing connection…</string>
|
|
||||||
<string name="connection_successful">Connection successful!</string>
|
|
||||||
<string name="connection_failed">Connection failed: %s</string>
|
|
||||||
<string name="test_and_save_button">Test & Save</string>
|
|
||||||
<string name="target_app_label">Target App</string>
|
|
||||||
<string name="select_an_app">Select an app</string>
|
|
||||||
<string name="app_dropdown_show_toasts">Notify about new registrations</string>
|
|
||||||
<string name="dynamic_colors_title">Dynamic Colors</string>
|
|
||||||
<string name="add_manual_app_content_description">Add Manual App</string>
|
|
||||||
|
|
||||||
<!-- UnifiedPush library strings (required for translations) -->
|
<!-- Library strings referenced by Prism code -->
|
||||||
|
<string name="toast_url_candidate_fail">Fail to use %1$s</string>
|
||||||
|
<string name="toast_url_candidate_success">Successfully using %1$s</string>
|
||||||
|
<string name="warning">Warning</string>
|
||||||
|
<string name="warning_notif_content">%1$s is disconnected</string>
|
||||||
|
<string name="warning_notif_description">Warn when %1$s is disconnected or an issue occurred.</string>
|
||||||
|
<string name="warning_notif_ticker">Prism Warning</string>
|
||||||
|
<string name="foreground_service">Foreground Service</string>
|
||||||
|
<string name="foreground_notif_description">Notification to run in the foreground</string>
|
||||||
|
<string name="foreground_notif_content_no_reg">Waiting for registration to connect</string>
|
||||||
|
<string name="foreground_notif_ticker">Prism</string>
|
||||||
|
<string name="bar_unregister_title">%d selected</string>
|
||||||
|
<string name="dialog_unregistering_content">Are you sure to delete this registration?</string>
|
||||||
<plurals name="bar_unregister_title">
|
<plurals name="bar_unregister_title">
|
||||||
<item quantity="one">%d selected</item>
|
<item quantity="one">%d selected</item>
|
||||||
<item quantity="other">%d selected</item>
|
<item quantity="other">%d selected</item>
|
||||||
|
|
@ -36,6 +26,38 @@
|
||||||
<item quantity="one">Are you sure to delete this registration?</item>
|
<item quantity="one">Are you sure to delete this registration?</item>
|
||||||
<item quantity="other">Are you sure to delete %d registrations?</item>
|
<item quantity="other">Are you sure to delete %d registrations?</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="foreground_notif_ticker">Foreground Service</string>
|
<plurals name="foreground_notif_content_with_reg">
|
||||||
<string name="warning_notif_ticker">Warning</string>
|
<item quantity="one">Connected for %d registration</item>
|
||||||
|
<item quantity="other">Connected for %d registrations</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<!-- Prism-specific strings -->
|
||||||
|
<string name="app_name">Prism</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
<string name="add_custom_app_title">Add Custom App</string>
|
||||||
|
<string name="app_name_label">App Name</string>
|
||||||
|
<string name="app_name_placeholder">Enter app name</string>
|
||||||
|
<string name="target_app_label">Target App (Optional)</string>
|
||||||
|
<string name="select_an_app">Select an app</string>
|
||||||
|
<string name="add_button">Add</string>
|
||||||
|
<string name="cancel_button">Cancel</string>
|
||||||
|
<string name="select_target_app_title">Select Target App</string>
|
||||||
|
<string name="search_apps_label">Search</string>
|
||||||
|
<string name="search_apps_placeholder">Search for apps</string>
|
||||||
|
<string name="add_manual_app_content_description">Add manual app</string>
|
||||||
|
<string name="debug_title">Debug Information</string>
|
||||||
|
<string name="configure_server">Configure Prism Server</string>
|
||||||
|
<string name="prism_server_configured">Prism server configured</string>
|
||||||
|
<string name="prism_server_not_configured">Prism server not configured</string>
|
||||||
|
<string name="prism_server_url_label">Server URL</string>
|
||||||
|
<string name="prism_server_url_placeholder">https://prism.example.com</string>
|
||||||
|
<string name="prism_api_key_label">API Key</string>
|
||||||
|
<string name="prism_api_key_placeholder">Enter API key</string>
|
||||||
|
<string name="testing_connection">Testing connection…</string>
|
||||||
|
<string name="connection_successful">Connection successful</string>
|
||||||
|
<string name="connection_failed">Connection failed</string>
|
||||||
|
<string name="test_and_save_button">Test and Save</string>
|
||||||
|
<string name="restart_service_button">Restart Service</string>
|
||||||
|
<string name="app_dropdown_show_toasts">Notify when apps register</string>
|
||||||
|
<string name="dynamic_colors_title">Dynamic Colors</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -4,8 +4,9 @@ androidx-activityCompose = "1.12.1"
|
||||||
androidx-lifecycle = "2.10.0"
|
androidx-lifecycle = "2.10.0"
|
||||||
androidx-work = "2.11.0"
|
androidx-work = "2.11.0"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
unifiedpush_distributor = "0.5.6"
|
unifiedpush_distributor = "0.7.1"
|
||||||
unifiedpush_distributor_ui = "0.5.5"
|
unifiedpush_distributor_base = "0.7.0"
|
||||||
|
accompanist_permissions = "0.37.3"
|
||||||
tink = "1.15.0"
|
tink = "1.15.0"
|
||||||
kotlin = "2.2.20"
|
kotlin = "2.2.20"
|
||||||
kotlinx_serializationJson = "1.9.0"
|
kotlinx_serializationJson = "1.9.0"
|
||||||
|
|
@ -29,7 +30,9 @@ androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "u
|
||||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
|
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidx-work" }
|
||||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||||
unifiedpush-distributor = { module = "org.unifiedpush.android:distributor", version.ref = "unifiedpush_distributor" }
|
unifiedpush-distributor = { module = "org.unifiedpush.android:distributor", version.ref = "unifiedpush_distributor" }
|
||||||
unifiedpush-distributor-ui = { module = "org.unifiedpush.android:distributor-ui", version.ref = "unifiedpush_distributor_ui" }
|
unifiedpush-distributor-base = { module = "org.unifiedpush.android:distributor-base", version.ref = "unifiedpush_distributor_base" }
|
||||||
|
unifiedpush-distributor-ui = { module = "org.unifiedpush.android:distributor-ui", version.ref = "unifiedpush_distributor" }
|
||||||
|
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist_permissions" }
|
||||||
tink-android = { module = "com.google.crypto.tink:tink-android", version.ref = "tink" }
|
tink-android = { module = "com.google.crypto.tink:tink-android", version.ref = "tink" }
|
||||||
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx_serializationJson" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx_serializationJson" }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue