Ask on first run if user wants restore

This commit is contained in:
Torsten Grote 2024-08-02 09:54:50 -03:00
parent cff5d20342
commit 22b3ace3aa
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
7 changed files with 79 additions and 28 deletions

View file

@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.settings
import android.app.Dialog
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.restore.RestoreActivity
class FirstRunFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
isCancelable = false // is what really works, specifying it for the dialog only doesn't
return MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.first_start_text)
.setPositiveButton(R.string.setup_button) { dialog, _ ->
parentFragmentManager.beginTransaction()
.replace(R.id.fragment, SettingsFragment(), null)
.commit()
dialog.dismiss()
}
.setNeutralButton(R.string.restore_backup_button) { dialog, _ ->
val i = Intent(requireContext(), RestoreActivity::class.java)
startActivity(i)
dialog.dismiss()
requireActivity().finish()
}
.setCancelable(false)
.create()
}
}

View file

@ -6,17 +6,14 @@
package com.stevesoltys.seedvault.settings package com.stevesoltys.seedvault.settings
import android.os.Bundle import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.RequireProvisioningActivity import com.stevesoltys.seedvault.ui.RequireProvisioningActivity
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.recoverycode.ARG_FOR_NEW_CODE import com.stevesoltys.seedvault.ui.recoverycode.ARG_FOR_NEW_CODE
import com.stevesoltys.seedvault.ui.storage.StorageCheckFragment import com.stevesoltys.seedvault.ui.storage.StorageCheckFragment
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
internal const val ACTION_APP_STATUS_LIST = "com.stevesoltys.seedvault.APP_STATUS_LIST" internal const val ACTION_APP_STATUS_LIST = "com.stevesoltys.seedvault.APP_STATUS_LIST"
@ -25,7 +22,6 @@ private const val PREF_BACKUP_RECOVERY_CODE = "backup_recovery_code"
class SettingsActivity : RequireProvisioningActivity(), OnPreferenceStartFragmentCallback { class SettingsActivity : RequireProvisioningActivity(), OnPreferenceStartFragmentCallback {
private val viewModel: SettingsViewModel by viewModel() private val viewModel: SettingsViewModel by viewModel()
private val notificationManager: BackupNotificationManager by inject()
override fun getViewModel(): RequireProvisioningViewModel = viewModel override fun getViewModel(): RequireProvisioningViewModel = viewModel
@ -37,8 +33,13 @@ class SettingsActivity : RequireProvisioningActivity(), OnPreferenceStartFragmen
setSupportActionBar(requireViewById(R.id.toolbar)) setSupportActionBar(requireViewById(R.id.toolbar))
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
if (savedInstanceState == null && viewModel.isFirstStart) {
// let user choose whether to restore on first start
FirstRunFragment().show(supportFragmentManager, null)
} else if (savedInstanceState == null) {
// always start with settings fragment as a base (when fresh start) // always start with settings fragment as a base (when fresh start)
if (savedInstanceState == null) showFragment(SettingsFragment()) showFragment(SettingsFragment())
}
// add app status fragment on the stack, if started via intent // add app status fragment on the stack, if started via intent
if (intent?.action == ACTION_APP_STATUS_LIST) { if (intent?.action == ACTION_APP_STATUS_LIST) {
showFragment(AppStatusFragment(), true) showFragment(AppStatusFragment(), true)
@ -58,24 +59,6 @@ class SettingsActivity : RequireProvisioningActivity(), OnPreferenceStartFragmen
} }
} }
@CallSuper
override fun onResume() {
super.onResume()
// Activity results from the parent will get delivered before and might tell us to finish.
// Don't start any new activities when that happens.
// Note: onStart() can get called *before* results get delivered, so we use onResume() here
if (isFinishing) return
// check that backup is provisioned
if (!viewModel.recoveryCodeIsSet()) {
showRecoveryCodeActivity()
} else if (!viewModel.validLocationIsSet()) {
showStorageActivity()
// remove potential error notifications
notificationManager.onBackupErrorSeen()
}
}
override fun onPreferenceStartFragment( override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat, caller: PreferenceFragmentCompat,
pref: Preference, pref: Preference,

View file

@ -30,6 +30,7 @@ import com.stevesoltys.seedvault.permitDiskReads
import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.plugins.StorageProperties import com.stevesoltys.seedvault.plugins.StorageProperties
import com.stevesoltys.seedvault.restore.RestoreActivity import com.stevesoltys.seedvault.restore.RestoreActivity
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.toRelativeTime import com.stevesoltys.seedvault.ui.toRelativeTime
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
@ -42,6 +43,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private val viewModel: SettingsViewModel by sharedViewModel() private val viewModel: SettingsViewModel by sharedViewModel()
private val storagePluginManager: StoragePluginManager by inject() private val storagePluginManager: StoragePluginManager by inject()
private val backupManager: IBackupManager by inject() private val backupManager: IBackupManager by inject()
private val notificationManager: BackupNotificationManager by inject()
private lateinit var backup: TwoStatePreference private lateinit var backup: TwoStatePreference
private lateinit var autoRestore: TwoStatePreference private lateinit var autoRestore: TwoStatePreference
@ -184,6 +186,24 @@ class SettingsFragment : PreferenceFragmentCompat() {
setAppBackupSchedulingSummary(viewModel.appBackupWorkInfo.value) setAppBackupSchedulingSummary(viewModel.appBackupWorkInfo.value)
} }
override fun onResume() {
super.onResume()
// Activity results from the parent will get delivered before and might tell us to finish.
// Don't start any new activities when that happens.
// Note: onStart() can get called *before* results get delivered, so we use onResume() here
if (requireActivity().isFinishing) return
// check that backup is provisioned
val activity = requireActivity() as SettingsActivity
if (!viewModel.recoveryCodeIsSet()) {
activity.showRecoveryCodeActivity()
} else if (!viewModel.validLocationIsSet()) {
activity.showStorageActivity()
// remove potential error notifications
notificationManager.onBackupErrorSeen()
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.settings_menu, menu) inflater.inflate(R.menu.settings_menu, menu)

View file

@ -247,6 +247,14 @@ class SettingsManager(private val context: Context) {
.putBoolean(PREF_KEY_D2D_BACKUPS, enabled) .putBoolean(PREF_KEY_D2D_BACKUPS, enabled)
.apply() .apply()
} }
/**
* This assumes that if there's no storage plugin set, it is the first start.
* We enforce a storage plugin and don't allow unsetting one,
* so this should be a safe assumption.
*/
val isFirstStart get() = prefs.getString(PREF_KEY_STORAGE_PLUGIN, null) == null
} }
data class FlashDrive( data class FlashDrive(

View file

@ -85,6 +85,7 @@ internal class SettingsViewModel(
private val workManager = WorkManager.getInstance(app) private val workManager = WorkManager.getInstance(app)
override val isRestoreOperation = false override val isRestoreOperation = false
val isFirstStart get() = settingsManager.isFirstStart
val isBackupRunning: StateFlow<Boolean> val isBackupRunning: StateFlow<Boolean>
private val mBackupPossible = MutableLiveData(false) private val mBackupPossible = MutableLiveData(false)

View file

@ -56,12 +56,12 @@ abstract class RequireProvisioningActivity : BackupActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
getViewModel().chooseBackupLocation.observeEvent(this, { show -> getViewModel().chooseBackupLocation.observeEvent(this) { show ->
if (show) showStorageActivity() if (show) showStorageActivity()
}) }
} }
protected fun showStorageActivity() { fun showStorageActivity() {
val intent = Intent(this, StorageActivity::class.java).apply { val intent = Intent(this, StorageActivity::class.java).apply {
putExtra(INTENT_EXTRA_IS_RESTORE, getViewModel().isRestoreOperation) putExtra(INTENT_EXTRA_IS_RESTORE, getViewModel().isRestoreOperation)
putExtra(INTENT_EXTRA_IS_SETUP_WIZARD, isSetupWizard) putExtra(INTENT_EXTRA_IS_SETUP_WIZARD, isSetupWizard)
@ -69,7 +69,7 @@ abstract class RequireProvisioningActivity : BackupActivity() {
requestLocation.launch(intent) requestLocation.launch(intent)
} }
protected fun showRecoveryCodeActivity() { fun showRecoveryCodeActivity() {
val intent = Intent(this, RecoveryCodeActivity::class.java).apply { val intent = Intent(this, RecoveryCodeActivity::class.java).apply {
putExtra(INTENT_EXTRA_IS_RESTORE, getViewModel().isRestoreOperation) putExtra(INTENT_EXTRA_IS_RESTORE, getViewModel().isRestoreOperation)
putExtra(INTENT_EXTRA_IS_SETUP_WIZARD, isSetupWizard) putExtra(INTENT_EXTRA_IS_SETUP_WIZARD, isSetupWizard)

View file

@ -9,7 +9,9 @@
<string name="data_management_label">Seedvault Backup</string> <string name="data_management_label">Seedvault Backup</string>
<string name="current_destination_string">Backup status and settings</string> <string name="current_destination_string">Backup status and settings</string>
<string name="first_start_text">Do you want to restore your device from an existing backup or start a new backup?</string>
<string name="restore_backup_button">Restore backup</string> <string name="restore_backup_button">Restore backup</string>
<string name="setup_button">Start new</string>
<!-- Settings --> <!-- Settings -->
<string name="settings_category_apps">App backup</string> <string name="settings_category_apps">App backup</string>