Skip to content

Commit

Permalink
WIP Files Backup Check UI
Browse files Browse the repository at this point in the history
  • Loading branch information
grote committed Nov 21, 2024
1 parent 1be2cd0 commit 825fcd7
Show file tree
Hide file tree
Showing 17 changed files with 649 additions and 14 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@
android:label="@string/notification_checking_finished_title"
android:launchMode="singleTask"/>

<activity
android:name=".ui.check.FileCheckResultActivity"
android:launchMode="singleTask"/>

<service
android:name=".transport.ConfigurableBackupTransportService"
android:exported="false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import com.stevesoltys.seedvault.worker.AppBackupWorker
import com.stevesoltys.seedvault.worker.AppBackupWorker.Companion.UNIQUE_WORK_NAME
import com.stevesoltys.seedvault.worker.AppCheckerWorker
import com.stevesoltys.seedvault.worker.BackupRequester.Companion.requestFilesAndAppBackup
import com.stevesoltys.seedvault.worker.FileCheckerWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
Expand Down Expand Up @@ -90,6 +91,8 @@ internal class SettingsViewModel(

private val mBackupSize = MutableLiveData<Long>()
val backupSize: LiveData<Long> = mBackupSize
private val mFilesBackupSize = MutableLiveData<Long>()
val filesBackupSize: LiveData<Long> = mFilesBackupSize

internal val lastBackupTime = settingsManager.lastBackupTime
internal val appBackupWorkInfo =
Expand Down Expand Up @@ -329,10 +332,20 @@ internal class SettingsViewModel(
}
}

fun loadFileBackupSize() {
viewModelScope.launch(Dispatchers.IO) {
mFilesBackupSize.postValue(storageBackup.getBackupSize())
}
}

fun checkAppBackups(percent: Int) {
AppCheckerWorker.scheduleNow(app, percent)
}

fun checkFileBackups(percent: Int) {
FileCheckerWorker.scheduleNow(app, percent)
}

fun onLogcatUriReceived(uri: Uri?) = viewModelScope.launch(Dispatchers.IO) {
if (uri == null) {
onLogcatError()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsViewModel
import org.koin.androidx.viewmodel.ext.android.activityViewModel

private const val WARN_PERCENT = 25
private const val WARN_BYTES = 1024 * 1024 * 1024 // 1 GB
internal const val WARN_PERCENT = 25
internal const val WARN_BYTES = 1024 * 1024 * 1024 // 1 GB

class AppCheckFragment : Fragment() {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.ui.check

import android.os.Bundle
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ScrollView
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.settings.SettingsViewModel
import org.koin.androidx.viewmodel.ext.android.activityViewModel

class FileCheckFragment : Fragment() {

private val viewModel: SettingsViewModel by activityViewModel()
private lateinit var sliderLabel: TextView

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
val v = inflater.inflate(R.layout.fragment_app_check, container, false) as ScrollView

v.requireViewById<TextView>(R.id.titleView).setText(R.string.settings_file_check_title)
v.requireViewById<TextView>(R.id.descriptionView).setText(R.string.settings_file_check_text)
v.requireViewById<TextView>(R.id.introView).setText(R.string.settings_file_check_text2)

val slider = v.requireViewById<Slider>(R.id.slider)
sliderLabel = v.requireViewById(R.id.sliderLabel)

// label not scrolling will be fixed in material-components 1.12.0 (next update)
slider.setLabelFormatter { value ->
viewModel.filesBackupSize.value?.let {
Formatter.formatShortFileSize(context, (it * value / 100).toLong())
} ?: "${value.toInt()}%"
}
slider.addOnChangeListener { _, value, _ ->
onSliderChanged(value)
}

viewModel.filesBackupSize.observe(viewLifecycleOwner) {
if (it != null) {
slider.labelBehavior = LabelFormatter.LABEL_VISIBLE
slider.invalidate()
onSliderChanged(slider.value)
}
// we can stop observing as the loaded size won't change again
viewModel.filesBackupSize.removeObservers(viewLifecycleOwner)
}

v.requireViewById<Button>(R.id.startButton).setOnClickListener {
viewModel.checkFileBackups(slider.value.toInt())
requireActivity().onBackPressedDispatcher.onBackPressed()
}
return v
}

override fun onStart() {
super.onStart()
viewModel.loadFileBackupSize()
}

private fun onSliderChanged(value: Float) {
val size = viewModel.filesBackupSize.value
// when size is unknown, we show warning based on percent
val showWarning = if (size == null) {
value > WARN_PERCENT
} else {
size * value / 100 > WARN_BYTES
}
// only update label visibility when different from before
val newVisibility = if (showWarning) View.VISIBLE else View.GONE
if (sliderLabel.visibility != newVisibility) {
sliderLabel.visibility = newVisibility
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.ui.check

import android.os.Bundle
import com.stevesoltys.seedvault.ui.setupEdgeToEdge
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.ui.check.CheckResultActivity
import org.koin.android.ext.android.inject

class FileCheckResultActivity : CheckResultActivity() {

override val storageBackup: StorageBackup by inject()

override fun onCreate(savedInstanceState: Bundle?) {
setupEdgeToEdge()
super.onCreate(savedInstanceState)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package com.stevesoltys.seedvault.worker

import android.content.Context
import android.util.Log
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import com.stevesoltys.seedvault.BackupStateManager
import com.stevesoltys.seedvault.ui.check.FileCheckResultActivity
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.flow.first
import org.calyxos.backup.storage.api.StorageBackup
import org.calyxos.backup.storage.check.CheckerWorker
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

class FileCheckerWorker(
appContext: Context,
workerParams: WorkerParameters,
) : CheckerWorker(appContext, workerParams), KoinComponent {

companion object {
private val TAG = FileCheckerWorker::class.simpleName

fun scheduleNow(
context: Context,
percent: Int,
) {
scheduleNow(context, percent, OneTimeWorkRequestBuilder<FileCheckerWorker>())
}
}

private val log = KotlinLogging.logger {}
private val backupStateManager: BackupStateManager by inject()
override val storageBackup: StorageBackup by inject()
override val resultActivityClass: Class<*> = FileCheckResultActivity::class.java

override suspend fun doWork(): Result {
log.info { "Start worker $this ($id)" }
if (backupStateManager.isBackupRunning.first()) {
Log.i(TAG, "isBackupRunning was true, so retrying later...")
return Result.retry()
}
return super.doWork()
}
}
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
<string name="settings_app_check_text2">Here you can run a one-time check to verify your app backup. Select how much of the app data should be downloaded for the check. The more you select, the longer it will take, yet the more reliable the check will be. This will run in the background and show a notification once the check is done.</string>
<string name="settings_app_check_warning">Warning: Downloading large amounts of data can take a long time.</string>
<string name="settings_app_check_button">Check backup</string>
<string name="settings_file_check_title">Verify files backup integrity</string>
<string name="settings_file_check_text">Here you can run a one-time check to verify your files backup.</string>
<string name="settings_file_check_text2">Select how much of the files should be downloaded for the check. The more you select, the longer it will take, yet the more reliable the check will be. This will run in the background and show a notification once the check is done.</string>

<string name="settings_expert_title">Expert settings</string>
<string name="settings_expert_logcat_title">Save app log</string>
Expand Down
9 changes: 8 additions & 1 deletion app/src/main/res/xml/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,20 @@

<androidx.preference.Preference
android:dependency="backup_storage"
app:dependency="backup_storage"
app:fragment="com.stevesoltys.seedvault.ui.files.FileSelectionFragment"
app:icon="@drawable/ic_library_add"
app:key="backup_files"
app:summary="@string/settings_backup_files_summary"
app:title="@string/settings_backup_files_title" />

<androidx.preference.Preference
android:dependency="backup_storage"
app:fragment="com.stevesoltys.seedvault.ui.check.FileCheckFragment"
app:icon="@drawable/ic_cloud_search"
app:key="backup_file_check"
app:summary="@string/settings_backup_app_check_summary"
app:title="@string/settings_backup_app_check_title" />

</PreferenceCategory>

<androidx.preference.Preference
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2021 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/

package org.calyxos.backup.storage.check

import android.content.Context
import org.calyxos.backup.storage.api.CheckObserver
import org.calyxos.backup.storage.ui.Notifications

public open class NotificationCheckObserver internal constructor(
private val n: Notifications,
private val reportActivityClass: Class<*>,
) : CheckObserver {

public constructor(
context: Context,
reportActivityClass: Class<*>,
) : this(Notifications(context), reportActivityClass)

override fun onStartChecking() {
n.showCheckStartedNotification()
}

override fun onCheckUpdate(speed: Long, thousandth: Int) {
n.showCheckNotification(speed, thousandth)
}

override fun onCheckSuccess(size: Long, speed: Long) {
n.onCheckComplete(reportActivityClass, size, speed)
}

override fun onCheckFoundErrors(size: Long, speed: Long) {
n.onCheckFinishedWithError(reportActivityClass, size, speed)
}

}
Loading

0 comments on commit 825fcd7

Please sign in to comment.