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
befcaf840c
commit
b2ae8c3e0c
39 changed files with 439 additions and 495 deletions
|
|
@ -97,7 +97,9 @@ android {
|
|||
|
||||
dependencies {
|
||||
implementation(libs.unifiedpush.distributor)
|
||||
implementation(libs.unifiedpush.distributor.base)
|
||||
implementation(libs.unifiedpush.distributor.ui)
|
||||
implementation(libs.accompanist.permissions)
|
||||
implementation(libs.tink.android)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:process=":ui"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
|
|
@ -33,7 +34,16 @@
|
|||
android:enabled="true"
|
||||
android:foregroundServiceType="specialUse">
|
||||
<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>
|
||||
|
||||
<receiver
|
||||
|
|
@ -45,6 +55,15 @@
|
|||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
</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
|
||||
android:name=".receivers.RegisterBroadcastReceiver"
|
||||
android:enabled="true"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package app.lonecloud.prism
|
|||
|
||||
import android.content.Context
|
||||
import androidx.core.content.edit
|
||||
import org.unifiedpush.distributor.MigrationManager
|
||||
import org.unifiedpush.distributor.Store
|
||||
import org.unifiedpush.android.distributor.MigrationManager
|
||||
import org.unifiedpush.android.distributor.Store
|
||||
|
||||
class AppStore(context: Context) :
|
||||
Store(context, PREF_NAME),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package app.lonecloud.prism
|
|||
import android.content.Context
|
||||
import app.lonecloud.prism.services.MainRegistrationCounter
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import org.unifiedpush.distributor.Database as Database
|
||||
import org.unifiedpush.android.distributor.Database as Database
|
||||
|
||||
object DatabaseFactory {
|
||||
class MainDatabase(context: Context) : Database(context) {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,11 @@ package app.lonecloud.prism
|
|||
import android.content.Context
|
||||
import app.lonecloud.prism.api.MessageSender
|
||||
import app.lonecloud.prism.api.data.ClientMessage
|
||||
import org.unifiedpush.distributor.Database
|
||||
import org.unifiedpush.distributor.UnifiedPushDistributor
|
||||
import org.unifiedpush.android.distributor.Database
|
||||
import org.unifiedpush.android.distributor.UnifiedPushDistributor
|
||||
|
||||
/**
|
||||
* These functions are used to send messages to other apps
|
||||
*/
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
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.theme.AppTheme
|
||||
import app.lonecloud.prism.services.RestartWorker
|
||||
import app.lonecloud.prism.utils.TAG
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private var jobs: MutableList<Job> = emptyList<Job>().toMutableList()
|
||||
private lateinit var messenger: InternalMessenger
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
RestartWorker.startPeriodic(this)
|
||||
|
||||
messenger = InternalMessenger(this)
|
||||
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
val factory = ViewModelFactory(this.application)
|
||||
val factory = ViewModelFactory(this.application, messenger)
|
||||
val themeViewModel = viewModel<ThemeViewModel>(factory = factory)
|
||||
AppTheme(
|
||||
dynamicColor = themeViewModel.dynamicColors
|
||||
) {
|
||||
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() {
|
||||
Log.d(TAG, "Destroy")
|
||||
jobs.removeAll {
|
||||
it.cancel()
|
||||
true
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,25 +25,31 @@ import app.lonecloud.prism.utils.VapidKeyGenerator
|
|||
import app.lonecloud.prism.utils.WebPushEncryptionKeys
|
||||
import java.util.UUID
|
||||
import kotlinx.coroutines.launch
|
||||
import org.unifiedpush.android.distributor.ui.compose.BatteryOptimisationViewModel
|
||||
import org.unifiedpush.android.distributor.ui.compose.RegistrationsViewModel
|
||||
import org.unifiedpush.android.distributor.ui.compose.state.RegistrationListState
|
||||
import org.unifiedpush.android.distributor.data.App
|
||||
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||
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(
|
||||
mainUiState: MainUiState,
|
||||
val batteryOptimisationViewModel: BatteryOptimisationViewModel,
|
||||
val registrationsViewModel: RegistrationsViewModel,
|
||||
val messenger: InternalMessenger?,
|
||||
val application: Application? = null
|
||||
) : ViewModel() {
|
||||
constructor(application: Application) : this(
|
||||
constructor(requireBatteryOpt: Boolean, messenger: InternalMessenger?, application: Application) : this(
|
||||
mainUiState = MainUiState(
|
||||
prismServerConfigured = !AppStore(application).prismServerUrl.isNullOrBlank() &&
|
||||
!AppStore(application).prismApiKey.isNullOrBlank()
|
||||
),
|
||||
batteryOptimisationViewModel = BatteryOptimisationViewModel(application),
|
||||
batteryOptimisationViewModel = BatteryOptimisationViewModel(requireBatteryOpt, messenger),
|
||||
registrationsViewModel = RegistrationsViewModel(
|
||||
getRegistrationListState(application)
|
||||
RegistrationListState(emptyList<App>()),
|
||||
messenger
|
||||
),
|
||||
messenger,
|
||||
application
|
||||
)
|
||||
|
||||
|
|
@ -69,9 +75,8 @@ class MainViewModel(
|
|||
|
||||
fun refreshRegistrations() {
|
||||
viewModelScope.launch {
|
||||
application?.let {
|
||||
registrationsViewModel.state = getRegistrationListState(it)
|
||||
}
|
||||
val apps = messenger?.sendIMessageL(InternalOpcode.REG_LIST, "apps", App::class.java)
|
||||
registrationsViewModel.state = RegistrationListState(apps ?: emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,9 +84,7 @@ class MainViewModel(
|
|||
viewModelScope.launch {
|
||||
val state = registrationsViewModel.state
|
||||
val tokenList = state.list.filter { it.selected }.map { it.token }
|
||||
publishAction(
|
||||
AppAction(AppAction.Action.DeleteRegistration(tokenList))
|
||||
)
|
||||
messenger?.sendIMessage(InternalOpcode.REG_DELETE, "regs" to tokenList)
|
||||
registrationsViewModel.state = RegistrationListState(
|
||||
list = state.list.filter {
|
||||
!it.selected
|
||||
|
|
@ -130,7 +133,9 @@ class MainViewModel(
|
|||
}
|
||||
|
||||
fun restartService() {
|
||||
publishAction(AppAction(AppAction.Action.RestartService))
|
||||
viewModelScope.launch {
|
||||
messenger?.sendIMessage(InternalOpcode.WORKER_RESTART, 0)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.lonecloud.prism.AppStore
|
||||
import app.lonecloud.prism.PrismServerClient
|
||||
import app.lonecloud.prism.activities.ui.SettingsState
|
||||
import app.lonecloud.prism.receivers.PrismConfigReceiver
|
||||
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() {
|
||||
constructor(application: Application) : this(
|
||||
class SettingsViewModel(
|
||||
state: SettingsState,
|
||||
val messenger: InternalMessenger?,
|
||||
val application: Application? = null
|
||||
) : ViewModel() {
|
||||
constructor(messenger: InternalMessenger?, application: Application) : this(
|
||||
SettingsState.from(application),
|
||||
messenger,
|
||||
application
|
||||
)
|
||||
|
||||
|
|
@ -22,7 +33,8 @@ class SettingsViewModel(state: SettingsState, val application: Application? = nu
|
|||
fun toggleShowToasts() {
|
||||
viewModelScope.launch {
|
||||
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 {
|
||||
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()) {
|
||||
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 {
|
||||
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()) {
|
||||
publishAction(AppAction(AppAction.Action.RegisterPrismServer))
|
||||
PrismServerClient.registerAllApps(it)
|
||||
}
|
||||
|
||||
UiAction.publish(UiAction.Action.UpdatePrismServerConfigured)
|
||||
sendUiAction(it, "UpdatePrismServerConfigured")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 app.lonecloud.prism.AppStore
|
||||
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() {
|
||||
var dynamicColors by mutableStateOf(
|
||||
application?.let { AppStore(it).dynamicColors } ?: false
|
||||
)
|
||||
class ThemeViewModel(val messenger: InternalMessenger?, val application: Application?) : ViewModel() {
|
||||
var dynamicColors by mutableStateOf(application?.let { AppStore(it).dynamicColors } ?: false)
|
||||
|
||||
fun toggleDynamicColors() {
|
||||
viewModelScope.launch {
|
||||
dynamicColors = !dynamicColors
|
||||
application?.run {
|
||||
AppStore(this).dynamicColors = dynamicColors
|
||||
}
|
||||
application?.let { AppStore(it).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.content.Context
|
||||
import android.os.PowerManager
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.SettingsState
|
||||
import org.unifiedpush.android.distributor.ui.compose.BatteryOptimisationViewModel
|
||||
import org.unifiedpush.android.distributor.ui.compose.previewRegistrationsViewModel
|
||||
import org.unifiedpush.android.distributor.ui.compose.state.DistribMigrationState
|
||||
import org.unifiedpush.android.distributor.ipc.InternalMessenger
|
||||
import org.unifiedpush.android.distributor.ui.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")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T = when {
|
||||
modelClass.isAssignableFrom(MainViewModel::class.java) -> MainViewModel(application)
|
||||
modelClass.isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel(
|
||||
application
|
||||
modelClass.isAssignableFrom(MainViewModel::class.java) -> MainViewModel(requireBatteryOptimization, messenger, application)
|
||||
modelClass.isAssignableFrom(SettingsViewModel::class.java) -> SettingsViewModel(messenger, 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}")
|
||||
} as T
|
||||
}
|
||||
|
|
@ -30,8 +37,15 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory {
|
|||
modelClass.isAssignableFrom(MainViewModel::class.java) -> {
|
||||
MainViewModel(
|
||||
MainUiState(),
|
||||
BatteryOptimisationViewModel(true),
|
||||
previewRegistrationsViewModel(context)
|
||||
org.unifiedpush.android.distributor.ui.vm.BatteryOptimisationViewModel(false, null),
|
||||
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) -> {
|
||||
|
|
@ -40,12 +54,18 @@ class PreviewFactory(val context: Context) : ViewModelProvider.Factory {
|
|||
showToasts = false,
|
||||
prismServerUrl = "",
|
||||
prismApiKey = ""
|
||||
)
|
||||
),
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel()
|
||||
modelClass.isAssignableFrom(ThemeViewModel::class.java) -> ThemeViewModel(null, null)
|
||||
modelClass.isAssignableFrom(DistribMigrationViewModel::class.java) -> {
|
||||
DistribMigrationViewModel(DistribMigrationState())
|
||||
DistribMigrationViewModel(
|
||||
DistribMigrationState(),
|
||||
PrismConfig,
|
||||
null
|
||||
)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown ViewModel class")
|
||||
} as T
|
||||
|
|
|
|||
|
|
@ -33,17 +33,17 @@ import androidx.navigation.compose.composable
|
|||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.lonecloud.prism.R
|
||||
import app.lonecloud.prism.activities.DistribMigrationViewModel
|
||||
import app.lonecloud.prism.activities.MainViewModel
|
||||
import app.lonecloud.prism.activities.PreviewFactory
|
||||
import app.lonecloud.prism.activities.SettingsViewModel
|
||||
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.vm.DistribMigrationViewModel
|
||||
|
||||
enum class AppScreen(@param:StringRes val title: Int) {
|
||||
Main(R.string.app_name),
|
||||
Settings(LibR.string.settings)
|
||||
Settings(R.string.settings)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -84,11 +84,13 @@ fun App(
|
|||
themeViewModel: ThemeViewModel = viewModel<ThemeViewModel>(factory = factory),
|
||||
navController: NavHostController = rememberNavController()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uiActionsFlow = subscribeUiActions(context)
|
||||
|
||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentScreen = AppScreen.valueOf(
|
||||
backStackEntry?.destination?.route ?: AppScreen.Main.name
|
||||
)
|
||||
// shared with all views, no need to scope it
|
||||
val migrationViewModel = viewModel<DistribMigrationViewModel>(factory = factory)
|
||||
val mainViewModel = viewModel<MainViewModel>(factory = factory)
|
||||
|
||||
|
|
@ -147,7 +149,8 @@ fun App(
|
|||
) {
|
||||
MainScreen(
|
||||
mainViewModel,
|
||||
migrationViewModel
|
||||
migrationViewModel,
|
||||
uiActionsFlow
|
||||
)
|
||||
}
|
||||
composable(
|
||||
|
|
|
|||
|
|
@ -25,13 +25,9 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
|||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import app.lonecloud.prism.AppStore
|
||||
import app.lonecloud.prism.EventBus
|
||||
import app.lonecloud.prism.R
|
||||
import app.lonecloud.prism.activities.DistribMigrationViewModel
|
||||
import app.lonecloud.prism.activities.MainViewModel
|
||||
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.CardDisableBatteryOptimisation
|
||||
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.RegistrationListHeading
|
||||
import org.unifiedpush.android.distributor.ui.compose.UnregisterBarUi
|
||||
import org.unifiedpush.android.distributor.ui.vm.DistribMigrationViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -69,7 +66,7 @@ fun MainAppBar(onGoToSettings: () -> Unit) {
|
|||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Settings,
|
||||
contentDescription = stringResource(LibR.string.settings)
|
||||
contentDescription = stringResource(R.string.settings)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -77,14 +74,17 @@ fun MainAppBar(onGoToSettings: () -> Unit) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationViewModel) {
|
||||
fun MainScreen(
|
||||
viewModel: MainViewModel,
|
||||
migrationViewModel: DistribMigrationViewModel,
|
||||
uiActionsFlow: kotlinx.coroutines.flow.Flow<String>?
|
||||
) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
LaunchedEffect(Unit) {
|
||||
EventBus.subscribe<UiAction> {
|
||||
it.handle { type ->
|
||||
when (type) {
|
||||
UiAction.Action.RefreshRegistrations -> viewModel.refreshRegistrations()
|
||||
UiAction.Action.UpdatePrismServerConfigured -> {
|
||||
uiActionsFlow?.collect { action ->
|
||||
when (action) {
|
||||
"RefreshRegistrations" -> viewModel.refreshRegistrations()
|
||||
"UpdatePrismServerConfigured" -> {
|
||||
viewModel.application?.let { app ->
|
||||
val store = AppStore(app)
|
||||
viewModel.updatePrismServerConfigured(
|
||||
|
|
@ -96,7 +96,6 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewModel.refreshRegistrations()
|
||||
|
|
@ -133,7 +132,7 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie
|
|||
)
|
||||
}
|
||||
|
||||
RegistrationList(viewModel.registrationsViewModel) {}
|
||||
RegistrationList(viewModel.registrationsViewModel)
|
||||
}
|
||||
if (viewModel.mainUiState.showPermissionDialog) {
|
||||
PermissionsUi {
|
||||
|
|
@ -155,7 +154,7 @@ fun MainScreen(viewModel: MainViewModel, migrationViewModel: DistribMigrationVie
|
|||
}
|
||||
)
|
||||
}
|
||||
if (migrationViewModel.state.showMigrations) {
|
||||
if (migrationViewModel.state.canMigrate) {
|
||||
DistribMigrationUi(migrationViewModel)
|
||||
}
|
||||
}
|
||||
|
|
@ -166,5 +165,5 @@ fun MainPreview() {
|
|||
val factory = PreviewFactory(LocalContext.current)
|
||||
val mainVM = viewModel<MainViewModel>(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
|
||||
|
||||
@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) }
|
||||
|
||||
Surface(
|
||||
|
|
@ -61,6 +65,7 @@ fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKe
|
|||
if (showDialog) {
|
||||
PrismServerConfigDialog(
|
||||
initialUrl = currentUrl,
|
||||
initialApiKey = currentApiKey,
|
||||
onDismiss = { showDialog = false },
|
||||
onSave = { url, apiKey ->
|
||||
onConfigure(url, apiKey)
|
||||
|
|
@ -73,11 +78,12 @@ fun PrismServerConfigButton(currentUrl: String, onConfigure: (url: String, apiKe
|
|||
@Composable
|
||||
fun PrismServerConfigDialog(
|
||||
initialUrl: String,
|
||||
initialApiKey: String,
|
||||
onDismiss: () -> Unit,
|
||||
onSave: (url: String, apiKey: String) -> Unit
|
||||
) {
|
||||
var url by remember { mutableStateOf(initialUrl) }
|
||||
var apiKey by remember { mutableStateOf("") }
|
||||
var apiKey by remember { mutableStateOf(initialApiKey) }
|
||||
var isTesting by remember { mutableStateOf(false) }
|
||||
var testResult by remember { mutableStateOf<String?>(null) }
|
||||
var showServerChangeWarning by remember { mutableStateOf(false) }
|
||||
|
|
|
|||
|
|
@ -14,12 +14,11 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
|||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import app.lonecloud.prism.R
|
||||
import app.lonecloud.prism.activities.DistribMigrationViewModel
|
||||
import app.lonecloud.prism.activities.PreviewFactory
|
||||
import app.lonecloud.prism.activities.SettingsViewModel
|
||||
import app.lonecloud.prism.activities.ThemeViewModel
|
||||
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
|
||||
fun SettingsScreen(
|
||||
|
|
@ -39,6 +38,7 @@ fun SettingsScreen(
|
|||
) {
|
||||
PrismServerConfigButton(
|
||||
currentUrl = viewModel.state.prismServerUrl,
|
||||
currentApiKey = viewModel.state.prismApiKey,
|
||||
onConfigure = { url, apiKey ->
|
||||
viewModel.updatePrismServerUrl(url)
|
||||
viewModel.updatePrismApiKey(apiKey)
|
||||
|
|
@ -57,13 +57,11 @@ fun SettingsScreen(
|
|||
onCheckedChange = { themeViewModel.toggleDynamicColors() }
|
||||
)
|
||||
|
||||
MigrationPreferences(migrationViewModel)
|
||||
|
||||
RestartServicesPreference {
|
||||
viewModel.restartService()
|
||||
}
|
||||
}
|
||||
if (migrationViewModel.state.showMigrations) {
|
||||
if (migrationViewModel.state.canMigrate) {
|
||||
DistribMigrationUi(migrationViewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ sealed class ApiUrlCandidate {
|
|||
instance.set(New(url))
|
||||
DatabaseFactory.getDb(context).run {
|
||||
if (countApps() == 0) {
|
||||
// registerApp update the counter which restart the service
|
||||
registerApp(
|
||||
context.packageName,
|
||||
FAKE_TOKEN,
|
||||
|
|
@ -44,9 +43,7 @@ sealed class ApiUrlCandidate {
|
|||
null
|
||||
)
|
||||
} else {
|
||||
// Else
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ object MessageSender {
|
|||
fun send(context: Context, message: ClientMessage) {
|
||||
synchronized(this) {
|
||||
websocket?.let {
|
||||
// Log.d(TAG, "Sending: ${message.serialize()}")
|
||||
Log.d(TAG, "Sending: ${message::class.java.simpleName}")
|
||||
message.send(it)
|
||||
} ?: run {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import app.lonecloud.prism.DatabaseFactory
|
|||
import app.lonecloud.prism.Distributor
|
||||
import app.lonecloud.prism.Distributor.sendMessage
|
||||
import app.lonecloud.prism.EncryptionKeyStore
|
||||
import app.lonecloud.prism.R
|
||||
import app.lonecloud.prism.api.data.ClientMessage
|
||||
import app.lonecloud.prism.api.data.ServerMessage
|
||||
import app.lonecloud.prism.callback.NetworkCallbackFactory
|
||||
|
|
@ -27,8 +28,7 @@ import okhttp3.Request
|
|||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
import org.unifiedpush.android.distributor.ui.R as LibR
|
||||
import org.unifiedpush.distributor.ChannelCreationStatus
|
||||
import org.unifiedpush.android.distributor.ChannelCreationStatus
|
||||
|
||||
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 {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(LibR.string.toast_url_candidate_success, it),
|
||||
context.getString(R.string.toast_url_candidate_success, it),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
|
@ -239,7 +239,7 @@ class ServerConnection(private val context: Context, private val releaseLock: ()
|
|||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(LibR.string.toast_url_candidate_fail, url),
|
||||
context.getString(R.string.toast_url_candidate_fail, url),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package app.lonecloud.prism.callback
|
||||
|
||||
import android.content.Context
|
||||
import org.unifiedpush.distributor.callback.BatteryCallback
|
||||
import org.unifiedpush.distributor.callback.CallbackFactory
|
||||
import org.unifiedpush.android.distributor.callback.BatteryCallback
|
||||
import org.unifiedpush.android.distributor.callback.CallbackFactory
|
||||
|
||||
/**
|
||||
* 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.RestartWorker
|
||||
import app.lonecloud.prism.services.SourceManager
|
||||
import org.unifiedpush.distributor.callback.CallbackFactory
|
||||
import org.unifiedpush.distributor.callback.NetworkCallback
|
||||
import org.unifiedpush.android.distributor.callback.CallbackFactory
|
||||
import org.unifiedpush.android.distributor.callback.NetworkCallback
|
||||
|
||||
object NetworkCallbackFactory : CallbackFactory<NetworkCallbackFactory.MainNetworkCallback>() {
|
||||
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.Distributor
|
||||
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() {
|
||||
|
||||
override val distributor = Distributor
|
||||
|
||||
override fun isConnected(context: Context): Boolean {
|
||||
// We don't have to care about login
|
||||
return true
|
||||
}
|
||||
override fun isConnected(context: Context): Boolean = true
|
||||
|
||||
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.TAG
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import org.unifiedpush.distributor.service.ForegroundService
|
||||
import org.unifiedpush.distributor.service.ForegroundServiceFactory
|
||||
import org.unifiedpush.android.distributor.service.ForegroundService
|
||||
import org.unifiedpush.android.distributor.service.ForegroundServiceFactory
|
||||
|
||||
class FgService : ForegroundService() {
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ package app.lonecloud.prism.services
|
|||
|
||||
import android.content.Context
|
||||
import app.lonecloud.prism.DatabaseFactory
|
||||
import app.lonecloud.prism.activities.UiAction
|
||||
import app.lonecloud.prism.utils.ForegroundNotification
|
||||
import org.unifiedpush.distributor.Database
|
||||
import org.unifiedpush.distributor.RegistrationCounter
|
||||
import org.unifiedpush.android.distributor.Database
|
||||
import org.unifiedpush.android.distributor.RegistrationCounter
|
||||
import org.unifiedpush.android.distributor.ipc.sendUiAction
|
||||
|
||||
object MainRegistrationCounter : RegistrationCounter() {
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ object MainRegistrationCounter : RegistrationCounter() {
|
|||
|
||||
override fun onCountRefreshed(context: Context) {
|
||||
ForegroundNotification(context).update()
|
||||
UiAction.publish(UiAction.Action.RefreshRegistrations)
|
||||
sendUiAction(context, "RefreshRegistrations")
|
||||
}
|
||||
|
||||
override fun getDb(context: Context): Database = DatabaseFactory.getDb(context)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package app.lonecloud.prism.services
|
|||
import android.content.Context
|
||||
import app.lonecloud.prism.AppStore
|
||||
import app.lonecloud.prism.Distributor
|
||||
import org.unifiedpush.distributor.MigrationManager as MManager
|
||||
import org.unifiedpush.android.distributor.MigrationManager as MManager
|
||||
|
||||
class MigrationManager : MManager() {
|
||||
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.callback.NetworkCallbackFactory
|
||||
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) {
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import app.lonecloud.prism.utils.DisconnectedNotification
|
|||
import okhttp3.Request
|
||||
import okhttp3.WebSocket
|
||||
import okio.ByteString
|
||||
import org.unifiedpush.distributor.AppNotification
|
||||
import org.unifiedpush.distributor.SourceManager as SManager
|
||||
import org.unifiedpush.android.distributor.AppNotification
|
||||
import org.unifiedpush.android.distributor.SourceManager as SManager
|
||||
|
||||
object SourceManager : SManager<WebSocket>() {
|
||||
override val foregroundService = FgService.service
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@ import android.app.NotificationManager
|
|||
import android.content.Context
|
||||
import androidx.core.app.NotificationCompat
|
||||
import app.lonecloud.prism.R
|
||||
import app.lonecloud.prism.activities.MainActivity
|
||||
import app.lonecloud.prism.services.MainRegistrationCounter
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import org.unifiedpush.android.distributor.ui.R as LibR
|
||||
import org.unifiedpush.distributor.AppNotification
|
||||
import org.unifiedpush.android.distributor.AppNotification
|
||||
|
||||
const val NOTIFICATION_ID_FOREGROUND = 51115
|
||||
private const val NOTIFICATION_ID_WARNING = 51215
|
||||
|
|
@ -25,16 +23,15 @@ class MainNotificationData(
|
|||
text = text,
|
||||
ticker = ticker,
|
||||
priority = priority,
|
||||
ongoing = ongoing,
|
||||
activity = MainActivity::class.java
|
||||
ongoing = ongoing
|
||||
)
|
||||
|
||||
private val Context.warningChannelData: AppNotification.ChannelData
|
||||
get() = AppNotification.ChannelData(
|
||||
"Warning",
|
||||
this.getString(LibR.string.warning),
|
||||
this.getString(R.string.warning),
|
||||
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) :
|
||||
|
|
@ -44,10 +41,10 @@ class DisconnectedNotification(context: Context) :
|
|||
NOTIFICATION_ID_WARNING,
|
||||
MainNotificationData(
|
||||
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(LibR.string.warning),
|
||||
context.getString(R.string.warning),
|
||||
NotificationCompat.PRIORITY_HIGH,
|
||||
true
|
||||
),
|
||||
|
|
@ -63,20 +60,20 @@ class ForegroundNotification(context: Context) :
|
|||
context.getString(R.string.app_name),
|
||||
if (MainRegistrationCounter.oneOrMore(context)) {
|
||||
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 {
|
||||
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,
|
||||
true
|
||||
),
|
||||
ChannelData(
|
||||
"Foreground",
|
||||
context.getString(LibR.string.foreground_service),
|
||||
context.getString(R.string.foreground_service),
|
||||
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"?>
|
||||
<resources>
|
||||
<string name="prism_server_url_label">Prism Server URL</string>
|
||||
<string name="prism_server_url_placeholder">https://your-prism-server.com</string>
|
||||
<string name="prism_api_key_label">Prism API Key</string>
|
||||
<string name="prism_api_key_placeholder">Your API key</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>
|
||||
<!-- Preview strings for distributor-ui library -->
|
||||
<string name="preview_app_name" translatable="false">Distrib name</string>
|
||||
<string name="preview_privacy_policy" translatable="false">Distrib privacy policy\ </string>
|
||||
<string name="preview_in_app_notif_link" translatable="false">on the Internet.</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">
|
||||
<item quantity="one">%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="other">Are you sure to delete %d registrations?</item>
|
||||
</plurals>
|
||||
<string name="foreground_notif_ticker">Foreground Service</string>
|
||||
<string name="warning_notif_ticker">Warning</string>
|
||||
<plurals name="foreground_notif_content_with_reg">
|
||||
<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>
|
||||
|
|
@ -4,8 +4,9 @@ androidx-activityCompose = "1.12.1"
|
|||
androidx-lifecycle = "2.10.0"
|
||||
androidx-work = "2.11.0"
|
||||
appcompat = "1.7.1"
|
||||
unifiedpush_distributor = "0.5.6"
|
||||
unifiedpush_distributor_ui = "0.5.5"
|
||||
unifiedpush_distributor = "0.7.1"
|
||||
unifiedpush_distributor_base = "0.7.0"
|
||||
accompanist_permissions = "0.37.3"
|
||||
tink = "1.15.0"
|
||||
kotlin = "2.2.20"
|
||||
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" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||
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" }
|
||||
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" }
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue