Add support for temporary migrations

This commit is contained in:
sim 2025-09-19 16:44:13 +02:00
parent 211458b104
commit fdd7736af3
9 changed files with 126 additions and 18 deletions

View file

@ -20,6 +20,22 @@ class AppStore(context: Context) : Store(context, PREF_NAME) {
.putOrRemove(PREF_API_URL, value)
.apply()
var fallbackIntroShown: Boolean
get() = sharedPreferences
.getBoolean(PREF_FALLBACK_INTRO_SHOWN, false)
set(value) = sharedPreferences
.edit()
.putBoolean(PREF_FALLBACK_INTRO_SHOWN, value)
.apply()
var fallbackService: String?
get() = sharedPreferences
.getString(PREF_FALLBACK_SERVICE, null)
set(value) = sharedPreferences
.edit()
.putOrRemove(PREF_FALLBACK_SERVICE, value)
.apply()
/**
* Show toasts when a new app is registered or an error occurred
*/
@ -39,6 +55,8 @@ class AppStore(context: Context) : Store(context, PREF_NAME) {
internal const val PREF_NAME = "Sunup"
private const val PREF_UAID = "uaid"
private const val PREF_API_URL = "api_url"
private const val PREF_FALLBACK_INTRO_SHOWN = "fallback_intro_shown"
private const val PREF_FALLBACK_SERVICE = "fallback_service"
private const val PREF_SHOW_TOASTS = "show_toasts"
}
}

View file

@ -20,6 +20,9 @@ class AppAction(private val action: Action) {
class NewPushServer(val url: String) : 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()
}
fun handle(context: Context) {
@ -28,6 +31,9 @@ class AppAction(private val action: Action) {
is Action.NewPushServer -> newPushServer(context, action)
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)
}
}
@ -54,6 +60,33 @@ class AppAction(private val action: Action) {
Distributor.deleteApp(context, it)
}
}
private fun fallbackIntroShown(context: Context) {
AppStore(context).fallbackIntroShown = true
}
/**
* 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) {
AppStore(context).fallbackService = action.distributor
action.distributor?.let {
// Fallback is set
if (SourceManager.shouldSendFallback) {
Distributor.sendTempFallbackToAll(context, it)
}
} ?: run {
// Fallback is disabled
Distributor.sendEndpointToAll(context)
}
}
private fun migrateToDistrib(context: Context, action: Action.MigrateToDistrib) {
Distributor.migrateAll(context, action.distributor)
}
}
fun ViewModel.publishAction(action: AppAction) {

View file

@ -13,10 +13,20 @@ import org.unifiedpush.distributor.sunup.BuildConfig
import org.unifiedpush.distributor.sunup.activities.ui.AppBarUiState
import org.unifiedpush.distributor.sunup.utils.TAG
class AppBarViewModel(appBarUiState: AppBarUiState) : ViewModel() {
/**
* Controls AppBar and dialogs open from the app bar
*
* The AppBar controls the MigrationView model because it provides
* the migration entry
*/
class AppBarViewModel(
appBarUiState: AppBarUiState,
val migrationViewModel: DistribMigrationViewModel
) : ViewModel() {
constructor(context: Context) : this(
AppBarUiState.from(context)
AppBarUiState.from(context),
DistribMigrationViewModel(context)
)
var state by mutableStateOf(appBarUiState)

View file

@ -22,4 +22,4 @@ fun Context.applicationRowState(packageName: String, description: String? = null
packageName = packageName,
description = description
)
}
}

View file

@ -0,0 +1,47 @@
package org.unifiedpush.distributor.sunup.activities
import android.content.Context
import org.unifiedpush.android.distributor.ui.compose.DistribMigrationViewModel as UPDistribMigrationViewModel
import org.unifiedpush.android.distributor.ui.compose.state.DistribMigrationState
import org.unifiedpush.distributor.sunup.AppStore
import org.unifiedpush.distributor.utils.listOtherDistributors
class DistribMigrationViewModel(state: DistribMigrationState) : UPDistribMigrationViewModel(state) {
constructor(context: Context) : this(
stateFrom(context)
)
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)
)
}
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
)
}
}
}

View file

@ -41,6 +41,7 @@ class MainViewModel(
viewModelScope.launch {
mainUiState = mainUiState.copy(showPermissionDialog = false)
}
appBarViewModel.migrationViewModel.mayShowFallbackIntro()
}
fun refreshRegistrations(context: Context) {
viewModelScope.launch {

View file

@ -8,25 +8,17 @@ import org.unifiedpush.distributor.sunup.DatabaseFactory
fun getRegistrationListState(context: Context): RegistrationListState {
return RegistrationListState(
list = emptyList<RegistrationState?>()
.toMutableList().also { appList ->
DatabaseFactory.getDb(context).let { db ->
db.listTokens().forEach {
appList.add(
getRegistrationState(context, db, it)
)
}
}
}.filterNotNull()
list = DatabaseFactory.getDb(context).listApps().map { app ->
getRegistrationState(context, app)
}
)
}
fun getRegistrationState(context: Context, db: Database, token: String): RegistrationState? {
val app = db.getAppFromCoToken(token) ?: return null
return RegistrationState(
fun getRegistrationState(context: Context, app: Database.App): RegistrationState {
return RegistrationState(
app = context.applicationRowState(app.packageName, app.description),
msgCount = app.msgCount,
token = token,
token = app.connectorToken,
copyable = false
)
}

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import org.unifiedpush.android.distributor.ui.R as LibR
import org.unifiedpush.android.distributor.ui.compose.DistribMigrationUi
import org.unifiedpush.distributor.sunup.R
import org.unifiedpush.distributor.sunup.activities.AppAction
import org.unifiedpush.distributor.sunup.activities.AppBarViewModel
@ -78,6 +79,7 @@ fun AppBarUi(appBarViewModel: AppBarViewModel) {
onConfirmation = { appBarViewModel.newPushServer(it) }
)
}
DistribMigrationUi(appBarViewModel.migrationViewModel)
}
@Composable

View file

@ -31,8 +31,10 @@ 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.compose.previewRegistrationsViewModel
import org.unifiedpush.android.distributor.ui.compose.state.DistribMigrationState
import org.unifiedpush.distributor.sunup.BuildConfig
import org.unifiedpush.distributor.sunup.activities.AppBarViewModel
import org.unifiedpush.distributor.sunup.activities.DistribMigrationViewModel
import org.unifiedpush.distributor.sunup.activities.MainViewModel
import org.unifiedpush.distributor.sunup.utils.getDebugInfo
@ -132,7 +134,10 @@ fun MainPreview() {
MainUi(
MainViewModel(
MainUiState(),
AppBarViewModel(AppBarUiState(BuildConfig.DEFAULT_API_URL, false)),
AppBarViewModel(
AppBarUiState(BuildConfig.DEFAULT_API_URL, false),
DistribMigrationViewModel(DistribMigrationState())
),
BatteryOptimisationViewModel(true),
previewRegistrationsViewModel()
)