Add UI prototype for selecting file to storage demo app
This commit is contained in:
parent
2b07b8417c
commit
5012099419
18 changed files with 471 additions and 8 deletions
|
@ -46,6 +46,7 @@ import org.calyxos.backup.storage.api.SnapshotItem
|
||||||
import org.calyxos.backup.storage.api.StorageBackup
|
import org.calyxos.backup.storage.api.StorageBackup
|
||||||
import org.calyxos.backup.storage.restore.RestoreService.Companion.EXTRA_TIMESTAMP_START
|
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.restore.RestoreService.Companion.EXTRA_USER_ID
|
||||||
|
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
|
||||||
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
|
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
@ -98,6 +99,8 @@ internal class RestoreViewModel(
|
||||||
get() = appDataRestoreManager.restoreBackupResult
|
get() = appDataRestoreManager.restoreBackupResult
|
||||||
|
|
||||||
override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher)
|
override val snapshots = storageBackup.getBackupSnapshots().asLiveData(ioDispatcher)
|
||||||
|
override val fileSelectionManager: FileSelectionManager
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
|
||||||
internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
|
internal fun loadRestoreSets() = viewModelScope.launch(ioDispatcher) {
|
||||||
val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) ->
|
val backups = restoreCoordinator.getAvailableMetadata()?.mapNotNull { (token, metadata) ->
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.calyxos.backup.storage.backup.BackupJobService
|
||||||
import org.calyxos.backup.storage.scanner.DocumentScanner
|
import org.calyxos.backup.storage.scanner.DocumentScanner
|
||||||
import org.calyxos.backup.storage.scanner.MediaScanner
|
import org.calyxos.backup.storage.scanner.MediaScanner
|
||||||
import org.calyxos.backup.storage.ui.backup.BackupContentViewModel
|
import org.calyxos.backup.storage.ui.backup.BackupContentViewModel
|
||||||
|
import org.calyxos.backup.storage.ui.restore.FileSelectionManager
|
||||||
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
|
import org.calyxos.backup.storage.ui.restore.SnapshotViewModel
|
||||||
|
|
||||||
private val logEmptyState = """
|
private val logEmptyState = """
|
||||||
|
@ -47,6 +48,7 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
|
||||||
private val app: App = application as App
|
private val app: App = application as App
|
||||||
private val settingsManager = app.settingsManager
|
private val settingsManager = app.settingsManager
|
||||||
override val storageBackup: StorageBackup = app.storageBackup
|
override val storageBackup: StorageBackup = app.storageBackup
|
||||||
|
override val fileSelectionManager = FileSelectionManager()
|
||||||
|
|
||||||
private val _backupLog = MutableLiveData(BackupProgress(0, 0, logEmptyState))
|
private val _backupLog = MutableLiveData(BackupProgress(0, 0, logEmptyState))
|
||||||
val backupLog: LiveData<BackupProgress> = _backupLog
|
val backupLog: LiveData<BackupProgress> = _backupLog
|
||||||
|
@ -124,6 +126,11 @@ class MainViewModel(application: Application) : BackupContentViewModel(applicati
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSnapshotClicked(item: SnapshotItem) {
|
fun onSnapshotClicked(item: SnapshotItem) {
|
||||||
|
val snapshot = item.snapshot ?: error("${item.storedSnapshot} had null snapshot")
|
||||||
|
fileSelectionManager.onSnapshotChosen(snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onFilesSelected(item: SnapshotItem) {
|
||||||
val snapshot = item.snapshot
|
val snapshot = item.snapshot
|
||||||
check(snapshot != null)
|
check(snapshot != null)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package de.grobox.storagebackuptester.restore
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import de.grobox.storagebackuptester.MainViewModel
|
||||||
|
import org.calyxos.backup.storage.ui.restore.FileSelectionFragment
|
||||||
|
|
||||||
|
class DemoFileSelectionFragment : FileSelectionFragment() {
|
||||||
|
|
||||||
|
override val viewModel: MainViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
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()
|
||||||
|
// }
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -39,8 +39,8 @@ class DemoSnapshotFragment : SnapshotFragment() {
|
||||||
override fun onSnapshotClicked(item: SnapshotItem) {
|
override fun onSnapshotClicked(item: SnapshotItem) {
|
||||||
viewModel.onSnapshotClicked(item)
|
viewModel.onSnapshotClicked(item)
|
||||||
parentFragmentManager.beginTransaction()
|
parentFragmentManager.beginTransaction()
|
||||||
.replace(R.id.container, RestoreFragment.newInstance())
|
.replace(R.id.container, DemoFileSelectionFragment())
|
||||||
.addToBackStack("RESTORE")
|
.addToBackStack("SELECT")
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,17 +52,17 @@ class RestoreFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
viewModel.restoreLog.observe(viewLifecycleOwner, { progress ->
|
viewModel.restoreLog.observe(viewLifecycleOwner) { progress ->
|
||||||
progress.text?.let { adapter.addItem(it) }
|
progress.text?.let { adapter.addItem(it) }
|
||||||
horizontalProgressBar.max = progress.total
|
horizontalProgressBar.max = progress.total
|
||||||
horizontalProgressBar.setProgress(progress.current, true)
|
horizontalProgressBar.setProgress(progress.current, true)
|
||||||
list.postDelayed({
|
list.postDelayed({
|
||||||
list.scrollToPosition(adapter.itemCount - 1)
|
list.scrollToPosition(adapter.itemCount - 1)
|
||||||
}, 50)
|
}, 50)
|
||||||
})
|
}
|
||||||
viewModel.restoreProgressVisible.observe(viewLifecycleOwner, { visible ->
|
viewModel.restoreProgressVisible.observe(viewLifecycleOwner) { visible ->
|
||||||
progressBar.visibility = if (visible) VISIBLE else INVISIBLE
|
progressBar.visibility = if (visible) VISIBLE else INVISIBLE
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.calyxos.backup.storage.ui.restore
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Lifecycle.State.STARTED
|
||||||
|
import androidx.lifecycle.flowWithLifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.calyxos.backup.storage.R
|
||||||
|
|
||||||
|
public abstract class FileSelectionFragment : Fragment() {
|
||||||
|
|
||||||
|
protected abstract val viewModel: SnapshotViewModel
|
||||||
|
private lateinit var list: RecyclerView
|
||||||
|
private lateinit var adapter: FilesAdapter
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?,
|
||||||
|
): View {
|
||||||
|
requireActivity().setTitle(R.string.select_files_title)
|
||||||
|
|
||||||
|
val v = inflater.inflate(R.layout.fragment_select_files, container, false)
|
||||||
|
list = v.findViewById(R.id.list)
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
adapter = FilesAdapter(
|
||||||
|
viewModel.fileSelectionManager::onExpandClicked,
|
||||||
|
viewModel.fileSelectionManager::onCheckedChanged,
|
||||||
|
)
|
||||||
|
list.adapter = adapter
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.fileSelectionManager.files.flowWithLifecycle(lifecycle, STARTED).collect {
|
||||||
|
onFileItemsChanged(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
public open fun onFileItemsChanged(filesItems: List<FilesItem>) {
|
||||||
|
adapter.submitList(filesItems)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 The Calyx Institute
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.calyxos.backup.storage.ui.restore
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.text.format.DateUtils.FORMAT_ABBREV_ALL
|
||||||
|
import android.text.format.DateUtils.getRelativeTimeSpanString
|
||||||
|
import android.text.format.Formatter
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.VISIBLE
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CheckBox
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat.getDrawable
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import org.calyxos.backup.storage.R
|
||||||
|
import org.calyxos.backup.storage.backup.BackupMediaFile.MediaType.AUDIO
|
||||||
|
import org.calyxos.backup.storage.backup.BackupMediaFile.MediaType.IMAGES
|
||||||
|
import org.calyxos.backup.storage.backup.BackupMediaFile.MediaType.VIDEO
|
||||||
|
import org.calyxos.backup.storage.ui.restore.FilesAdapter.FilesViewHolder
|
||||||
|
|
||||||
|
private class FilesItemCallback : DiffUtil.ItemCallback<FilesItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: FilesItem, newItem: FilesItem): Boolean {
|
||||||
|
if (oldItem is FileItem && newItem is FileItem) return newItem.file == oldItem.file
|
||||||
|
if (oldItem is FolderItem && newItem is FolderItem) return newItem.name == oldItem.name
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: FilesItem, newItem: FilesItem): Boolean {
|
||||||
|
if (oldItem is FileItem && newItem is FileItem) return newItem.selected == oldItem.selected
|
||||||
|
if (oldItem is FolderItem && newItem is FolderItem) {
|
||||||
|
return newItem.selected == oldItem.selected && newItem.expanded == oldItem.expanded &&
|
||||||
|
newItem.partiallySelected == oldItem.partiallySelected
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class FilesAdapter(
|
||||||
|
private val onExpandClicked: (FolderItem) -> Unit,
|
||||||
|
private val onCheckedChanged: (FilesItem) -> Unit,
|
||||||
|
) : ListAdapter<FilesItem, FilesViewHolder>(FilesItemCallback()) {
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilesViewHolder {
|
||||||
|
val v = LayoutInflater.from(parent.context).inflate(R.layout.item_file, parent, false)
|
||||||
|
return FilesViewHolder(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: FilesViewHolder, position: Int) {
|
||||||
|
holder.bind(getItem(position))
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class FilesViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||||
|
|
||||||
|
private val context = itemView.context
|
||||||
|
private val expandView: ImageView = itemView.findViewById(R.id.expandView)
|
||||||
|
private val nameView: TextView = itemView.findViewById(R.id.nameView)
|
||||||
|
private val infoView: TextView = itemView.findViewById(R.id.infoView)
|
||||||
|
private val checkBox: CheckBox = itemView.findViewById(R.id.checkBox)
|
||||||
|
|
||||||
|
private val indentPadding = (8 * Resources.getSystem().displayMetrics.density).toInt()
|
||||||
|
private val checkBoxDrawable = checkBox.buttonDrawable
|
||||||
|
private val indeterminateDrawable =
|
||||||
|
getDrawable(context, R.drawable.ic_indeterminate_check_box)
|
||||||
|
|
||||||
|
fun bind(item: FilesItem) {
|
||||||
|
if (item is FolderItem) {
|
||||||
|
expandView.visibility = VISIBLE
|
||||||
|
val res = if (item.expanded) {
|
||||||
|
R.drawable.ic_keyboard_arrow_down
|
||||||
|
} else {
|
||||||
|
R.drawable.ic_chevron_right
|
||||||
|
}
|
||||||
|
expandView.setImageResource(res)
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
onExpandClicked(item)
|
||||||
|
}
|
||||||
|
} else if (item is FileItem) {
|
||||||
|
expandView.setImageResource(getDrawableResource(item))
|
||||||
|
itemView.setOnClickListener(null)
|
||||||
|
}
|
||||||
|
itemView.updatePadding(left = indentPadding * item.level)
|
||||||
|
nameView.text = item.name
|
||||||
|
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
var text = Formatter.formatShortFileSize(context, item.size)
|
||||||
|
item.lastModified?.let {
|
||||||
|
text += " - " + getRelativeTimeSpanString(it, now, 0L, FORMAT_ABBREV_ALL)
|
||||||
|
}
|
||||||
|
if (item is FolderItem) {
|
||||||
|
val numStr = context.getString(R.string.select_files_number_of_files, item.numFiles)
|
||||||
|
text += " - $numStr"
|
||||||
|
}
|
||||||
|
infoView.text = text
|
||||||
|
// unset and re-reset onCheckedChangeListener while updating checked state
|
||||||
|
checkBox.setOnCheckedChangeListener(null)
|
||||||
|
checkBox.isChecked = item.selected
|
||||||
|
checkBox.setOnCheckedChangeListener { _, _ ->
|
||||||
|
onCheckedChanged(item)
|
||||||
|
}
|
||||||
|
if (item is FolderItem && item.partiallySelected) {
|
||||||
|
checkBox.buttonDrawable = indeterminateDrawable
|
||||||
|
} else {
|
||||||
|
checkBox.buttonDrawable = checkBoxDrawable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDrawableResource(item: FileItem): Int = item.file.mediaFile?.type?.let { type ->
|
||||||
|
when (type) {
|
||||||
|
IMAGES -> R.drawable.ic_image
|
||||||
|
VIDEO -> R.drawable.ic_video_file
|
||||||
|
AUDIO -> R.drawable.ic_audio_file
|
||||||
|
else -> R.drawable.ic_insert_drive_file
|
||||||
|
}
|
||||||
|
} ?: R.drawable.ic_insert_drive_file
|
||||||
|
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import org.calyxos.backup.storage.api.SnapshotResult
|
||||||
|
|
||||||
public interface SnapshotViewModel {
|
public interface SnapshotViewModel {
|
||||||
public val snapshots: LiveData<SnapshotResult>
|
public val snapshots: LiveData<SnapshotResult>
|
||||||
|
public val fileSelectionManager: FileSelectionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
internal interface SnapshotClickListener {
|
internal interface SnapshotClickListener {
|
||||||
|
@ -46,7 +47,7 @@ public abstract class SnapshotFragment : Fragment(), SnapshotClickListener {
|
||||||
|
|
||||||
val adapter = SnapshotAdapter(this)
|
val adapter = SnapshotAdapter(this)
|
||||||
list.adapter = adapter
|
list.adapter = adapter
|
||||||
viewModel.snapshots.observe(viewLifecycleOwner, {
|
viewModel.snapshots.observe(viewLifecycleOwner) {
|
||||||
progressBar.visibility = INVISIBLE
|
progressBar.visibility = INVISIBLE
|
||||||
when (it) {
|
when (it) {
|
||||||
is SnapshotResult.Success -> {
|
is SnapshotResult.Success -> {
|
||||||
|
@ -54,6 +55,7 @@ public abstract class SnapshotFragment : Fragment(), SnapshotClickListener {
|
||||||
emptyStateView.visibility = VISIBLE
|
emptyStateView.visibility = VISIBLE
|
||||||
} else adapter.submitList(it.snapshots)
|
} else adapter.submitList(it.snapshots)
|
||||||
}
|
}
|
||||||
|
|
||||||
is SnapshotResult.Error -> {
|
is SnapshotResult.Error -> {
|
||||||
val color = resources.getColor(R.color.design_default_color_error, null)
|
val color = resources.getColor(R.color.design_default_color_error, null)
|
||||||
emptyStateView.setTextColor(color)
|
emptyStateView.setTextColor(color)
|
||||||
|
@ -61,7 +63,7 @@ public abstract class SnapshotFragment : Fragment(), SnapshotClickListener {
|
||||||
emptyStateView.visibility = VISIBLE
|
emptyStateView.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
15
storage/lib/src/main/res/drawable/ic_audio_file.xml
Normal file
15
storage/lib/src/main/res/drawable/ic_audio_file.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M14,2H6C4.9,2 4.01,2.9 4.01,4L4,20c0,1.1 0.89,2 1.99,2H18c1.1,0 2,-0.9 2,-2V8L14,2zM16,13h-3v3.75c0,1.24 -1.01,2.25 -2.25,2.25S8.5,17.99 8.5,16.75c0,-1.24 1.01,-2.25 2.25,-2.25c0.46,0 0.89,0.14 1.25,0.38V11h4V13zM13,9V3.5L18.5,9H13z" />
|
||||||
|
|
||||||
|
</vector>
|
15
storage/lib/src/main/res/drawable/ic_chevron_right.xml
Normal file
15
storage/lib/src/main/res/drawable/ic_chevron_right.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z" />
|
||||||
|
|
||||||
|
</vector>
|
14
storage/lib/src/main/res/drawable/ic_image.xml
Normal file
14
storage/lib/src/main/res/drawable/ic_image.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="32dp"
|
||||||
|
android:height="32dp"
|
||||||
|
android:tint="?colorAccent"
|
||||||
|
android:viewportWidth="48"
|
||||||
|
android:viewportHeight="48">
|
||||||
|
<group
|
||||||
|
android:name="icon_null"
|
||||||
|
android:scaleX="0.2"
|
||||||
|
android:scaleY="0.2"
|
||||||
|
android:translateX="6"
|
||||||
|
android:translateY="6">
|
||||||
|
<group
|
||||||
|
android:name="check"
|
||||||
|
android:scaleX="7.5"
|
||||||
|
android:scaleY="7.5">
|
||||||
|
<path
|
||||||
|
android:name="check_path_merged"
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM17,13H7v-2h10V13z" />
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
15
storage/lib/src/main/res/drawable/ic_insert_drive_file.xml
Normal file
15
storage/lib/src/main/res/drawable/ic_insert_drive_file.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z" />
|
||||||
|
|
||||||
|
</vector>
|
15
storage/lib/src/main/res/drawable/ic_keyboard_arrow_down.xml
Normal file
15
storage/lib/src/main/res/drawable/ic_keyboard_arrow_down.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z" />
|
||||||
|
|
||||||
|
</vector>
|
14
storage/lib/src/main/res/drawable/ic_video_file.xml
Normal file
14
storage/lib/src/main/res/drawable/ic_video_file.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
SPDX-FileCopyrightText: Material Design Authors / Google LLC
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
-->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?android:attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M14,2H6.01c-1.1,0 -2,0.89 -2,2L4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8L14,2zM13,9V3.5L18.5,9H13zM14,14l2,-1.06v4.12L14,16v1c0,0.55 -0.45,1 -1,1H9c-0.55,0 -1,-0.45 -1,-1v-4c0,-0.55 0.45,-1 1,-1h4c0.55,0 1,0.45 1,1V14z" />
|
||||||
|
</vector>
|
58
storage/lib/src/main/res/layout/fragment_select_files.xml
Normal file
58
storage/lib/src/main/res/layout/fragment_select_files.xml
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlwaysCollapsed">
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/topStub"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inflatedId="@+id/topStub"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:layout="@layout/item_custom"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/list"
|
||||||
|
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"
|
||||||
|
tools:listitem="@layout/item_file" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/fab"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:text="@string/select_files_button_restore"
|
||||||
|
android:textColor="#ffffff"
|
||||||
|
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" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
54
storage/lib/src/main/res/layout/item_file.xml
Normal file
54
storage/lib/src/main/res/layout/item_file.xml
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?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"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:paddingVertical="8dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/expandView"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:scaleType="center"
|
||||||
|
android:src="@drawable/ic_folder"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/nameView"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nameView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/checkBox"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/expandView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="File/folder name which might be quite long, who knows...?" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/infoView"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/nameView"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/nameView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/nameView"
|
||||||
|
tools:text="24h ago - 23 MB" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatCheckBox
|
||||||
|
android:id="@+id/checkBox"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/nameView"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -22,4 +22,8 @@
|
||||||
<string name="snapshots_title">Available storage backups</string>
|
<string name="snapshots_title">Available storage backups</string>
|
||||||
<string name="snapshots_empty">No storage backups found\n\nSorry, but there is nothing that can be restored.</string>
|
<string name="snapshots_empty">No storage backups found\n\nSorry, but there is nothing that can be restored.</string>
|
||||||
<string name="snapshots_error">Error loading snapshots</string>
|
<string name="snapshots_error">Error loading snapshots</string>
|
||||||
|
|
||||||
|
<string name="select_files_title">Files to be restored</string>
|
||||||
|
<string name="select_files_number_of_files">%1$d file(s)</string>
|
||||||
|
<string name="select_files_button_restore">Restore checked files</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue