aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-12-05 06:07:18 +0530
committerGitHub <noreply@github.com>2020-12-05 06:07:18 +0530
commit5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch)
treeaee323a08253fe7b863976eff92a1bd412bd1430 /app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments
parent8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff)
downloadHardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.tar.gz
HardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.zip
Refactor package structure (#1233)
* idea: default test runner to Gradle * Kick off package structure revamp * Reparent all classes under dev.msfjarvis.aps Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments')
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt59
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt66
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt192
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt30
4 files changed, 347 insertions, 0 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt
new file mode 100644
index 00000000..aed0b7a3
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2019-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ *
+ */
+
+package dev.msfjarvis.aps.ui.onboarding.fragments
+
+import android.os.Bundle
+import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
+import androidx.fragment.app.Fragment
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.databinding.FragmentCloneBinding
+import dev.msfjarvis.aps.ui.git.config.GitServerConfigActivity
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import dev.msfjarvis.aps.util.extensions.finish
+import dev.msfjarvis.aps.util.extensions.performTransactionWithBackStack
+import dev.msfjarvis.aps.util.extensions.sharedPrefs
+import dev.msfjarvis.aps.util.extensions.viewBinding
+
+class CloneFragment : Fragment(R.layout.fragment_clone) {
+
+ private val binding by viewBinding(FragmentCloneBinding::bind)
+
+ private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs }
+
+ private val cloneAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == AppCompatActivity.RESULT_OK) {
+ settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) }
+ finish()
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.cloneRemote.setOnClickListener {
+ cloneToHiddenDir()
+ }
+ binding.createLocal.setOnClickListener {
+ parentFragmentManager.performTransactionWithBackStack(RepoLocationFragment.newInstance())
+ }
+ }
+
+ /**
+ * Clones a remote Git repository to the app's private directory
+ */
+ private fun cloneToHiddenDir() {
+ settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, false) }
+ cloneAction.launch(GitServerConfigActivity.createCloneIntent(requireContext()))
+ }
+
+ companion object {
+
+ fun newInstance(): CloneFragment = CloneFragment()
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt
new file mode 100644
index 00000000..5df830b1
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/KeySelectionFragment.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.onboarding.fragments
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.ui.crypto.GetKeyIdsActivity
+import dev.msfjarvis.aps.databinding.FragmentKeySelectionBinding
+import dev.msfjarvis.aps.data.repo.PasswordRepository
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import dev.msfjarvis.aps.util.extensions.commitChange
+import dev.msfjarvis.aps.util.extensions.finish
+import dev.msfjarvis.aps.util.extensions.sharedPrefs
+import dev.msfjarvis.aps.util.extensions.viewBinding
+import java.io.File
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import me.msfjarvis.openpgpktx.util.OpenPgpApi
+
+class KeySelectionFragment : Fragment(R.layout.fragment_key_selection) {
+
+ private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs }
+ private val binding by viewBinding(FragmentKeySelectionBinding::bind)
+
+ private val gpgKeySelectAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == AppCompatActivity.RESULT_OK) {
+ result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds ->
+ lifecycleScope.launch {
+ withContext(Dispatchers.IO) {
+ val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id")
+ gpgIdentifierFile.writeText(keyIds.joinToString("\n"))
+ }
+ settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) }
+ requireActivity().commitChange(getString(
+ R.string.git_commit_gpg_id,
+ getString(R.string.app_name)
+ ))
+ }
+ }
+ } else {
+ throw IllegalStateException("Failed to initialize repository state.")
+ }
+ finish()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.selectKey.setOnClickListener { gpgKeySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java)) }
+ }
+
+ companion object {
+
+ fun newInstance() = KeySelectionFragment()
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt
new file mode 100644
index 00000000..08090d1b
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/RepoLocationFragment.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ *
+ */
+
+package dev.msfjarvis.aps.ui.onboarding.fragments
+
+import android.Manifest
+import android.os.Bundle
+import android.view.View
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.edit
+import androidx.fragment.app.Fragment
+import com.github.ajalt.timberkt.d
+import com.github.ajalt.timberkt.e
+import com.github.michaelbull.result.onFailure
+import com.github.michaelbull.result.runCatching
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.ui.settings.UserPreference
+import dev.msfjarvis.aps.databinding.FragmentRepoLocationBinding
+import dev.msfjarvis.aps.data.repo.PasswordRepository
+import dev.msfjarvis.aps.util.settings.PasswordSortOrder
+import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import dev.msfjarvis.aps.util.extensions.finish
+import dev.msfjarvis.aps.util.extensions.getString
+import dev.msfjarvis.aps.util.extensions.isPermissionGranted
+import dev.msfjarvis.aps.util.extensions.listFilesRecursively
+import dev.msfjarvis.aps.util.extensions.performTransactionWithBackStack
+import dev.msfjarvis.aps.util.extensions.sharedPrefs
+import dev.msfjarvis.aps.util.extensions.viewBinding
+import java.io.File
+
+class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) {
+
+ private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs }
+ private val binding by viewBinding(FragmentRepoLocationBinding::bind)
+ private val sortOrder: PasswordSortOrder
+ get() = PasswordSortOrder.getSortOrder(settings)
+
+ private val repositoryInitAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == AppCompatActivity.RESULT_OK) {
+ initializeRepositoryInfo()
+ }
+ }
+
+ private val externalDirectorySelectAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == AppCompatActivity.RESULT_OK) {
+ if (checkExternalDirectory()) {
+ finish()
+ } else {
+ createRepository()
+ }
+ }
+ }
+
+ private val externalDirPermGrantedAction = createPermGrantedAction {
+ externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
+ }
+
+ private val repositoryUsePermGrantedAction = createPermGrantedAction {
+ initializeRepositoryInfo()
+ }
+
+ private val repositoryChangePermGrantedAction = createPermGrantedAction {
+ repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.hidden.setOnClickListener {
+ createRepoInHiddenDir()
+ }
+
+ binding.sdcard.setOnClickListener {
+ createRepoFromExternalDir()
+ }
+ }
+
+ /**
+ * Initializes an empty repository in the app's private directory
+ */
+ private fun createRepoInHiddenDir() {
+ settings.edit {
+ putBoolean(PreferenceKeys.GIT_EXTERNAL, false)
+ remove(PreferenceKeys.GIT_EXTERNAL_REPO)
+ }
+ initializeRepositoryInfo()
+ }
+
+ /**
+ * Initializes an empty repository in a selected directory if one does not already exist
+ */
+ private fun createRepoFromExternalDir() {
+ settings.edit { putBoolean(PreferenceKeys.GIT_EXTERNAL, true) }
+ val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
+ if (externalRepo == null) {
+ if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ externalDirPermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ } else {
+ // Unlikely we have storage permissions without user ever selecting a directory,
+ // but let's not assume.
+ externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
+ }
+ } else {
+ MaterialAlertDialogBuilder(requireActivity())
+ .setTitle(resources.getString(R.string.directory_selected_title))
+ .setMessage(resources.getString(R.string.directory_selected_message, externalRepo))
+ .setPositiveButton(resources.getString(R.string.use)) { _, _ ->
+ if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ repositoryUsePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ } else {
+ initializeRepositoryInfo()
+ }
+ }
+ .setNegativeButton(resources.getString(R.string.change)) { _, _ ->
+ if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ repositoryChangePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ } else {
+ repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
+ }
+ }
+ .show()
+ }
+ }
+
+ private fun checkExternalDirectory(): Boolean {
+ if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false) &&
+ settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) != null) {
+ val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
+ val dir = externalRepoPath?.let { File(it) }
+ if (dir != null && // The directory could be opened
+ dir.exists() && // The directory exists
+ dir.isDirectory && // The directory, is really a directory
+ dir.listFilesRecursively().isNotEmpty() && // The directory contains files
+ // The directory contains a non-zero number of password files
+ PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(), sortOrder).isNotEmpty()
+ ) {
+ PasswordRepository.closeRepository()
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun createRepository() {
+ val localDir = PasswordRepository.getRepositoryDirectory()
+ runCatching {
+ check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" }
+ PasswordRepository.createRepository(localDir)
+ if (!PasswordRepository.isInitialized) {
+ PasswordRepository.initialize()
+ }
+ parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance())
+ }.onFailure { e ->
+ e(e)
+ if (!localDir.delete()) {
+ d { "Failed to delete local repository: $localDir" }
+ }
+ finish()
+ }
+ }
+
+ private fun initializeRepositoryInfo() {
+ val externalRepo = settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)
+ val externalRepoPath = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
+ if (externalRepo && !isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ return
+ }
+ if (externalRepo && externalRepoPath != null) {
+ if (checkExternalDirectory()) {
+ finish()
+ return
+ }
+ }
+ createRepository()
+ }
+
+ private fun createPermGrantedAction(block: () -> Unit) =
+ registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
+ if (granted) {
+ block.invoke()
+ }
+ }
+
+ companion object {
+
+ fun newInstance(): RepoLocationFragment = RepoLocationFragment()
+ }
+}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt
new file mode 100644
index 00000000..696aba17
--- /dev/null
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/WelcomeFragment.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2019-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.ui.onboarding.fragments
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.annotation.Keep
+import androidx.fragment.app.Fragment
+import dev.msfjarvis.aps.R
+import dev.msfjarvis.aps.ui.settings.UserPreference
+import dev.msfjarvis.aps.databinding.FragmentWelcomeBinding
+import dev.msfjarvis.aps.util.extensions.performTransactionWithBackStack
+import dev.msfjarvis.aps.util.extensions.viewBinding
+
+@Keep
+@Suppress("unused")
+class WelcomeFragment : Fragment(R.layout.fragment_welcome) {
+
+ private val binding by viewBinding(FragmentWelcomeBinding::bind)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ binding.letsGo.setOnClickListener { parentFragmentManager.performTransactionWithBackStack(CloneFragment.newInstance()) }
+ binding.settingsButton.setOnClickListener { startActivity(Intent(requireContext(), UserPreference::class.java)) }
+ }
+}