diff --git a/presentation/src/main/java/org/cryptomator/presentation/model/VaultListSortOption.kt b/presentation/src/main/java/org/cryptomator/presentation/model/VaultListSortOption.kt new file mode 100644 index 000000000..ec1fcacae --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/model/VaultListSortOption.kt @@ -0,0 +1,6 @@ +package org.cryptomator.presentation.model + +enum class VaultListSortOption { + NAME, + LOCATION +} diff --git a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt index 4e6e2e1d4..085d5ee6f 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt @@ -46,6 +46,7 @@ import org.cryptomator.presentation.intent.UnlockVaultIntent import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudTypeModel import org.cryptomator.presentation.model.ProgressModel +import org.cryptomator.presentation.model.VaultListSortOption import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper import org.cryptomator.presentation.ui.activity.LicenseCheckActivity @@ -66,6 +67,7 @@ import org.cryptomator.presentation.workflow.PermissionsResult import org.cryptomator.presentation.workflow.Workflow import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.crypto.CryptoMode +import java.util.Locale import javax.inject.Inject import timber.log.Timber @@ -97,6 +99,7 @@ class VaultListPresenter @Inject constructor( // ) : Presenter(exceptionMappings) { private var vaultAction: VaultAction? = null + private var cachedVaults: List = emptyList() override fun workflows(): Iterable> { return listOf(addExistingVaultWorkflow, createNewVaultWorkflow) @@ -391,6 +394,7 @@ class VaultListPresenter @Inject constructor( // get() { getVaultListUseCase.run(object : DefaultResultHandler>() { override fun onSuccess(vaults: List) { + cachedVaults = vaults val vaultModels = vaults.mapTo(ArrayList()) { VaultModel(it) } if (vaultModels.isEmpty()) { view?.showVaultCreationHint() @@ -601,6 +605,7 @@ class VaultListPresenter @Inject constructor( // .andToPosition(toPosition) // .run(object : DefaultResultHandler>() { override fun onSuccess(vaults: List) { + cachedVaults = vaults view?.vaultMoved(vaults.mapTo(ArrayList()) { VaultModel(it) }) } @@ -610,6 +615,51 @@ class VaultListPresenter @Inject constructor( // }) } + fun onSortOverrideConfirmed(sortOption: VaultListSortOption) { + if (cachedVaults.isEmpty()) { + loadVaultList() + return + } + + val sortedVaults = when (sortOption) { + VaultListSortOption.NAME -> cachedVaults.sortedWith(nameComparator()) + VaultListSortOption.LOCATION -> cachedVaults.sortedWith(locationComparator()) + } + + if (sortedVaults.map { it.id } == cachedVaults.map { it.id }) { + return + } + + val reindexedVaults = sortedVaults.mapIndexed { index, vault -> + Vault.aCopyOf(vault).withPosition(index).build() + } + + saveVaultsUseCase // + .withVaults(reindexedVaults) // + .run(object : DefaultResultHandler>() { + override fun onSuccess(vaults: List) { + cachedVaults = vaults + val vaultModels = vaults.mapTo(ArrayList()) { VaultModel(it) } + view?.vaultMoved(vaultModels) + } + + override fun onError(e: Throwable) { + showError(e) + } + }) + } + + private fun nameComparator(): Comparator { + return compareBy { it.name.lowercase(Locale.getDefault()) } + .thenBy { it.name } + } + + private fun locationComparator(): Comparator { + return compareBy { it.cloudType.ordinal } + .thenBy { it.name.lowercase(Locale.getDefault()) } + .thenBy { it.name } + } + private enum class VaultAction { UNLOCK, RENAME } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt index 409b867f2..e7b6e8e4d 100644 --- a/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt @@ -17,6 +17,7 @@ import org.cryptomator.presentation.intent.Intents.settingsIntent import org.cryptomator.presentation.intent.VaultListIntent import org.cryptomator.presentation.model.CloudFolderModel import org.cryptomator.presentation.model.ProgressModel +import org.cryptomator.presentation.model.VaultListSortOption import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.presenter.VaultListPresenter import org.cryptomator.presentation.service.OpenWritableFileNotification @@ -27,6 +28,7 @@ import org.cryptomator.presentation.ui.callback.VaultListCallback import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog import org.cryptomator.presentation.ui.dialog.BetaConfirmationDialog import org.cryptomator.presentation.ui.dialog.CBCPasswordVaultsMigrationDialog +import org.cryptomator.presentation.ui.dialog.SortOverrideConfirmationDialog import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog import org.cryptomator.presentation.ui.dialog.UpdateAppDialog import org.cryptomator.presentation.ui.dialog.VaultDeleteConfirmationDialog @@ -45,7 +47,8 @@ class VaultListActivity : BaseActivity(Activi UpdateAppDialog.Callback, // BetaConfirmationDialog.Callback, // CBCPasswordVaultsMigrationDialog.Callback, // - BiometricAuthenticationMigration.Callback { + BiometricAuthenticationMigration.Callback, // + SortOverrideConfirmationDialog.Callback { @Inject lateinit var vaultListPresenter: VaultListPresenter @@ -98,6 +101,10 @@ class VaultListActivity : BaseActivity(Activi override fun getCustomMenuResource(): Int = R.menu.menu_vault_list override fun onMenuItemSelected(itemId: Int): Boolean = when (itemId) { + R.id.action_sort_by -> { + showDialog(SortOverrideConfirmationDialog.newInstance()) + true + } R.id.action_settings -> { vaultListPresenter.startIntent(settingsIntent()) true @@ -105,6 +112,10 @@ class VaultListActivity : BaseActivity(Activi else -> super.onMenuItemSelected(itemId) } + override fun onSortOverrideConfirmed(sortOption: VaultListSortOption) { + vaultListPresenter.onSortOverrideConfirmed(sortOption) + } + override fun isVaultLocked(vaultModel: VaultModel): Boolean { return vaultListFragment().isVaultLocked(vaultModel) } diff --git a/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/SortOverrideConfirmationDialog.kt b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/SortOverrideConfirmationDialog.kt new file mode 100644 index 000000000..2b612762c --- /dev/null +++ b/presentation/src/main/java/org/cryptomator/presentation/ui/dialog/SortOverrideConfirmationDialog.kt @@ -0,0 +1,42 @@ +package org.cryptomator.presentation.ui.dialog + +import android.content.DialogInterface +import androidx.appcompat.app.AlertDialog +import org.cryptomator.generator.Dialog +import org.cryptomator.presentation.R +import org.cryptomator.presentation.databinding.DialogSortOverrideConfirmationBinding +import org.cryptomator.presentation.model.VaultListSortOption + +@Dialog +class SortOverrideConfirmationDialog : + BaseDialog(DialogSortOverrideConfirmationBinding::inflate) { + + interface Callback { + + fun onSortOverrideConfirmed(sortOption: VaultListSortOption) + } + + public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog { + return builder // + .setPositiveButton(getString(R.string.dialog_sort_override_positive_button)) { _: DialogInterface, _: Int -> callback?.onSortOverrideConfirmed(selectedSortOption()) } // + .setNegativeButton(getString(R.string.dialog_sort_override_negative_button)) { _: DialogInterface, _: Int -> } // + .create() + } + + public override fun setupView() { + binding.tvMessage.text = getString(R.string.dialog_sort_override_message) + binding.rbSortByName.isChecked = true + } + + private fun selectedSortOption(): VaultListSortOption { + return when (binding.rgSortOptions.checkedRadioButtonId) { + R.id.rb_sort_by_location -> VaultListSortOption.LOCATION + else -> VaultListSortOption.NAME + } + } + + companion object { + + fun newInstance(): SortOverrideConfirmationDialog = SortOverrideConfirmationDialog() + } +} diff --git a/presentation/src/main/res/layout/dialog_sort_override_confirmation.xml b/presentation/src/main/res/layout/dialog_sort_override_confirmation.xml new file mode 100644 index 000000000..e1061fb69 --- /dev/null +++ b/presentation/src/main/res/layout/dialog_sort_override_confirmation.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/menu/menu_vault_list.xml b/presentation/src/main/res/menu/menu_vault_list.xml index 9ebc14213..80770a0c5 100644 --- a/presentation/src/main/res/menu/menu_vault_list.xml +++ b/presentation/src/main/res/menu/menu_vault_list.xml @@ -1,6 +1,11 @@ + Next Sort + Sort by... A - Z Z - A Newest first @@ -334,6 +335,11 @@ Cancel + This will override any existing sorting. Are you sure? + Close + Proceed + Sort by vault name + Sort by vault location @string/screen_file_browser_action_create_folder @string/screen_enter_vault_name_button_text