From 4ba3b75f858251099f18f07be700f9900128f8e1 Mon Sep 17 00:00:00 2001 From: Aditya Wasan Date: Tue, 15 Sep 2020 21:53:12 +0530 Subject: Update on-boarding UI (#1099) * Add onboarding flow from v2 Signed-off-by: Aditya Wasan * Minor fixes Signed-off-by: Aditya Wasan * Add changelog entry Signed-off-by: Aditya Wasan * Remove old activity from manifest Signed-off-by: Aditya Wasan * Remove view type prefix from view ids Signed-off-by: Aditya Wasan * Review fixes Signed-off-by: Aditya Wasan * Treewide: Reformat code Signed-off-by: Aditya Wasan * Moar review fixes Signed-off-by: Aditya Wasan * Revert "Treewide: Reformat code" This reverts commit 348ef0050942526a55890b245afec8d7fee4d81e. Signed-off-by: Harsh Shandilya * onboarding: cleanup OnboardingActivity init Signed-off-by: Harsh Shandilya * Remove unused layout Signed-off-by: Harsh Shandilya * Remove unnecessary ConstraintLayout Signed-off-by: Harsh Shandilya * Shorten animation duration Signed-off-by: Harsh Shandilya * onboarding: use viewBinding extension in fragments Signed-off-by: Harsh Shandilya Co-authored-by: Harsh Shandilya --- CHANGELOG.md | 1 + app/src/main/AndroidManifest.xml | 2 +- .../java/com/zeapo/pwdstore/OnboardingActivity.kt | 208 --------------------- .../main/java/com/zeapo/pwdstore/PasswordStore.kt | 1 + .../ui/onboarding/activity/OnboardingActivity.kt | 26 +++ .../ui/onboarding/fragments/CloneFragment.kt | 59 ++++++ .../onboarding/fragments/RepoLocationFragment.kt | 191 +++++++++++++++++++ .../ui/onboarding/fragments/WelcomeFragment.kt | 25 +++ .../java/com/zeapo/pwdstore/utils/Extensions.kt | 3 +- .../com/zeapo/pwdstore/utils/FragmentExtensions.kt | 52 ++++++ app/src/main/res/animator/slide_in_left.xml | 11 ++ app/src/main/res/animator/slide_in_right.xml | 11 ++ app/src/main/res/animator/slide_out_left.xml | 11 ++ app/src/main/res/animator/slide_out_right.xml | 11 ++ app/src/main/res/layout/activity_onboarding.xml | 76 +------- app/src/main/res/layout/fragment_clone.xml | 87 +++++++++ app/src/main/res/layout/fragment_repo_location.xml | 87 +++++++++ app/src/main/res/layout/fragment_welcome.xml | 51 +++++ app/src/main/res/values/strings.xml | 20 +- 19 files changed, 654 insertions(+), 279 deletions(-) delete mode 100644 app/src/main/java/com/zeapo/pwdstore/OnboardingActivity.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/ui/onboarding/activity/OnboardingActivity.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/WelcomeFragment.kt create mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt create mode 100644 app/src/main/res/animator/slide_in_left.xml create mode 100644 app/src/main/res/animator/slide_in_right.xml create mode 100644 app/src/main/res/animator/slide_out_left.xml create mode 100644 app/src/main/res/animator/slide_out_right.xml create mode 100644 app/src/main/res/layout/fragment_clone.xml create mode 100644 app/src/main/res/layout/fragment_repo_location.xml create mode 100644 app/src/main/res/layout/fragment_welcome.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7672575f..2bd290d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ All notable changes to this project will be documented in this file. - 'Show hidden folders' is now 'Show hidden files and folders' - Generated SSH keys are now stored in the Android Keystore if available, and encrypted at rest otherwise - Allow using device's screen lock credentials to secure generated SSH key +- Update onboarding UI ### Fixed diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 034124f9..3b0781f4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,7 @@ android:label="@string/app_name" /> - if (result.resultCode == RESULT_OK) { - settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } - finish() - } - } - - private val repositoryInitAction = registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - initializeRepositoryInfo() - finish() - } - } - - private val externalDirectorySelectAction = registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - if (checkExternalDirectory()) { - finish() - } else { - createRepository() - } - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - supportActionBar?.hide() - setContentView(binding.root) - binding.settingsButton.setOnClickListener { - startActivity(Intent(this, UserPreference::class.java)) - } - binding.localDirectoryButton.setOnClickListener { - MaterialAlertDialogBuilder(this) - .setTitle(resources.getString(R.string.location_dialog_title)) - .setMessage(resources.getString(R.string.location_dialog_create_text)) - .setPositiveButton(resources.getString(R.string.location_hidden)) { _, _ -> - createRepoInHiddenDir() - } - .setNegativeButton(resources.getString(R.string.location_sdcard)) { _, _ -> - createRepoFromExternalDir() - } - .show() - } - binding.cloneFromServerButton.setOnClickListener { - cloneToHiddenDir() - } - } - - override fun onBackPressed() { - finishAffinity() - } - - /** - * 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(this)) - } - - /** - * 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() - finish() - } - - /** - * 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)) { - registerForActivityResult(RequestPermission()) { granted -> - if (granted) { - externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this)) - } - }.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(this)) - } - } else { - MaterialAlertDialogBuilder(this) - .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)) { - registerForActivityResult(RequestPermission()) { granted -> - if (granted) { - initializeRepositoryInfo() - } - }.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - initializeRepositoryInfo() - } - } - .setNegativeButton(resources.getString(R.string.change)) { _, _ -> - if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - registerForActivityResult(RequestPermission()) { granted -> - if (granted) { - repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(this)) - } - }.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(this)) - } - } - .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() - } - if (File(localDir.absolutePath + "/.gpg-id").createNewFile()) { - settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } - } else { - throw IllegalStateException("Failed to initialize repository state.") - } - }.onFailure { e -> - e.printStackTrace() - if (!localDir.delete()) { - d { "Failed to delete local repository: $localDir" } - } - } - } - - 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()) return - } - createRepository() - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt index 93dfad56..4e4bf85e 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt @@ -52,6 +52,7 @@ import com.zeapo.pwdstore.git.BaseGitActivity import com.zeapo.pwdstore.git.config.AuthMode import com.zeapo.pwdstore.git.config.GitSettings import com.zeapo.pwdstore.ui.dialogs.FolderCreationDialogFragment +import com.zeapo.pwdstore.ui.onboarding.activity.OnboardingActivity import com.zeapo.pwdstore.utils.PasswordItem import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepository diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/activity/OnboardingActivity.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/activity/OnboardingActivity.kt new file mode 100644 index 00000000..b81efb4c --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/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 com.zeapo.pwdstore.ui.onboarding.activity + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.zeapo.pwdstore.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/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt new file mode 100644 index 00000000..19a14695 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/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 com.zeapo.pwdstore.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 com.zeapo.pwdstore.R +import com.zeapo.pwdstore.databinding.FragmentCloneBinding +import com.zeapo.pwdstore.git.GitServerConfigActivity +import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.finish +import com.zeapo.pwdstore.utils.performTransactionWithBackStack +import com.zeapo.pwdstore.utils.sharedPrefs +import com.zeapo.pwdstore.utils.viewBinding + +class CloneFragment : Fragment(R.layout.fragment_clone) { + + private val binding by viewBinding(FragmentCloneBinding::bind) + + private val settings by lazy { 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/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt new file mode 100644 index 00000000..7ff85237 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt @@ -0,0 +1,191 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + * + */ + +package com.zeapo.pwdstore.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.michaelbull.result.onFailure +import com.github.michaelbull.result.runCatching +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.UserPreference +import com.zeapo.pwdstore.databinding.FragmentRepoLocationBinding +import com.zeapo.pwdstore.utils.PasswordRepository +import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.finish +import com.zeapo.pwdstore.utils.getString +import com.zeapo.pwdstore.utils.isPermissionGranted +import com.zeapo.pwdstore.utils.listFilesRecursively +import com.zeapo.pwdstore.utils.sharedPrefs +import com.zeapo.pwdstore.utils.viewBinding +import java.io.File + +class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { + + private val firstRunActivity by lazy { requireActivity() } + private val settings by lazy { firstRunActivity.applicationContext.sharedPrefs } + private val binding by viewBinding(FragmentRepoLocationBinding::bind) + private val sortOrder: PasswordRepository.PasswordSortOrder + get() = PasswordRepository.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() + } + if (File(localDir.absolutePath + "/.gpg-id").createNewFile()) { + settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } + } else { + throw IllegalStateException("Failed to initialize repository state.") + } + }.onFailure { e -> + e.printStackTrace() + 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()) 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/com/zeapo/pwdstore/ui/onboarding/fragments/WelcomeFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/WelcomeFragment.kt new file mode 100644 index 00000000..2cf4dd49 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/WelcomeFragment.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2019-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package com.zeapo.pwdstore.ui.onboarding.fragments + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.databinding.FragmentWelcomeBinding +import com.zeapo.pwdstore.utils.performTransactionWithBackStack +import com.zeapo.pwdstore.utils.viewBinding + +@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()) } + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt index d9277bef..2a11f56a 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt @@ -56,7 +56,8 @@ fun String.base64(): String { return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP) } -val Context.clipboard get() = getSystemService() +val Context.clipboard + get() = getSystemService() fun FragmentActivity.snackbar( view: View = findViewById(android.R.id.content), diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt new file mode 100644 index 00000000..01103d19 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt @@ -0,0 +1,52 @@ +package com.zeapo.pwdstore.utils + +import android.content.pm.PackageManager +import android.view.View +import androidx.annotation.IdRes +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.commit +import com.zeapo.pwdstore.R + +fun Fragment.isPermissionGranted(permission: String): Boolean { + return ContextCompat.checkSelfPermission(requireActivity(), permission) == PackageManager.PERMISSION_GRANTED +} + +fun Fragment.finish() = requireActivity().finish() + +fun FragmentManager.performTransaction(destinationFragment: Fragment, @IdRes containerViewId: Int = android.R.id.content) { + this.commit { + beginTransaction() + setCustomAnimations( + R.animator.slide_in_left, + R.animator.slide_out_left, + R.animator.slide_in_right, + R.animator.slide_out_right) + replace(containerViewId, destinationFragment) + } +} + +fun FragmentManager.performTransactionWithBackStack(destinationFragment: Fragment, @IdRes containerViewId: Int = android.R.id.content) { + this.commit { + beginTransaction() + addToBackStack(destinationFragment.tag) + setCustomAnimations( + R.animator.slide_in_left, + R.animator.slide_out_left, + R.animator.slide_in_right, + R.animator.slide_out_right) + replace(containerViewId, destinationFragment) + } +} + +fun FragmentManager.performSharedElementTransaction(destinationFragment: Fragment, views: List, @IdRes containerViewId: Int = android.R.id.content) { + this.commit { + beginTransaction() + for (view in views) { + addSharedElement(view, view.transitionName) + } + addToBackStack(destinationFragment.tag) + replace(containerViewId, destinationFragment) + } +} diff --git a/app/src/main/res/animator/slide_in_left.xml b/app/src/main/res/animator/slide_in_left.xml new file mode 100644 index 00000000..5f5498e5 --- /dev/null +++ b/app/src/main/res/animator/slide_in_left.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/animator/slide_in_right.xml b/app/src/main/res/animator/slide_in_right.xml new file mode 100644 index 00000000..34420581 --- /dev/null +++ b/app/src/main/res/animator/slide_in_right.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/animator/slide_out_left.xml b/app/src/main/res/animator/slide_out_left.xml new file mode 100644 index 00000000..5bc22aa9 --- /dev/null +++ b/app/src/main/res/animator/slide_out_left.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/animator/slide_out_right.xml b/app/src/main/res/animator/slide_out_right.xml new file mode 100644 index 00000000..8447ae76 --- /dev/null +++ b/app/src/main/res/animator/slide_out_right.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml index f2c6f797..6d880c31 100644 --- a/app/src/main/res/layout/activity_onboarding.xml +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -1,73 +1,13 @@ - - - - - - - - - - - - - - + + diff --git a/app/src/main/res/layout/fragment_clone.xml b/app/src/main/res/layout/fragment_clone.xml new file mode 100644 index 00000000..0e173428 --- /dev/null +++ b/app/src/main/res/layout/fragment_clone.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_repo_location.xml b/app/src/main/res/layout/fragment_repo_location.xml new file mode 100644 index 00000000..cc987d8c --- /dev/null +++ b/app/src/main/res/layout/fragment_repo_location.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_welcome.xml b/app/src/main/res/layout/fragment_welcome.xml new file mode 100644 index 00000000..98a0475d --- /dev/null +++ b/app/src/main/res/layout/fragment_welcome.xml @@ -0,0 +1,51 @@ + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 53140e6c..90295413 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,7 +77,7 @@ Clone from server Use local directory Repository location - Select where to create your password repository + Select where do you want to create your password repository SD-Card Hidden (Preferred) Choose where to store the passwords @@ -418,4 +418,22 @@ A folder by that name already exists Digits/Symbols (d/s) ds + + + Repository \nLocation + Select \nOpenPGP Provider + Let\'s Go + Select \nRepository Type + Select if you want to create a local repo or clone a remote repo. + Clone Remote Repo + Create Local Repo + Error getting directory uri + Select a directory to store passwords + Select \nStore Name + Select a name for your password store. + Store Name + Select an empty directory for password store + Enter a store name to continue + Cannot create new directory. + -- cgit v1.2.3