Expand show hidden folders to also cover files (#1059)
* PasswordItem: only strip .gpg suffixes Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Add preference key and migration for showing all hidden contents Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Allow showing both hidden files and directories Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Add tests for hidden folder setting migration Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Add changelog entry Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Slightly improve migration logic Skip migration if old key is not found and always delete the previous key even if its set to false. Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Tweak wording Suggested-by: Fabian Henneke <fabian@henneke.me> Signed-off-by: Harsh Shandilya <me@msfjarvis.dev> * Assert previous key's removal in tests Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
8ec3320df7
commit
1ce3ef4ea3
12 changed files with 60 additions and 21 deletions
|
@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
- A descriptive error message is shown if no username is specified in the Git server settings
|
- A descriptive error message is shown if no username is specified in the Git server settings
|
||||||
- Remove explicit protocol choice from Git server settings, it is now inferred from your URL
|
- Remove explicit protocol choice from Git server settings, it is now inferred from your URL
|
||||||
|
- 'Show hidden folders' is now 'Show hidden files and folders'
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress("DEPRECATION")
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
package com.zeapo.pwdstore
|
package com.zeapo.pwdstore
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -13,10 +14,10 @@ import com.zeapo.pwdstore.git.config.Protocol
|
||||||
import com.zeapo.pwdstore.utils.PreferenceKeys
|
import com.zeapo.pwdstore.utils.PreferenceKeys
|
||||||
import com.zeapo.pwdstore.utils.getString
|
import com.zeapo.pwdstore.utils.getString
|
||||||
import com.zeapo.pwdstore.utils.sharedPrefs
|
import com.zeapo.pwdstore.utils.sharedPrefs
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
class MigrationsTest {
|
class MigrationsTest {
|
||||||
|
|
||||||
private fun checkOldKeysAreRemoved(context: Context) = with(context.sharedPrefs) {
|
private fun checkOldKeysAreRemoved(context: Context) = with(context.sharedPrefs) {
|
||||||
|
@ -84,4 +85,25 @@ class MigrationsTest {
|
||||||
"https://github.com/Android-Password-Store/pass-test"
|
"https://github.com/Android-Password-Store/pass-test"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun verifyHiddenFoldersMigrationIfDisabled() {
|
||||||
|
val context = Application.instance.applicationContext
|
||||||
|
context.sharedPrefs.edit { clear() }
|
||||||
|
runMigrations(context)
|
||||||
|
assertEquals(true, context.sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, true))
|
||||||
|
assertEquals(false, context.sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun verifyHiddenFoldersMigrationIfEnabled() {
|
||||||
|
val context = Application.instance.applicationContext
|
||||||
|
context.sharedPrefs.edit {
|
||||||
|
clear()
|
||||||
|
putBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, true)
|
||||||
|
}
|
||||||
|
runMigrations(context)
|
||||||
|
assertEquals(false, context.sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, false))
|
||||||
|
assertEquals(true, context.sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.net.URI
|
||||||
|
|
||||||
fun runMigrations(context: Context) {
|
fun runMigrations(context: Context) {
|
||||||
migrateToGitUrlBasedConfig(context)
|
migrateToGitUrlBasedConfig(context)
|
||||||
|
migrateToHideAll(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateToGitUrlBasedConfig(context: Context) {
|
private fun migrateToGitUrlBasedConfig(context: Context) {
|
||||||
|
@ -84,3 +85,12 @@ private fun migrateToGitUrlBasedConfig(context: Context) {
|
||||||
e { "Failed to migrate to URL-based Git config, generated URL is invalid" }
|
e { "Failed to migrate to URL-based Git config, generated URL is invalid" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun migrateToHideAll(context: Context) {
|
||||||
|
context.sharedPrefs.all[PreferenceKeys.SHOW_HIDDEN_FOLDERS] ?: return
|
||||||
|
val isHidden = context.sharedPrefs.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, false)
|
||||||
|
context.sharedPrefs.edit {
|
||||||
|
remove(PreferenceKeys.SHOW_HIDDEN_FOLDERS)
|
||||||
|
putBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, isHidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ private fun PasswordItem.Companion.makeComparator(
|
||||||
PasswordRepository.PasswordSortOrder.FOLDER_FIRST -> compareBy { it.type }
|
PasswordRepository.PasswordSortOrder.FOLDER_FIRST -> compareBy { it.type }
|
||||||
// In order to let INDEPENDENT not distinguish between items based on their type, we simply
|
// In order to let INDEPENDENT not distinguish between items based on their type, we simply
|
||||||
// declare them all equal at this stage.
|
// declare them all equal at this stage.
|
||||||
PasswordRepository.PasswordSortOrder.INDEPENDENT -> Comparator<PasswordItem> { _, _ -> 0 }
|
PasswordRepository.PasswordSortOrder.INDEPENDENT -> Comparator { _, _ -> 0 }
|
||||||
PasswordRepository.PasswordSortOrder.FILE_FIRST -> compareByDescending { it.type }
|
PasswordRepository.PasswordSortOrder.FILE_FIRST -> compareByDescending { it.type }
|
||||||
PasswordRepository.PasswordSortOrder.RECENTLY_USED -> PasswordRepository.PasswordSortOrder.RECENTLY_USED.comparator
|
PasswordRepository.PasswordSortOrder.RECENTLY_USED -> PasswordRepository.PasswordSortOrder.RECENTLY_USED.comparator
|
||||||
}
|
}
|
||||||
|
@ -140,8 +140,8 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel
|
||||||
private val root
|
private val root
|
||||||
get() = PasswordRepository.getRepositoryDirectory()
|
get() = PasswordRepository.getRepositoryDirectory()
|
||||||
private val settings by lazy { application.sharedPrefs }
|
private val settings by lazy { application.sharedPrefs }
|
||||||
private val showHiddenDirs
|
private val showHiddenContents
|
||||||
get() = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, false)
|
get() = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false)
|
||||||
private val defaultSearchMode
|
private val defaultSearchMode
|
||||||
get() = if (settings.getBoolean(PreferenceKeys.FILTER_RECURSIVELY, true)) {
|
get() = if (settings.getBoolean(PreferenceKeys.FILTER_RECURSIVELY, true)) {
|
||||||
SearchMode.RecursivelyInSubdirectories
|
SearchMode.RecursivelyInSubdirectories
|
||||||
|
@ -254,8 +254,9 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel
|
||||||
}.asLiveData(Dispatchers.IO)
|
}.asLiveData(Dispatchers.IO)
|
||||||
|
|
||||||
private fun shouldTake(file: File) = with(file) {
|
private fun shouldTake(file: File) = with(file) {
|
||||||
|
if (showHiddenContents) return true
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
!isHidden || showHiddenDirs
|
!isHidden
|
||||||
} else {
|
} else {
|
||||||
!isHidden && file.extension == "gpg"
|
!isHidden && file.extension == "gpg"
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ open class PasswordItemRecyclerAdapter :
|
||||||
|
|
||||||
fun bind(item: PasswordItem) {
|
fun bind(item: PasswordItem) {
|
||||||
val settings = itemView.context.sharedPrefs
|
val settings = itemView.context.sharedPrefs
|
||||||
val showHidden = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, false)
|
val showHidden = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false)
|
||||||
val parentPath = item.fullPathToParent.replace("(^/)|(/$)".toRegex(), "")
|
val parentPath = item.fullPathToParent.replace("(^/)|(/$)".toRegex(), "")
|
||||||
val source = if (parentPath.isNotEmpty()) {
|
val source = if (parentPath.isNotEmpty()) {
|
||||||
"$parentPath\n$item"
|
"$parentPath\n$item"
|
||||||
|
@ -62,8 +62,8 @@ open class PasswordItemRecyclerAdapter :
|
||||||
name.text = spannable
|
name.text = spannable
|
||||||
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
||||||
folderIndicator.visibility = View.VISIBLE
|
folderIndicator.visibility = View.VISIBLE
|
||||||
val children = item.file.listFiles { pathname ->
|
val children = with(item.file) {
|
||||||
!(!showHidden && (pathname.isDirectory && pathname.isHidden))
|
if (showHidden) listFiles() else listFiles { pathname -> pathname.isDirectory && !pathname.isHidden }
|
||||||
} ?: emptyArray<File>()
|
} ?: emptyArray<File>()
|
||||||
val count = children.size
|
val count = children.size
|
||||||
childCount.visibility = if (count > 0) View.VISIBLE else View.GONE
|
childCount.visibility = if (count > 0) View.VISIBLE else View.GONE
|
||||||
|
|
|
@ -33,7 +33,7 @@ data class PasswordItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return name.replace(".gpg", "")
|
return name.replace("\\.gpg$".toRegex(), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
|
|
@ -226,12 +226,10 @@ open class PasswordRepository protected constructor() {
|
||||||
// We need to recover the passwords then parse the files
|
// We need to recover the passwords then parse the files
|
||||||
val passList = getFilesList(path).also { it.sortBy { f -> f.name } }
|
val passList = getFilesList(path).also { it.sortBy { f -> f.name } }
|
||||||
val passwordList = ArrayList<PasswordItem>()
|
val passwordList = ArrayList<PasswordItem>()
|
||||||
val showHiddenDirs = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_FOLDERS, false)
|
val showHidden = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false)
|
||||||
|
|
||||||
if (passList.size == 0) return passwordList
|
if (passList.size == 0) return passwordList
|
||||||
if (showHiddenDirs) {
|
if (!showHidden) {
|
||||||
passList.filter { !(it.isFile && it.isHidden) }.toCollection(passList.apply { clear() })
|
|
||||||
} else {
|
|
||||||
passList.filter { !it.isHidden }.toCollection(passList.apply { clear() })
|
passList.filter { !it.isHidden }.toCollection(passList.apply { clear() })
|
||||||
}
|
}
|
||||||
passList.forEach { file ->
|
passList.forEach { file ->
|
||||||
|
|
|
@ -28,16 +28,21 @@ object PreferenceKeys {
|
||||||
const val GIT_EXTERNAL = "git_external"
|
const val GIT_EXTERNAL = "git_external"
|
||||||
const val GIT_EXTERNAL_REPO = "git_external_repo"
|
const val GIT_EXTERNAL_REPO = "git_external_repo"
|
||||||
const val GIT_REMOTE_AUTH = "git_remote_auth"
|
const val GIT_REMOTE_AUTH = "git_remote_auth"
|
||||||
|
|
||||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||||
const val GIT_REMOTE_LOCATION = "git_remote_location"
|
const val GIT_REMOTE_LOCATION = "git_remote_location"
|
||||||
|
|
||||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||||
const val GIT_REMOTE_PORT = "git_remote_port"
|
const val GIT_REMOTE_PORT = "git_remote_port"
|
||||||
|
|
||||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||||
const val GIT_REMOTE_PROTOCOL = "git_remote_protocol"
|
const val GIT_REMOTE_PROTOCOL = "git_remote_protocol"
|
||||||
const val GIT_DELETE_REPO = "git_delete_repo"
|
const val GIT_DELETE_REPO = "git_delete_repo"
|
||||||
|
|
||||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||||
const val GIT_REMOTE_SERVER = "git_remote_server"
|
const val GIT_REMOTE_SERVER = "git_remote_server"
|
||||||
const val GIT_REMOTE_URL = "git_remote_url"
|
const val GIT_REMOTE_URL = "git_remote_url"
|
||||||
|
|
||||||
@Deprecated("Use GIT_REMOTE_URL instead")
|
@Deprecated("Use GIT_REMOTE_URL instead")
|
||||||
const val GIT_REMOTE_USERNAME = "git_remote_username"
|
const val GIT_REMOTE_USERNAME = "git_remote_username"
|
||||||
const val GIT_SERVER_INFO = "git_server_info"
|
const val GIT_SERVER_INFO = "git_server_info"
|
||||||
|
@ -55,7 +60,13 @@ object PreferenceKeys {
|
||||||
const val REPO_CHANGED = "repo_changed"
|
const val REPO_CHANGED = "repo_changed"
|
||||||
const val SEARCH_ON_START = "search_on_start"
|
const val SEARCH_ON_START = "search_on_start"
|
||||||
const val SHOW_EXTRA_CONTENT = "show_extra_content"
|
const val SHOW_EXTRA_CONTENT = "show_extra_content"
|
||||||
|
|
||||||
|
@Deprecated(
|
||||||
|
message = "Use SHOW_HIDDEN_CONTENTS instead",
|
||||||
|
replaceWith = ReplaceWith("PreferenceKeys.SHOW_HIDDEN_CONTENTS")
|
||||||
|
)
|
||||||
const val SHOW_HIDDEN_FOLDERS = "show_hidden_folders"
|
const val SHOW_HIDDEN_FOLDERS = "show_hidden_folders"
|
||||||
|
const val SHOW_HIDDEN_CONTENTS = "show_hidden_contents"
|
||||||
const val SORT_ORDER = "sort_order"
|
const val SORT_ORDER = "sort_order"
|
||||||
const val SHOW_PASSWORD = "show_password"
|
const val SHOW_PASSWORD = "show_password"
|
||||||
const val SSH_KEY = "ssh_key"
|
const val SSH_KEY = "ssh_key"
|
||||||
|
|
|
@ -269,8 +269,6 @@
|
||||||
<string name="access_sdcard_text">O local do armazenamento está em seu cartão SD ou armazenamento interno, mas o aplicativo não tem permissão para acessá-lo.</string>
|
<string name="access_sdcard_text">O local do armazenamento está em seu cartão SD ou armazenamento interno, mas o aplicativo não tem permissão para acessá-lo.</string>
|
||||||
<string name="your_public_key">Sua chave pública</string>
|
<string name="your_public_key">Sua chave pública</string>
|
||||||
<string name="error_generate_ssh_key">Erro ao tentar gerar a chave SSH</string>
|
<string name="error_generate_ssh_key">Erro ao tentar gerar a chave SSH</string>
|
||||||
<string name="pref_show_hidden_title">Mostrar pastas ocultas</string>
|
|
||||||
<string name="pref_show_hidden_summary">Incluir diretórios ocultos na lista de senhas</string>
|
|
||||||
<string name="title_create_folder">Criar pasta</string>
|
<string name="title_create_folder">Criar pasta</string>
|
||||||
<string name="title_rename_folder">Renomear pasta</string>
|
<string name="title_rename_folder">Renomear pasta</string>
|
||||||
<string name="message_category_error_empty_field">O nome da categoria não pode ser vazio</string>
|
<string name="message_category_error_empty_field">O nome da categoria não pode ser vazio</string>
|
||||||
|
|
|
@ -251,8 +251,6 @@
|
||||||
<string name="ssh_openkeystore_clear_keyid">Очистить сохраненный SSH Key идентификатор OpenKystortore</string>
|
<string name="ssh_openkeystore_clear_keyid">Очистить сохраненный SSH Key идентификатор OpenKystortore</string>
|
||||||
<string name="your_public_key">Ваш публичный ключ</string>
|
<string name="your_public_key">Ваш публичный ключ</string>
|
||||||
<string name="error_generate_ssh_key">Возникла ошибка при попытке генерации ssh ключа</string>
|
<string name="error_generate_ssh_key">Возникла ошибка при попытке генерации ssh ключа</string>
|
||||||
<string name="pref_show_hidden_title">Показать скрытые папки</string>
|
|
||||||
<string name="pref_show_hidden_summary">Включить скрытые директории в список паролей</string>
|
|
||||||
<string name="title_create_folder">Создать папку</string>
|
<string name="title_create_folder">Создать папку</string>
|
||||||
<string name="button_create">Создать</string>
|
<string name="button_create">Создать</string>
|
||||||
<string name="pref_search_on_start">Открыть поиск на старте</string>
|
<string name="pref_search_on_start">Открыть поиск на старте</string>
|
||||||
|
|
|
@ -302,8 +302,8 @@
|
||||||
<string name="access_sdcard_text">The store location is in your SD Card or Internal storage, but the app does not have permission to access it.</string>
|
<string name="access_sdcard_text">The store location is in your SD Card or Internal storage, but the app does not have permission to access it.</string>
|
||||||
<string name="your_public_key">Your public key</string>
|
<string name="your_public_key">Your public key</string>
|
||||||
<string name="error_generate_ssh_key">Error while trying to generate the ssh-key</string>
|
<string name="error_generate_ssh_key">Error while trying to generate the ssh-key</string>
|
||||||
<string name="pref_show_hidden_title">Show hidden folders</string>
|
<string name="pref_show_hidden_title">Show all files and folders</string>
|
||||||
<string name="pref_show_hidden_summary">Include hidden directories in the password list</string>
|
<string name="pref_show_hidden_summary">Include non-password files and directories in the password list</string>
|
||||||
<string name="title_create_folder">Create folder</string>
|
<string name="title_create_folder">Create folder</string>
|
||||||
<string name="title_rename_folder">Rename folder</string>
|
<string name="title_rename_folder">Rename folder</string>
|
||||||
<string name="message_category_error_empty_field">Category name can\'t be empty</string>
|
<string name="message_category_error_empty_field">Category name can\'t be empty</string>
|
||||||
|
|
|
@ -127,7 +127,7 @@
|
||||||
app:title="@string/pref_search_on_start" />
|
app:title="@string/pref_search_on_start" />
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
app:defaultValue="false"
|
app:defaultValue="false"
|
||||||
app:key="show_hidden_folders"
|
app:key="show_hidden_contents"
|
||||||
app:persistent="true"
|
app:persistent="true"
|
||||||
app:summary="@string/pref_show_hidden_summary"
|
app:summary="@string/pref_show_hidden_summary"
|
||||||
app:title="@string/pref_show_hidden_title" />
|
app:title="@string/pref_show_hidden_title" />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue