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) .putOrRemove(PREF_API_URL, value)
.apply() .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 * 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" internal const val PREF_NAME = "Sunup"
private const val PREF_UAID = "uaid" private const val PREF_UAID = "uaid"
private const val PREF_API_URL = "api_url" 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" 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 NewPushServer(val url: String) : Action()
class ShowToasts(val enable: Boolean) : Action() class ShowToasts(val enable: Boolean) : Action()
class DeleteRegistration(val registrations: List<String>) : 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) { fun handle(context: Context) {
@ -28,6 +31,9 @@ class AppAction(private val action: Action) {
is Action.NewPushServer -> newPushServer(context, action) is Action.NewPushServer -> newPushServer(context, action)
is Action.ShowToasts -> showToasts(context, action) is Action.ShowToasts -> showToasts(context, action)
is Action.DeleteRegistration -> deleteRegistration(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) 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) { 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.activities.ui.AppBarUiState
import org.unifiedpush.distributor.sunup.utils.TAG 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( constructor(context: Context) : this(
AppBarUiState.from(context) AppBarUiState.from(context),
DistribMigrationViewModel(context)
) )
var state by mutableStateOf(appBarUiState) var state by mutableStateOf(appBarUiState)

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 { viewModelScope.launch {
mainUiState = mainUiState.copy(showPermissionDialog = false) mainUiState = mainUiState.copy(showPermissionDialog = false)
} }
appBarViewModel.migrationViewModel.mayShowFallbackIntro()
} }
fun refreshRegistrations(context: Context) { fun refreshRegistrations(context: Context) {
viewModelScope.launch { viewModelScope.launch {

View file

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

View file

@ -19,6 +19,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import org.unifiedpush.android.distributor.ui.R as LibR 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.R
import org.unifiedpush.distributor.sunup.activities.AppAction import org.unifiedpush.distributor.sunup.activities.AppAction
import org.unifiedpush.distributor.sunup.activities.AppBarViewModel import org.unifiedpush.distributor.sunup.activities.AppBarViewModel
@ -78,6 +79,7 @@ fun AppBarUi(appBarViewModel: AppBarViewModel) {
onConfirmation = { appBarViewModel.newPushServer(it) } onConfirmation = { appBarViewModel.newPushServer(it) }
) )
} }
DistribMigrationUi(appBarViewModel.migrationViewModel)
} }
@Composable @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.RegistrationListHeading
import org.unifiedpush.android.distributor.ui.compose.UnregisterBarUi import org.unifiedpush.android.distributor.ui.compose.UnregisterBarUi
import org.unifiedpush.android.distributor.ui.compose.previewRegistrationsViewModel 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.BuildConfig
import org.unifiedpush.distributor.sunup.activities.AppBarViewModel 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.activities.MainViewModel
import org.unifiedpush.distributor.sunup.utils.getDebugInfo import org.unifiedpush.distributor.sunup.utils.getDebugInfo
@ -132,7 +134,10 @@ fun MainPreview() {
MainUi( MainUi(
MainViewModel( MainViewModel(
MainUiState(), MainUiState(),
AppBarViewModel(AppBarUiState(BuildConfig.DEFAULT_API_URL, false)), AppBarViewModel(
AppBarUiState(BuildConfig.DEFAULT_API_URL, false),
DistribMigrationViewModel(DistribMigrationState())
),
BatteryOptimisationViewModel(true), BatteryOptimisationViewModel(true),
previewRegistrationsViewModel() previewRegistrationsViewModel()
) )