Skip to content

Commit 49d6705

Browse files
Option to sort vaults by vault name or cloud
1 parent 137dccf commit 49d6705

File tree

7 files changed

+163
-1
lines changed

7 files changed

+163
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.cryptomator.presentation.model
2+
3+
enum class VaultListSortOption {
4+
NAME,
5+
LOCATION
6+
}

presentation/src/main/java/org/cryptomator/presentation/presenter/VaultListPresenter.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import org.cryptomator.presentation.intent.UnlockVaultIntent
4646
import org.cryptomator.presentation.model.CloudModel
4747
import org.cryptomator.presentation.model.CloudTypeModel
4848
import org.cryptomator.presentation.model.ProgressModel
49+
import org.cryptomator.presentation.model.VaultListSortOption
4950
import org.cryptomator.presentation.model.VaultModel
5051
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
5152
import org.cryptomator.presentation.ui.activity.LicenseCheckActivity
@@ -66,6 +67,7 @@ import org.cryptomator.presentation.workflow.PermissionsResult
6667
import org.cryptomator.presentation.workflow.Workflow
6768
import org.cryptomator.util.SharedPreferencesHandler
6869
import org.cryptomator.util.crypto.CryptoMode
70+
import java.util.Locale
6971
import javax.inject.Inject
7072
import timber.log.Timber
7173

@@ -97,6 +99,7 @@ class VaultListPresenter @Inject constructor( //
9799
) : Presenter<VaultListView>(exceptionMappings) {
98100

99101
private var vaultAction: VaultAction? = null
102+
private var cachedVaults: List<Vault> = emptyList()
100103

101104
override fun workflows(): Iterable<Workflow<*>> {
102105
return listOf(addExistingVaultWorkflow, createNewVaultWorkflow)
@@ -391,6 +394,7 @@ class VaultListPresenter @Inject constructor( //
391394
get() {
392395
getVaultListUseCase.run(object : DefaultResultHandler<List<Vault>>() {
393396
override fun onSuccess(vaults: List<Vault>) {
397+
cachedVaults = vaults
394398
val vaultModels = vaults.mapTo(ArrayList()) { VaultModel(it) }
395399
if (vaultModels.isEmpty()) {
396400
view?.showVaultCreationHint()
@@ -601,6 +605,7 @@ class VaultListPresenter @Inject constructor( //
601605
.andToPosition(toPosition) //
602606
.run(object : DefaultResultHandler<List<Vault>>() {
603607
override fun onSuccess(vaults: List<Vault>) {
608+
cachedVaults = vaults
604609
view?.vaultMoved(vaults.mapTo(ArrayList()) { VaultModel(it) })
605610
}
606611

@@ -610,6 +615,51 @@ class VaultListPresenter @Inject constructor( //
610615
})
611616
}
612617

618+
fun onSortOverrideConfirmed(sortOption: VaultListSortOption) {
619+
if (cachedVaults.isEmpty()) {
620+
loadVaultList()
621+
return
622+
}
623+
624+
val sortedVaults = when (sortOption) {
625+
VaultListSortOption.NAME -> cachedVaults.sortedWith(nameComparator())
626+
VaultListSortOption.LOCATION -> cachedVaults.sortedWith(locationComparator())
627+
}
628+
629+
if (sortedVaults.map { it.id } == cachedVaults.map { it.id }) {
630+
return
631+
}
632+
633+
val reindexedVaults = sortedVaults.mapIndexed { index, vault ->
634+
Vault.aCopyOf(vault).withPosition(index).build()
635+
}
636+
637+
saveVaultsUseCase //
638+
.withVaults(reindexedVaults) //
639+
.run(object : DefaultResultHandler<List<Vault>>() {
640+
override fun onSuccess(vaults: List<Vault>) {
641+
cachedVaults = vaults
642+
val vaultModels = vaults.mapTo(ArrayList()) { VaultModel(it) }
643+
view?.vaultMoved(vaultModels)
644+
}
645+
646+
override fun onError(e: Throwable) {
647+
showError(e)
648+
}
649+
})
650+
}
651+
652+
private fun nameComparator(): Comparator<Vault> {
653+
return compareBy<Vault> { it.name.lowercase(Locale.getDefault()) }
654+
.thenBy { it.name }
655+
}
656+
657+
private fun locationComparator(): Comparator<Vault> {
658+
return compareBy<Vault> { it.cloudType.ordinal }
659+
.thenBy { it.name.lowercase(Locale.getDefault()) }
660+
.thenBy { it.name }
661+
}
662+
613663
private enum class VaultAction {
614664
UNLOCK, RENAME
615665
}

presentation/src/main/java/org/cryptomator/presentation/ui/activity/VaultListActivity.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import org.cryptomator.presentation.intent.Intents.settingsIntent
1717
import org.cryptomator.presentation.intent.VaultListIntent
1818
import org.cryptomator.presentation.model.CloudFolderModel
1919
import org.cryptomator.presentation.model.ProgressModel
20+
import org.cryptomator.presentation.model.VaultListSortOption
2021
import org.cryptomator.presentation.model.VaultModel
2122
import org.cryptomator.presentation.presenter.VaultListPresenter
2223
import org.cryptomator.presentation.service.OpenWritableFileNotification
@@ -27,6 +28,7 @@ import org.cryptomator.presentation.ui.callback.VaultListCallback
2728
import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog
2829
import org.cryptomator.presentation.ui.dialog.BetaConfirmationDialog
2930
import org.cryptomator.presentation.ui.dialog.CBCPasswordVaultsMigrationDialog
31+
import org.cryptomator.presentation.ui.dialog.SortOverrideConfirmationDialog
3032
import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog
3133
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
3234
import org.cryptomator.presentation.ui.dialog.VaultDeleteConfirmationDialog
@@ -45,7 +47,8 @@ class VaultListActivity : BaseActivity<ActivityLayoutObscureAwareBinding>(Activi
4547
UpdateAppDialog.Callback, //
4648
BetaConfirmationDialog.Callback, //
4749
CBCPasswordVaultsMigrationDialog.Callback, //
48-
BiometricAuthenticationMigration.Callback {
50+
BiometricAuthenticationMigration.Callback, //
51+
SortOverrideConfirmationDialog.Callback {
4952

5053
@Inject
5154
lateinit var vaultListPresenter: VaultListPresenter
@@ -98,13 +101,21 @@ class VaultListActivity : BaseActivity<ActivityLayoutObscureAwareBinding>(Activi
98101
override fun getCustomMenuResource(): Int = R.menu.menu_vault_list
99102

100103
override fun onMenuItemSelected(itemId: Int): Boolean = when (itemId) {
104+
R.id.action_sort_by -> {
105+
showDialog(SortOverrideConfirmationDialog.newInstance())
106+
true
107+
}
101108
R.id.action_settings -> {
102109
vaultListPresenter.startIntent(settingsIntent())
103110
true
104111
}
105112
else -> super.onMenuItemSelected(itemId)
106113
}
107114

115+
override fun onSortOverrideConfirmed(sortOption: VaultListSortOption) {
116+
vaultListPresenter.onSortOverrideConfirmed(sortOption)
117+
}
118+
108119
override fun isVaultLocked(vaultModel: VaultModel): Boolean {
109120
return vaultListFragment().isVaultLocked(vaultModel)
110121
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.cryptomator.presentation.ui.dialog
2+
3+
import android.content.DialogInterface
4+
import androidx.appcompat.app.AlertDialog
5+
import org.cryptomator.generator.Dialog
6+
import org.cryptomator.presentation.R
7+
import org.cryptomator.presentation.databinding.DialogSortOverrideConfirmationBinding
8+
import org.cryptomator.presentation.model.VaultListSortOption
9+
10+
@Dialog
11+
class SortOverrideConfirmationDialog :
12+
BaseDialog<SortOverrideConfirmationDialog.Callback, DialogSortOverrideConfirmationBinding>(DialogSortOverrideConfirmationBinding::inflate) {
13+
14+
interface Callback {
15+
16+
fun onSortOverrideConfirmed(sortOption: VaultListSortOption)
17+
}
18+
19+
public override fun setupDialog(builder: AlertDialog.Builder): android.app.Dialog {
20+
return builder //
21+
.setPositiveButton(getString(R.string.dialog_sort_override_positive_button)) { _: DialogInterface, _: Int -> callback?.onSortOverrideConfirmed(selectedSortOption()) } //
22+
.setNegativeButton(getString(R.string.dialog_sort_override_negative_button)) { _: DialogInterface, _: Int -> } //
23+
.create()
24+
}
25+
26+
public override fun setupView() {
27+
binding.tvMessage.text = getString(R.string.dialog_sort_override_message)
28+
binding.rbSortByName.isChecked = true
29+
}
30+
31+
private fun selectedSortOption(): VaultListSortOption {
32+
return when (binding.rgSortOptions.checkedRadioButtonId) {
33+
R.id.rb_sort_by_location -> VaultListSortOption.LOCATION
34+
else -> VaultListSortOption.NAME
35+
}
36+
}
37+
38+
companion object {
39+
40+
fun newInstance(): SortOverrideConfirmationDialog = SortOverrideConfirmationDialog()
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:layout_width="match_parent"
4+
android:layout_height="match_parent">
5+
6+
<LinearLayout
7+
android:layout_width="match_parent"
8+
android:layout_height="wrap_content"
9+
android:orientation="vertical"
10+
android:paddingStart="@dimen/activity_horizontal_margin"
11+
android:paddingTop="@dimen/activity_vertical_margin"
12+
android:paddingEnd="@dimen/activity_horizontal_margin"
13+
android:paddingBottom="@dimen/activity_vertical_margin">
14+
15+
<TextView
16+
android:id="@+id/tv_message"
17+
android:layout_width="match_parent"
18+
android:layout_height="wrap_content"
19+
android:textSize="16sp" />
20+
21+
<RadioGroup
22+
android:id="@+id/rg_sort_options"
23+
android:layout_width="match_parent"
24+
android:layout_height="wrap_content"
25+
android:layout_marginTop="@dimen/activity_vertical_margin"
26+
android:orientation="vertical">
27+
28+
<RadioButton
29+
android:id="@+id/rb_sort_by_name"
30+
android:layout_width="match_parent"
31+
android:layout_height="wrap_content"
32+
android:text="@string/dialog_sort_override_option_name" />
33+
34+
<RadioButton
35+
android:id="@+id/rb_sort_by_location"
36+
android:layout_width="match_parent"
37+
android:layout_height="wrap_content"
38+
android:text="@string/dialog_sort_override_option_location" />
39+
</RadioGroup>
40+
41+
</LinearLayout>
42+
</androidx.core.widget.NestedScrollView>

presentation/src/main/res/menu/menu_vault_list.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<menu xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:app="http://schemas.android.com/apk/res-auto">
4+
<item
5+
android:id="@+id/action_sort_by"
6+
android:orderInCategory="90"
7+
android:title="@string/menu_vault_list_sort_by"
8+
app:showAsAction="never" />
49
<item
510
android:id="@+id/action_settings"
611
android:orderInCategory="100"

presentation/src/main/res/values/strings.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
<string name="snack_bar_action_title_search_next">Next</string>
7979

8080
<string name="snack_bar_action_title_sort">Sort</string>
81+
<string name="menu_vault_list_sort_by">Sort by...</string>
8182
<string name="snack_bar_action_title_sort_az">A - Z</string>
8283
<string name="snack_bar_action_title_sort_za">Z - A</string>
8384
<string name="snack_bar_action_title_sort_newest">Newest first</string>
@@ -334,6 +335,11 @@
334335

335336
<!-- # dialogs -->
336337
<string name="dialog_button_cancel">Cancel</string>
338+
<string name="dialog_sort_override_message">This will override any existing sorting. Are you sure?</string>
339+
<string name="dialog_sort_override_negative_button">Close</string>
340+
<string name="dialog_sort_override_positive_button">Proceed</string>
341+
<string name="dialog_sort_override_option_name">Sort by vault name</string>
342+
<string name="dialog_sort_override_option_location">Sort by vault location</string>
337343

338344
<string name="dialog_create_folder_title" translatable="false">@string/screen_file_browser_action_create_folder</string>
339345
<string name="dialog_create_folder_positive_button" translatable="false">@string/screen_enter_vault_name_button_text</string>

0 commit comments

Comments
 (0)