Update on-boarding UI (#1099)

* Add onboarding flow from v2

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Minor fixes

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Add changelog entry

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Remove old activity from manifest

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Remove view type prefix from view ids

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Review fixes

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Treewide: Reformat code

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Moar review fixes

Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>

* Revert "Treewide: Reformat code"

This reverts commit 348ef0050942526a55890b245afec8d7fee4d81e.

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* onboarding: cleanup OnboardingActivity init

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Remove unused layout

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Remove unnecessary ConstraintLayout

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* Shorten animation duration

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

* onboarding: use viewBinding extension in fragments

Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>

Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Aditya Wasan 2020-09-15 21:53:12 +05:30 committed by GitHub
parent a34f749e9a
commit 4ba3b75f85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 517 additions and 142 deletions

View file

@ -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' - '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 - 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 - Allow using device's screen lock credentials to secure generated SSH key
- Update onboarding UI
### Fixed ### Fixed

View file

@ -33,7 +33,7 @@
android:label="@string/app_name" /> android:label="@string/app_name" />
<activity <activity
android:name=".OnboardingActivity" android:name=".ui.onboarding.activity.OnboardingActivity"
android:configChanges="orientation|screenSize" /> android:configChanges="orientation|screenSize" />
<activity <activity

View file

@ -52,6 +52,7 @@ import com.zeapo.pwdstore.git.BaseGitActivity
import com.zeapo.pwdstore.git.config.AuthMode import com.zeapo.pwdstore.git.config.AuthMode
import com.zeapo.pwdstore.git.config.GitSettings import com.zeapo.pwdstore.git.config.GitSettings
import com.zeapo.pwdstore.ui.dialogs.FolderCreationDialogFragment 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.PasswordItem
import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepository import com.zeapo.pwdstore.utils.PasswordRepository.Companion.getRepository

View file

@ -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()
}
}
}

View file

@ -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()
}
}

View file

