diff options
| author | Harsh Shandilya <me@msfjarvis.dev> | 2020-12-05 06:07:18 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-05 06:07:18 +0530 |
| commit | 5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch) | |
| tree | aee323a08253fe7b863976eff92a1bd412bd1430 /app/src/main/java/dev/msfjarvis/aps/ui/onboarding | |
| parent | 8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff) | |
| download | HardenedPass-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')
5 files changed, 373 insertions, 0 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/activity/OnboardingActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/activity/OnboardingActivity.kt new file mode 100644 index 00000000..b0443447 --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/activity/OnboardingActivity.kt @@ -0,0 +1,26 @@ +/* + * Copyright © 2019-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package dev.msfjarvis.aps.ui.onboarding.activity + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import dev.msfjarvis.aps.R + +class OnboardingActivity : AppCompatActivity(R.layout.activity_onboarding) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + supportActionBar?.hide() + } + + override fun onBackPressed() { + if (supportFragmentManager.backStackEntryCount == 0) { + finishAffinity() + } else { + super.onBackPressed() + } + } +} 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)) } + } +} |
