diff --git a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt index 60d0ece9e..6c0fbdf54 100644 --- a/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt +++ b/app/src/androidTest/java/com/stevesoltys/seedvault/KoinInstrumentationTestApp.kt @@ -57,6 +57,7 @@ class KoinInstrumentationTestApp : App() { iconManager = get(), storageBackup = get(), pluginManager = get(), + fileSelectionManager = get(), ) ) currentRestoreViewModel!! diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index 9389eb058..0189cbe7a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -28,8 +28,8 @@ import com.stevesoltys.seedvault.metadata.metadataModule import com.stevesoltys.seedvault.plugins.StoragePluginManager import com.stevesoltys.seedvault.plugins.saf.storagePluginModuleSaf import com.stevesoltys.seedvault.plugins.webdav.storagePluginModuleWebDav -import com.stevesoltys.seedvault.restore.RestoreViewModel import com.stevesoltys.seedvault.restore.install.installModule +import com.stevesoltys.seedvault.restore.restoreUiModule import com.stevesoltys.seedvault.settings.AppListRetriever import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsViewModel @@ -37,7 +37,6 @@ import com.stevesoltys.seedvault.storage.storageModule import com.stevesoltys.seedvault.transport.TRANSPORT_ID import com.stevesoltys.seedvault.transport.backup.backupModule import com.stevesoltys.seedvault.transport.restore.restoreModule -import com.stevesoltys.seedvault.ui.files.FileSelectionViewModel import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.recoverycode.RecoveryCodeViewModel import com.stevesoltys.seedvault.ui.storage.BackupStorageViewModel @@ -97,20 +96,6 @@ open class App : Application() { ) } viewModel { RestoreStorageViewModel(this@App, get(), get(), get(), get()) } - viewModel { - RestoreViewModel( - app = this@App, - settingsManager = get(), - keyManager = get(), - backupManager = get(), - restoreCoordinator = get(), - apkRestore = get(), - iconManager = get(), - storageBackup = get(), - pluginManager = get(), - ) - } - viewModel { FileSelectionViewModel(this@App, get()) } } override fun onCreate() { @@ -155,6 +140,7 @@ open class App : Application() { installModule, storageModule, workerModule, + restoreUiModule, appModule ) diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/FilesSelectionFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/FilesSelectionFragment.kt new file mode 100644 index 000000000..656be0a94 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/FilesSelectionFragment.kt @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2024 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.restore + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewStub +import android.widget.Button +import com.stevesoltys.seedvault.R +import org.calyxos.backup.storage.ui.restore.FileSelectionFragment +import org.calyxos.backup.storage.ui.restore.FilesItem +import org.koin.androidx.viewmodel.ext.android.sharedViewModel + +internal class FilesSelectionFragment : FileSelectionFragment() { + + override val viewModel: RestoreViewModel by sharedViewModel() + private lateinit var button: Button + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View { + val v = super.onCreateView(inflater, container, savedInstanceState) + val topStub: ViewStub = v.requireViewById(R.id.topStub) + topStub.layoutResource = R.layout.header_files_selection + topStub.inflate() + val bottomStub: ViewStub = v.requireViewById(R.id.bottomStub) + bottomStub.layoutResource = R.layout.footer_files_selection + button = bottomStub.inflate() as Button + button.setOnClickListener { + viewModel.startFilesRestore() + } + return v + } + + override fun onFileItemsChanged(filesItems: List) { + slideUpInRootView(button) + } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt index 5b5c30bdf..8f99db587 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreActivity.kt @@ -12,6 +12,7 @@ import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES_STARTED +import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_SELECT_FILES import com.stevesoltys.seedvault.restore.DisplayFragment.SELECT_APPS import com.stevesoltys.seedvault.restore.install.InstallProgressFragment import com.stevesoltys.seedvault.ui.RequireProvisioningActivity @@ -35,7 +36,12 @@ class RestoreActivity : RequireProvisioningActivity() { RESTORE_APPS -> showFragment(InstallProgressFragment()) RESTORE_BACKUP -> showFragment(RestoreProgressFragment()) RESTORE_FILES -> showFragment(RestoreFilesFragment()) - RESTORE_FILES_STARTED -> showFragment(RestoreFilesStartedFragment()) + RESTORE_SELECT_FILES -> showFragment(FilesSelectionFragment(), true) + RESTORE_FILES_STARTED -> { + // pop back stack, so back navigation doesn't bring us to RESTORE_SELECT_FILES + supportFragmentManager.popBackStackImmediate() + showFragment(RestoreFilesStartedFragment()) + } else -> throw AssertionError() } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt index 281d508e5..e3e9187b7 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreFilesFragment.kt @@ -47,7 +47,7 @@ internal class RestoreFilesFragment : SnapshotFragment() { } override fun onSnapshotClicked(item: SnapshotItem) { - viewModel.startFilesRestore(item) + viewModel.selectFilesForRestore(item) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreUiModule.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreUiModule.kt new file mode 100644 index 000000000..41abcddff --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreUiModule.kt @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2020 The Calyx Institute + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.stevesoltys.seedvault.restore + +import com.stevesoltys.seedvault.ui.files.FileSelectionViewModel +import org.calyxos.backup.storage.ui.restore.FileSelectionManager +import org.koin.android.ext.koin.androidApplication +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val restoreUiModule = module { + single { FileSelectionManager() } + viewModel { + RestoreViewModel( + app = androidApplication(), + settingsManager = get(), + keyManager = get(), + backupManager = get(), + restoreCoordinator = get(), + apkRestore = get(), + iconManager = get(), + storageBackup = get(), + pluginManager = get(), + fileSelectionManager = get(), + ) + } + viewModel { FileSelectionViewModel(androidApplication(), get()) } +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index 0912fbe2c..22af1365d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -23,6 +23,7 @@ import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_FILES_STARTED +import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_SELECT_FILES import com.stevesoltys.seedvault.restore.DisplayFragment.SELECT_APPS import com.stevesoltys.seedvault.restore.install.ApkRestore import com.stevesoltys.seedvault.restore.install.InstallIntentCreator @@ -44,8 +45,10 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.calyxos.backup.storage.api.SnapshotItem import org.calyxos.backup.storage.api.StorageBackup +import org.calyxos.backup.storage.api.StoredSnapshot import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_TIMESTAMP_START import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_USER_ID +import org.calyxos.backup.storage.ui.restore.FileSelectionManager import org.calyxos.backup.storage.ui.restore.SnapshotViewModel import java.util.LinkedList @@ -63,6 +66,7 @@ internal class RestoreViewModel( private val iconManager: IconManager, storageBackup: StorageBackup, pluginManager: StoragePluginManager, + override val fileSelectionManager: FileSelectionManager, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO, ) : RequireProvisioningViewModel(app, settingsManager, keyManager, pluginManager), RestorableBackupClickListener, SnapshotViewModel { @@ -98,6 +102,7 @@ internal class RestoreViewModel( get() = appDataRestoreManager.restoreBackupResult override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher) + private var storedSnapshot: StoredSnapshot? = null internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) { val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) -> @@ -179,12 +184,22 @@ internal class RestoreViewModel( } @UiThread - internal fun startFilesRestore(item: SnapshotItem) { + internal fun selectFilesForRestore(item: SnapshotItem) { + val snapshot = item.snapshot ?: error("${item.storedSnapshot} had null snapshot") + fileSelectionManager.onSnapshotChosen(snapshot) + storedSnapshot = item.storedSnapshot + mDisplayFragment.setEvent(RESTORE_SELECT_FILES) + } + + @UiThread + internal fun startFilesRestore() { + val storedSnapshot = this.storedSnapshot ?: error("No snapshot stored") val i = Intent(app, StorageRestoreService::class.java) - i.putExtra(EXTRA_USER_ID, item.storedSnapshot.userId) - i.putExtra(EXTRA_TIMESTAMP_START, item.time) + i.putExtra(EXTRA_USER_ID, storedSnapshot.userId) + i.putExtra(EXTRA_TIMESTAMP_START, storedSnapshot.timestamp) app.startForegroundService(i) mDisplayFragment.setEvent(RESTORE_FILES_STARTED) + this.storedSnapshot = null } } @@ -206,5 +221,10 @@ internal class RestoreBackupResult(val errorMsg: String? = null) { } internal enum class DisplayFragment { - SELECT_APPS, RESTORE_APPS, RESTORE_BACKUP, RESTORE_FILES, RESTORE_FILES_STARTED + SELECT_APPS, + RESTORE_APPS, + RESTORE_BACKUP, + RESTORE_FILES, + RESTORE_SELECT_FILES, + RESTORE_FILES_STARTED, } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt index cf1fd6107..c69ec7a0d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt @@ -224,12 +224,13 @@ internal class SettingsViewModel( internal fun backupNow() { viewModelScope.launch(Dispatchers.IO) { + val isAppBackupEnabled = backupManager.isBackupEnabled if (settingsManager.isStorageBackupEnabled()) { val i = Intent(app, StorageBackupService::class.java) - // this starts an app backup afterwards - i.putExtra(EXTRA_START_APP_BACKUP, true) + // this starts an app backup afterwards (if enabled) + i.putExtra(EXTRA_START_APP_BACKUP, isAppBackupEnabled) startForegroundService(app, i) - } else { + } else if (isAppBackupEnabled) { AppBackupWorker.scheduleNow(app, reschedule = !pluginManager.isOnRemovableDrive) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt index ae32f4b26..8675483a5 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt @@ -18,6 +18,7 @@ import org.calyxos.backup.storage.backup.BackupService import org.calyxos.backup.storage.backup.NotificationBackupObserver import org.calyxos.backup.storage.restore.NotificationRestoreObserver import org.calyxos.backup.storage.restore.RestoreService +import org.calyxos.backup.storage.ui.restore.FileSelectionManager import org.koin.android.ext.android.inject /* @@ -70,6 +71,7 @@ internal class StorageBackupService : BackupService() { internal class StorageRestoreService : RestoreService() { override val storageBackup: StorageBackup by inject() + override val fileSelectionManager: FileSelectionManager by inject() // use lazy delegate because context isn't available during construction time override val restoreObserver: RestoreObserver by lazy { diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt index 021842118..72114e87a 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/BackupActivity.kt @@ -24,8 +24,7 @@ abstract class BackupActivity : AppCompatActivity() { protected fun showFragment(f: Fragment, addToBackStack: Boolean = false, tag: String? = null) { supportFragmentManager.beginTransaction().apply { - if (tag == null) replace(R.id.fragment, f) - else replace(R.id.fragment, f, tag) + replace(R.id.fragment, f, tag) if (addToBackStack) addToBackStack(null) commit() } diff --git a/app/src/main/res/layout/footer_files_selection.xml b/app/src/main/res/layout/footer_files_selection.xml new file mode 100644 index 000000000..aa92e3a34 --- /dev/null +++ b/app/src/main/res/layout/footer_files_selection.xml @@ -0,0 +1,16 @@ + + diff --git a/app/src/main/res/layout/fragment_restore_app_selection.xml b/app/src/main/res/layout/fragment_restore_app_selection.xml index 07f215d3e..0d627ed8f 100644 --- a/app/src/main/res/layout/fragment_restore_app_selection.xml +++ b/app/src/main/res/layout/fragment_restore_app_selection.xml @@ -108,10 +108,6 @@ android:layout_marginTop="0dp" android:layout_marginEnd="0dp" app:layout_behavior="@string/appbar_scrolling_view_behavior" - app:layout_constraintBottom_toTopOf="@+id/button" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/backupNameView" tools:listitem="@layout/list_item_app_status" />