@ -1,55 +1,51 @@
/* /*
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only * SPDX-License-Identifier: GPL-3.0-only
*
*/ */
package com.zeapo.pwdstore package com.zeapo.pwdstore.ui.onboarding.fragments
import android.Manifest import android.Manifest
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission import android.view.View
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
import androidx.fragment.app.Fragment
import com.github.ajalt.timberkt.d import com.github.ajalt.timberkt.d
import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.databinding.ActivityOnboardingBinding import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.git.GitServerConfigActivity import com.zeapo.pwdstore.UserPreference
import com.zeapo.pwdstore.databinding.FragmentRepoLocationBinding
import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PreferenceKeys import com.zeapo.pwdstore.utils.PreferenceKeys
import com.zeapo.pwdstore.utils.isPermissionGranted import com.zeapo.pwdstore.utils.finish
import com.zeapo.pwdstore.utils.getString import com.zeapo.pwdstore.utils.getString
import com.zeapo.pwdstore.utils.isPermissionGranted
import com.zeapo.pwdstore.utils.listFilesRecursively import com.zeapo.pwdstore.utils.listFilesRecursively
import com.zeapo.pwdstore.utils.sharedPrefs import com.zeapo.pwdstore.utils.sharedPrefs
import com.zeapo.pwdstore.utils.viewBinding import com.zeapo.pwdstore.utils.viewBinding
import java.io.File import java.io.File
class OnboardingActivity : AppCompatActivity() { class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) {
private val binding by viewBinding(ActivityOnboardingBinding::inflate) private val firstRunActivity by lazy { requireActivity() }
private val settings by lazy { applicationContext.sharedPrefs } private val settings by lazy { firstRunActivity.applicationContext.sharedPrefs }
private val binding by viewBinding(FragmentRepoLocationBinding::bind)
private val sortOrder: PasswordRepository.PasswordSortOrder private val sortOrder: PasswordRepository.PasswordSortOrder
get() = PasswordRepository.PasswordSortOrder.getSortOrder(settings) get() = PasswordRepository.PasswordSortOrder.getSortOrder(settings)
private val cloneAction = registerForActivityResult(StartActivityForResult()) { result -> private val repositoryInitAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) { if (result.resultCode == AppCompatActivity.RESULT_OK) {
settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) }
finish()
}
}
private val repositoryInitAction = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
initializeRepositoryInfo() initializeRepositoryInfo()
finish()
} }
} }
private val externalDirectorySelectAction = registerForActivityResult(StartActivityForResult()) { result -> private val externalDirectorySelectAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) { if (result.resultCode == AppCompatActivity.RESULT_OK) {
if (checkExternalDirectory()) { if (checkExternalDirectory()) {
finish() finish()
} else { } else {
@ -58,40 +54,27 @@ class OnboardingActivity : AppCompatActivity() {
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { private val externalDirPermGrantedAction = createPermGrantedAction {
super.onCreate(savedInstanceState) externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
supportActionBar?.hide()
setContentView(binding.root)
binding.settingsButton.setOnClickListener {
startActivity(Intent(this, UserPreference::class.java))
} }
binding.localDirectoryButton.setOnClickListener {
MaterialAlertDialogBuilder(this) private val repositoryUsePermGrantedAction = createPermGrantedAction {
.setTitle(resources.getString(R.string.location_dialog_title)) initializeRepositoryInfo()
.setMessage(resources.getString(R.string.location_dialog_create_text)) }
.setPositiveButton(resources.getString(R.string.location_hidden)) { _, _ ->
private val repositoryChangePermGrantedAction = createPermGrantedAction {
repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.hidden.setOnClickListener {
createRepoInHiddenDir() createRepoInHiddenDir()
} }
.setNegativeButton(resources.getString(R.string.location_sdcard)) { _, _ ->
binding.sdcard.setOnClickListener {
createRepoFromExternalDir() 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))
} }
/** /**
@ -103,7 +86,6 @@ class OnboardingActivity : AppCompatActivity() {
remove(PreferenceKeys.GIT_EXTERNAL_REPO) remove(PreferenceKeys.GIT_EXTERNAL_REPO)
} }
initializeRepositoryInfo() initializeRepositoryInfo()
finish()
} }
/** /**
@ -114,40 +96,28 @@ class OnboardingActivity : AppCompatActivity() {
val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO)
if (externalRepo == null) { if (externalRepo == null) {
if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
registerForActivityResult(RequestPermission()) { granted -> externalDirPermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (granted) {
externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this))
}
}.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else { } else {
// Unlikely we have storage permissions without user ever selecting a directory, // Unlikely we have storage permissions without user ever selecting a directory,
// but let's not assume. // but let's not assume.
externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this)) externalDirectorySelectAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
} }
} else { } else {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(requireActivity())
.setTitle(resources.getString(R.string.directory_selected_title)) .setTitle(resources.getString(R.string.directory_selected_title))
.setMessage(resources.getString(R.string.directory_selected_message, externalRepo)) .setMessage(resources.getString(R.string.directory_selected_message, externalRepo))
.setPositiveButton(resources.getString(R.string.use)) { _, _ -> .setPositiveButton(resources.getString(R.string.use)) { _, _ ->
if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
registerForActivityResult(RequestPermission()) { granted -> repositoryUsePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (granted) {
initializeRepositoryInfo()
}
}.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else { } else {
initializeRepositoryInfo() initializeRepositoryInfo()
} }
} }
.setNegativeButton(resources.getString(R.string.change)) { _, _ -> .setNegativeButton(resources.getString(R.string.change)) { _, _ ->
if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { if (!isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
registerForActivityResult(RequestPermission()) { granted -> repositoryChangePermGrantedAction.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (granted) {
repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(this))
}
}.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else { } else {
repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(this)) repositoryInitAction.launch(UserPreference.createDirectorySelectionIntent(requireContext()))
} }
} }
.show() .show()
@ -192,6 +162,7 @@ class OnboardingActivity : AppCompatActivity() {
d { "Failed to delete local repository: $localDir" } d { "Failed to delete local repository: $localDir" }
} }
} }
finish()
} }
private fun initializeRepositoryInfo() { private fun initializeRepositoryInfo() {
@ -205,4 +176,16 @@ class OnboardingActivity : AppCompatActivity() {
} }
createRepository() createRepository()
} }
private fun createPermGrantedAction(block: () -> Unit) =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) {
block.invoke()
}
}
companion object {
fun newInstance(): RepoLocationFragment = RepoLocationFragment()
}
} }

View file

@ -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()) }
}
}

View file

@ -56,7 +56,8 @@ fun String.base64(): String {
return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP) return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP)
} }
val Context.clipboard get() = getSystemService<ClipboardManager>() val Context.clipboard
get() = getSystemService<ClipboardManager>()
fun FragmentActivity.snackbar( fun FragmentActivity.snackbar(
view: View = findViewById(android.R.id.content), view: View = findViewById(android.R.id.content),

View file

@ -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<View>, @IdRes containerViewId: Int = android.R.id.content) {
this.commit {
beginTransaction()
for (view in views) {
addSharedElement(view, view.transitionName)
}
addToBackStack(destinationFragment.tag)
replace(containerViewId, destinationFragment)
}
}

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="250"
android:propertyName="x"
android:valueFrom="1000"
android:valueTo="0"
android:valueType="floatType" />
</set>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="250"
android:propertyName="x"
android:valueFrom="-1000"
android:valueTo="0"
android:valueType="floatType" />
</set>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="250"
android:propertyName="x"
android:valueFrom="0"
android:valueTo="-1000"
android:valueType="floatType" />
</set>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="250"
android:propertyName="x"
android:valueFrom="0"
android:valueTo="1000"
android:valueType="floatType" />
</set>

View file

@ -1,73 +1,13 @@
<!-- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
~ SPDX-License-Identifier: GPL-3.0-only
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/colorPrimary" android:background="?attr/colorPrimary"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView <androidx.fragment.app.FragmentContainerView
android:id="@+id/app_icon" android:id="@+id/fragment_first_run"
android:layout_width="wrap_content" android:name="com.zeapo.pwdstore.ui.onboarding.fragments.WelcomeFragment"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_gravity="center" android:layout_height="match_parent"
android:contentDescription="@string/app_icon_hint" android:tag="welcome_fragment" />
android:src="@mipmap/ic_launcher" </LinearLayout>
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/app_icon"
android:layout_centerHorizontal="true"
android:text="@string/app_name"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?attr/colorOnPrimary"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/app_icon" />
<com.google.android.material.button.MaterialButton
android:id="@+id/settings_button"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:text="@string/action_settings"
android:textColor="?attr/colorOnPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/local_directory_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="@string/initialize"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/app_name" />
<com.google.android.material.button.MaterialButton
android:id="@+id/clone_from_server_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clone"
android:textSize="12sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/local_directory_button" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,87 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/app_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="32dp"
android:layout_marginTop="100dp"
android:contentDescription="@string/app_icon_hint"
android:src="@mipmap/ic_launcher"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginStart="16dp"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="@color/color_control_normal"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/app_icon"
app:layout_constraintStart_toEndOf="@id/app_icon"
app:layout_constraintTop_toTopOf="@+id/app_icon" />
<TextView
android:id="@+id/repo_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="@string/select_n_repository_type"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
android:textColor="@color/color_control_normal"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@id/app_icon"
app:layout_constraintTop_toBottomOf="@id/app_icon" />
<TextView
android:id="@+id/tv_repo_type_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:layout_marginEnd="16dp"
android:text="@string/select_repo_type_text"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/color_control_normal"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/repo_type"
app:layout_constraintTop_toBottomOf="@id/repo_type" />
<com.google.android.material.button.MaterialButton
android:id="@+id/clone_remote"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:maxWidth="300dp"
android:minWidth="100dp"
android:text="@string/clone_remote_repo"
app:layout_constraintBottom_toTopOf="@id/create_local"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/create_local"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="128dp"
android:maxWidth="300dp"
android:minWidth="100dp"
android:text="@string/create_local_repo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/clone_remote"
app:layout_constraintStart_toStartOf="@id/clone_remote" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,87 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/app_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginStart="32dp"
android:layout_marginTop="100dp"
android:contentDescription="@string/app_icon_hint"
android:src="@mipmap/ic_launcher"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginStart="16dp"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="@color/color_control_normal"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/app_icon"
app:layout_constraintStart_toEndOf="@id/app_icon"
app:layout_constraintTop_toTopOf="@+id/app_icon" />
<TextView
android:id="@+id/repo_location"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:text="@string/repository_n_location"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4"
android:textColor="@color/color_control_normal"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="@id/app_icon"
app:layout_constraintTop_toBottomOf="@id/app_icon" />
<TextView
android:id="@+id/repo_location_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="48dp"
android:layout_marginEnd="16dp"
android:text="@string/location_dialog_create_text"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/color_control_normal"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/repo_location"
app:layout_constraintTop_toBottomOf="@id/repo_location" />
<com.google.android.material.button.MaterialButton
android:id="@+id/hidden"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:maxWidth="300dp"
android:minWidth="100dp"
android:text="@string/location_hidden"
app:layout_constraintBottom_toTopOf="@id/sdcard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sdcard"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="128dp"
android:maxWidth="300dp"
android:minWidth="100dp"
android:text="@string/location_sdcard"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/hidden"
app:layout_constraintStart_toStartOf="@id/hidden" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,51 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimary"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/app_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:contentDescription="@string/app_icon_hint"
android:src="@mipmap/ic_launcher"
android:transitionName="transition_first_app_icon"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5"
android:textColor="@color/color_control_normal"
android:textStyle="bold"
android:transitionName="transition_first_run_app_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/app_icon" />
<com.google.android.material.button.MaterialButton
android:id="@+id/lets_go"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="48dp"
android:layout_marginEnd="16dp"
android:maxWidth="300dp"
android:minWidth="100dp"
android:text="@string/let_s_go"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/app_name" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -77,7 +77,7 @@
<string name="clone">Clone from server</string> <string name="clone">Clone from server</string>
<string name="initialize">Use local directory</string> <string name="initialize">Use local directory</string>
<string name="location_dialog_title">Repository location</string> <string name="location_dialog_title">Repository location</string>
<string name="location_dialog_create_text">Select where to create your password repository</string> <string name="location_dialog_create_text">Select where do you want to create your password repository</string>
<string name="location_sdcard">SD-Card</string> <string name="location_sdcard">SD-Card</string>
<string name="location_hidden">Hidden (Preferred)</string> <string name="location_hidden">Hidden (Preferred)</string>
<string name="external_repository_dialog_title">Choose where to store the passwords</string> <string name="external_repository_dialog_title">Choose where to store the passwords</string>
@ -418,4 +418,22 @@
<string name="folder_creation_err_folder_exists">A folder by that name already exists</string> <string name="folder_creation_err_folder_exists">A folder by that name already exists</string>
<string name="xkpwgen_extrachars_label">Digits/Symbols (d/s)</string> <string name="xkpwgen_extrachars_label">Digits/Symbols (d/s)</string>
<string name="xk_numbers_symbols_append_default">ds</string> <string name="xk_numbers_symbols_append_default">ds</string>
<!-- Onboarding flow -->
<string name="repository_n_location">Repository \nLocation</string>
<string name="select_n_openpgp_provider">Select \nOpenPGP Provider</string>
<string name="let_s_go">Let\'s Go</string>
<string name="select_n_repository_type">Select \nRepository Type</string>
<string name="select_repo_type_text">Select if you want to create a local repo or clone a remote repo.</string>
<string name="clone_remote_repo">Clone Remote Repo</string>
<string name="create_local_repo">Create Local Repo</string>
<string name="error_directory_uri">Error getting directory uri</string>
<string name="select_directory_passwords">Select a directory to store passwords</string>
<string name="select_n_store_name">Select \nStore Name</string>
<string name="select_n_store_text">Select a name for your password store.</string>
<string name="store_name">Store Name</string>
<string name="select_empty_directory">Select an empty directory for password store</string>
<string name="err_enter_store_name">Enter a store name to continue</string>
<string name="exception_cannot_create_directory">Cannot create new directory.</string>
</resources> </resources>