Make demo restore with file selection functional
This injects FileSelectionManager as a singleton, so we can use its selection to recreate a snapshot, even in a service. Also includes some UI improvements.
This commit is contained in:
parent
5012099419
commit
7d6ab6f8e0
17 changed files with 139 additions and 54 deletions
|
@ -57,6 +57,7 @@ class KoinInstrumentationTestApp : App() {
|
|||
iconManager = get(),
|
||||
storageBackup = get(),
|
||||
pluginManager = get(),
|
||||
fileSelectionManager = get(),
|
||||
)
|
||||
)
|
||||
currentRestoreViewModel!!
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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()) }
|
||||
}
|
|
@ -64,6 +64,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 {
|
||||
|
@ -99,8 +100,6 @@ internal class RestoreViewModel(
|
|||
get() = appDataRestoreManager.restoreBackupResult
|
||||
|
||||
override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher)
|
||||
override val fileSelectionManager: FileSelectionManager
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
|
||||
val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) ->
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<Button
|
||||
|
@ -122,9 +118,6 @@
|
|||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/restore_backup_button"
|
||||
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appList" />
|
||||
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.util.Log
|
|||
import de.grobox.storagebackuptester.plugin.TestSafStoragePlugin
|
||||
import de.grobox.storagebackuptester.settings.SettingsManager
|
||||
import org.calyxos.backup.storage.api.StorageBackup
|
||||
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
|
||||
|
||||
class App : Application() {
|
||||
|
||||
|
@ -20,6 +21,7 @@ class App : Application() {
|
|||
val plugin = TestSafStoragePlugin(this) { settingsManager.getBackupLocation() }
|
||||
StorageBackup(this, { plugin })
|
||||
}
|
||||
val fileSelectionManager: FileSelectionManager get() = FileSelectionManager()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
|
|
@ -14,6 +14,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 java.util.concurrent.TimeUnit.HOURS
|
||||
|
||||
// debug with:
|
||||
|
@ -45,6 +46,8 @@ class DemoBackupService : BackupService() {
|
|||
class DemoRestoreService : RestoreService() {
|
||||
// use lazy delegate because context isn't available during construction time
|
||||
override val storageBackup: StorageBackup by lazy { (application as App).storageBackup }
|
||||
override val fileSelectionManager: FileSelectionManager
|
||||
get() = (application as App).fileSelectionManager
|
||||
override val restoreObserver: RestoreObserver by lazy {
|
||||
NotificationRestoreObserver(applicationContext)
|
||||
}
|
||||
|
|
|
@ -24,11 +24,11 @@ import kotlinx.coroutines.withContext
|
|||
import org.calyxos.backup.storage.api.SnapshotItem
|
||||
import org.calyxos.backup.storage.api.SnapshotResult
|
||||
import org.calyxos.backup.storage.api.StorageBackup
|
||||
import org.calyxos.backup.storage.api.StoredSnapshot
|
||||
import org.calyxos.backup.storage.backup.BackupJobService
|
||||
import org.calyxos.backup.storage.scanner.DocumentScanner
|
||||
import org.calyxos.backup.storage.scanner.MediaScanner
|
||||
import org.calyxos.backup.storage.ui.backup.BackupContentViewModel
|
||||
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
|
||||
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
|
||||
|
||||
private val logEmptyState = """
|
||||
|
@ -48,7 +48,7 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
|
|||
private val app: App = application as App
|
||||
private val settingsManager = app.settingsManager
|
||||
override val storageBackup: StorageBackup = app.storageBackup
|
||||
override val fileSelectionManager = FileSelectionManager()
|
||||
override val fileSelectionManager = app.fileSelectionManager
|
||||
|
||||
private val _backupLog = MutableLiveData(BackupProgress(0, 0, logEmptyState))
|
||||
val backupLog: LiveData<BackupProgress> = _backupLog
|
||||
|
@ -64,6 +64,7 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
|
|||
|
||||
override val snapshots: LiveData<SnapshotResult>
|
||||
get() = storageBackup.getBackupSnapshots().asLiveData(Dispatchers.IO)
|
||||
private var storedSnapshot: StoredSnapshot? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch { loadContent() }
|
||||
|
@ -128,11 +129,12 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
|
|||
fun onSnapshotClicked(item: SnapshotItem) {
|
||||
val snapshot = item.snapshot ?: error("${item.storedSnapshot} had null snapshot")
|
||||
fileSelectionManager.onSnapshotChosen(snapshot)
|
||||
storedSnapshot = item.storedSnapshot
|
||||
}
|
||||
|
||||
fun onFilesSelected(item: SnapshotItem) {
|
||||
val snapshot = item.snapshot
|
||||
check(snapshot != null)
|
||||
fun onFilesSelected() {
|
||||
val storedSnapshot = this.storedSnapshot ?: error("No snapshot stored")
|
||||
val snapshot = fileSelectionManager.getBackupSnapshotAndReset()
|
||||
|
||||
// example for how to do restore via foreground service
|
||||
// app.startForegroundService(Intent(app, DemoRestoreService::class.java).apply {
|
||||
|
@ -144,8 +146,9 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
|
|||
_restoreProgressVisible.value = true
|
||||
val restoreObserver = RestoreStats(app, _restoreLog)
|
||||
viewModelScope.launch {
|
||||
storageBackup.restoreBackupSnapshot(item.storedSnapshot, snapshot, restoreObserver)
|
||||
storageBackup.restoreBackupSnapshot(storedSnapshot, snapshot, restoreObserver)
|
||||
_restoreProgressVisible.value = false
|
||||
this@MainViewModel.storedSnapshot = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,10 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewStub
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import de.grobox.storagebackuptester.MainViewModel
|
||||
import de.grobox.storagebackuptester.R
|
||||
import org.calyxos.backup.storage.ui.restore.FileSelectionFragment
|
||||
|
||||
class DemoFileSelectionFragment : FileSelectionFragment() {
|
||||
|
@ -23,14 +25,17 @@ class DemoFileSelectionFragment : FileSelectionFragment() {
|
|||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
val v = super.onCreateView(inflater, container, savedInstanceState)
|
||||
// val topStub: ViewStub = v.findViewById(R.id.topStub)
|
||||
// topStub.layoutResource = R.layout.footer_snapshot
|
||||
// val header = topStub.inflate()
|
||||
// header.findViewById<Button>(R.id.button).setOnClickListener {
|
||||
// requireActivity().onBackPressed()
|
||||
// }
|
||||
val topStub: ViewStub = v.findViewById(R.id.topStub)
|
||||
topStub.layoutResource = R.layout.header_file_select
|
||||
topStub.inflate()
|
||||
return v
|
||||
}
|
||||
|
||||
override fun onRestoreButtonClicked() {
|
||||
viewModel.onFilesSelected()
|
||||
parentFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, RestoreFragment.newInstance())
|
||||
.commit()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
20
storage/demo/src/main/res/layout/header_file_select.xml
Normal file
20
storage/demo/src/main/res/layout/header_file_select.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2021 The Calyx Institute
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="De-select folders you don't want to restore. Tap folders to see their contents."
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -200,7 +200,7 @@ public class StorageBackup(
|
|||
|
||||
public suspend fun restoreBackupSnapshot(
|
||||
storedSnapshot: StoredSnapshot,
|
||||
snapshot: BackupSnapshot? = null,
|
||||
snapshot: BackupSnapshot,
|
||||
restoreObserver: RestoreObserver? = null,
|
||||
): Boolean = withContext(dispatcher) {
|
||||
if (restoreRunning.getAndSet(true)) {
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.calyxos.backup.storage.plugin.SnapshotRetriever
|
|||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.GeneralSecurityException
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
private const val TAG = "Restore"
|
||||
|
||||
|
@ -99,15 +98,12 @@ internal class Restore(
|
|||
Log.e(TAG, "Decrypting and parsing $numSnapshots snapshots took $time")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@Throws(IOException::class, GeneralSecurityException::class)
|
||||
suspend fun restoreBackupSnapshot(
|
||||
storedSnapshot: StoredSnapshot,
|
||||
optionalSnapshot: BackupSnapshot? = null,
|
||||
snapshot: BackupSnapshot,
|
||||
observer: RestoreObserver? = null,
|
||||
) {
|
||||
val snapshot = optionalSnapshot ?: snapshotRetriever.getSnapshot(streamKey, storedSnapshot)
|
||||
|
||||
val filesTotal = snapshot.mediaFilesList.size + snapshot.documentFilesList.size
|
||||
val totalSize =
|
||||
snapshot.mediaFilesList.sumOf { it.size } + snapshot.documentFilesList.sumOf { it.size }
|
||||
|
|
|
@ -9,8 +9,10 @@ import android.app.Service
|
|||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.calyxos.backup.storage.api.RestoreObserver
|
||||
import org.calyxos.backup.storage.api.StorageBackup
|
||||
import org.calyxos.backup.storage.api.StoredSnapshot
|
||||
|
@ -18,6 +20,7 @@ import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_TIMESTA
|
|||
import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_USER_ID
|
||||
import org.calyxos.backup.storage.ui.NOTIFICATION_ID_RESTORE
|
||||
import org.calyxos.backup.storage.ui.Notifications
|
||||
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
|
||||
|
||||
/**
|
||||
* Start to trigger restore as a foreground service. Ensure that you provide the snapshot
|
||||
|
@ -36,6 +39,7 @@ public abstract class RestoreService : Service() {
|
|||
|
||||
private val n by lazy { Notifications(applicationContext) }
|
||||
protected abstract val storageBackup: StorageBackup
|
||||
protected abstract val fileSelectionManager: FileSelectionManager
|
||||
protected abstract val restoreObserver: RestoreObserver?
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
|
@ -47,8 +51,11 @@ public abstract class RestoreService : Service() {
|
|||
|
||||
startForeground(NOTIFICATION_ID_RESTORE, n.getRestoreNotification())
|
||||
GlobalScope.launch {
|
||||
val snapshot = withContext(Dispatchers.Main) {
|
||||
fileSelectionManager.getBackupSnapshotAndReset()
|
||||
}
|
||||
// TODO offer a way to try again if failed, or do an automatic retry here
|
||||
storageBackup.restoreBackupSnapshot(storedSnapshot, null, restoreObserver)
|
||||
storageBackup.restoreBackupSnapshot(storedSnapshot, snapshot, restoreObserver)
|
||||
stopSelf(startId)
|
||||
}
|
||||
return START_STICKY_COMPATIBILITY
|
||||
|
|
|
@ -33,6 +33,9 @@ public abstract class FileSelectionFragment : Fragment() {
|
|||
|
||||
val v = inflater.inflate(R.layout.fragment_select_files, container, false)
|
||||
list = v.findViewById(R.id.list)
|
||||
v.findViewById<View>(R.id.fab).setOnClickListener {
|
||||
onRestoreButtonClicked()
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
@ -51,8 +54,10 @@ public abstract class FileSelectionFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract fun onRestoreButtonClicked()
|
||||
|
||||
@CallSuper
|
||||
public open fun onFileItemsChanged(filesItems: List<FilesItem>) {
|
||||
protected open fun onFileItemsChanged(filesItems: List<FilesItem>) {
|
||||
adapter.submitList(filesItems)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ public class FileSelectionManager {
|
|||
|
||||
private val allFolders = HashMap<String, FolderItem>()
|
||||
private val allFiles = HashMap<String, MutableList<FileItem>>()
|
||||
private var snapshot: BackupSnapshot? = null
|
||||
private var expandedFolder: String? = null
|
||||
|
||||
private val mFiles = MutableStateFlow<List<FilesItem>>(emptyList())
|
||||
|
@ -61,6 +62,12 @@ public class FileSelectionManager {
|
|||
|
||||
@UiThread
|
||||
public fun onSnapshotChosen(snapshot: BackupSnapshot) {
|
||||
// clear previous state if existing
|
||||
clearState()
|
||||
// store snapshot for later
|
||||
this.snapshot = snapshot
|
||||
|
||||
// cache files from snapshot within [RestorableFile] (for easier processing)
|
||||
snapshot.mediaFilesList.forEach { mediaFile ->
|
||||
cacheFileItem(RestorableFile(mediaFile))
|
||||
}
|
||||
|
@ -126,6 +133,28 @@ public class FileSelectionManager {
|
|||
mFiles.value = rebuildListFromCache()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public fun getBackupSnapshotAndReset(): BackupSnapshot {
|
||||
val snapshot = this.snapshot ?: error("No snapshot stored")
|
||||
// clear previous media files from snapshot
|
||||
val snapshotBuilder = snapshot.toBuilder()
|
||||
.clearMediaFiles()
|
||||
.clearDocumentFiles()
|
||||
// add only selected files back to snapshot
|
||||
allFiles.values.forEach { fileList ->
|
||||
fileList.forEach { file ->
|
||||
if (file.selected && file.file.mediaFile != null) {
|
||||
snapshotBuilder.addMediaFiles(file.file.mediaFile)
|
||||
} else if (file.selected && file.file.docFile != null) {
|
||||
snapshotBuilder.addDocumentFiles(file.file.docFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
// clear state to free up memory
|
||||
clearState()
|
||||
return snapshotBuilder.build()
|
||||
}
|
||||
|
||||
private fun cacheFileItem(restorableFile: RestorableFile) {
|
||||
val fileItem = FileItem(restorableFile, 0, true)
|
||||
allFiles.getOrPut(restorableFile.dir) {
|
||||
|
@ -215,4 +244,12 @@ public class FileSelectionManager {
|
|||
)
|
||||
}
|
||||
|
||||
private fun clearState() {
|
||||
snapshot = null
|
||||
expandedFolder = null
|
||||
allFolders.clear()
|
||||
allFiles.clear()
|
||||
mFiles.value = emptyList()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<ViewStub
|
||||
android:id="@+id/topStub"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inflatedId="@+id/topStub"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -34,10 +34,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toTopOf="@+id/bottomStub"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/topStub"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/item_file" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
|
@ -51,8 +48,6 @@
|
|||
app:backgroundTint="?colorAccent"
|
||||
app:icon="@drawable/ic_cloud_restore"
|
||||
app:iconTint="#ffffff"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
|
Loading…
Reference in a new issue