summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt22
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt17
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt45
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/widget/fab/EmitExpandableTransformationBehaviour.kt141
4 files changed, 220 insertions, 5 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
index 61437731..22bad566 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt
@@ -70,12 +70,30 @@ class PasswordFragment : Fragment() {
recyclerView.adapter = recyclerAdapter
// Setup fast scroller
FastScrollerBuilder(recyclerView).build()
- val fab: FloatingActionButton = view.findViewById(R.id.fab)
- fab.setOnClickListener { (requireActivity() as PasswordStore).createPassword() }
+ val fab = view.findViewById<FloatingActionButton>(R.id.fab)
+ fab.setOnClickListener {
+ toggleFabExpand(fab)
+ }
+
+ view.findViewById<FloatingActionButton>(R.id.create_folder).setOnClickListener {
+ (requireActivity() as PasswordStore).createFolder()
+ toggleFabExpand(fab)
+ }
+
+ view.findViewById<FloatingActionButton>(R.id.create_password).setOnClickListener {
+ (requireActivity() as PasswordStore).createPassword()
+ toggleFabExpand(fab)
+ }
registerForContextMenu(recyclerView)
return view
}
+ private fun toggleFabExpand(fab: FloatingActionButton) = with(fab) {
+ isExpanded = !isExpanded
+ isActivated = isExpanded
+ animate().rotationBy(if (isExpanded) -45f else 45f).setDuration(100).start()
+ }
+
override fun onAttach(context: Context) {
super.onAttach(context)
try {
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
index f0302d83..087eb42d 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt
@@ -38,6 +38,7 @@ import com.zeapo.pwdstore.git.GitActivity
import com.zeapo.pwdstore.git.GitAsyncTask
import com.zeapo.pwdstore.git.GitOperation
import com.zeapo.pwdstore.ui.adapters.PasswordRecyclerAdapter
+import com.zeapo.pwdstore.ui.dialogs.FolderCreationDialogFragment
import com.zeapo.pwdstore.utils.PasswordItem
import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.PasswordRepository.Companion.closeRepository
@@ -435,13 +436,13 @@ class PasswordStore : AppCompatActivity() {
startActivityForResult(intent, REQUEST_CODE_EDIT)
}
- fun createPassword() {
+ private fun validateState(): Boolean {
if (!isInitialized) {
MaterialAlertDialogBuilder(this)
.setMessage(this.resources.getString(R.string.creation_dialog_text))
.setPositiveButton(this.resources.getString(R.string.dialog_ok), null)
.show()
- return
+ return false
}
if (settings.getStringSet("openpgp_key_ids_set", HashSet()).isNullOrEmpty()) {
MaterialAlertDialogBuilder(this)
@@ -452,8 +453,13 @@ class PasswordStore : AppCompatActivity() {
startActivity(intent)
}
.show()
- return
+ return false
}
+ return true
+ }
+
+ fun createPassword() {
+ if (!validateState()) return
val currentDir = currentDir
Timber.tag(TAG).i("Adding file to : ${currentDir!!.absolutePath}")
val intent = Intent(this, PgpActivity::class.java)
@@ -463,6 +469,11 @@ class PasswordStore : AppCompatActivity() {
startActivityForResult(intent, REQUEST_CODE_ENCRYPT)
}
+ fun createFolder() {
+ if (!validateState()) return
+ FolderCreationDialogFragment.newInstance(currentDir!!.path).show(supportFragmentManager, null)
+ }
+
// deletes passwords in order from top to bottom
fun deletePasswords(adapter: PasswordRecyclerAdapter, selectedItems: MutableSet<Int>) {
val it: MutableIterator<*> = selectedItems.iterator()
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt
new file mode 100644
index 00000000..8584aeaf
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright © 2014-2019 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.ui.dialogs
+
+import android.app.Dialog
+import android.content.DialogInterface
+import android.os.Bundle
+import androidx.core.os.bundleOf
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.textfield.TextInputEditText
+import com.zeapo.pwdstore.PasswordStore
+import com.zeapo.pwdstore.R
+import java.io.File
+
+class FolderCreationDialogFragment : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
+ alertDialogBuilder.setView(R.layout.folder_creation_dialog_fragment)
+ alertDialogBuilder.setPositiveButton(getString(R.string.button_create)) { _: DialogInterface, _: Int ->
+ createDirectory(requireArguments().getString(CURRENT_DIR_EXTRA)!!)
+ }
+ return alertDialogBuilder.create()
+ }
+
+ private fun createDirectory(currentDir: String) {
+ val dialog = requireDialog()
+ val materialTextView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text)
+ val folderName = materialTextView.text.toString()
+ File("$currentDir/$folderName").mkdir()
+ (requireActivity() as PasswordStore).updateListAdapter()
+ }
+
+ companion object {
+ private const val CURRENT_DIR_EXTRA = "CURRENT_DIRECTORY"
+ fun newInstance(startingDirectory: String): FolderCreationDialogFragment {
+ val extras = bundleOf(CURRENT_DIR_EXTRA to startingDirectory)
+ val fragment = FolderCreationDialogFragment()
+ fragment.arguments = extras
+ return fragment
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/widget/fab/EmitExpandableTransformationBehaviour.kt b/app/src/main/java/com/zeapo/pwdstore/widget/fab/EmitExpandableTransformationBehaviour.kt
new file mode 100644
index 00000000..0b05fb8f
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/widget/fab/EmitExpandableTransformationBehaviour.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright © 2014-2019 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.widget.fab
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.PropertyValuesHolder
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import androidx.coordinatorlayout.widget.CoordinatorLayout
+import androidx.core.animation.addListener
+import androidx.core.view.children
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import com.google.android.material.transformation.ExpandableTransformationBehavior
+import java.util.ArrayList
+
+/**
+ * Taken from Mao Yufeng's excellent example at https://git.io/Jvml9, all credits to him for this.
+ * It's hard to create per-file copyright rules for Spotless so I'm choosing to credit him here.
+ */
+class EmitExpandableTransformationBehavior @JvmOverloads constructor(
+ context: Context? = null,
+ attrs: AttributeSet? = null
+) : ExpandableTransformationBehavior(context, attrs) {
+
+ companion object {
+ private const val EXPAND_DELAY = 60L
+ private const val EXPAND_DURATION = 150L
+ private const val COLLAPSE_DELAY = 60L
+ private const val COLLAPSE_DURATION = 150L
+ }
+
+ override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
+ return dependency is FloatingActionButton && child is ViewGroup
+ }
+
+ override fun onCreateExpandedStateChangeAnimation(
+ dependency: View,
+ child: View,
+ expanded: Boolean,
+ isAnimating: Boolean
+ ): AnimatorSet {
+
+ if (child !is ViewGroup) {
+ return AnimatorSet()
+ }
+
+ val animations = ArrayList<Animator>()
+
+ if (expanded) {
+ createExpandAnimation(child, isAnimating, animations)
+ } else {
+ createCollapseAnimation(child, animations)
+ }
+
+ val set = AnimatorSet()
+ set.playTogether(animations)
+ set.addListener(
+ onStart = {
+ if (expanded) {
+ child.isVisible = true
+ }
+ },
+ onEnd = {
+ if (!expanded) {
+ child.isInvisible = true
+ }
+ }
+ )
+ return set
+ }
+
+ private fun createExpandAnimation(
+ child: ViewGroup,
+ currentlyAnimating: Boolean,
+ animations: MutableList<Animator>
+ ) {
+ if (!currentlyAnimating) {
+ child.children.forEach {
+ it.alpha = 0f
+ it.scaleX = 0.4f
+ it.scaleY = 0.4f
+ }
+ }
+ val delays = List(child.childCount) {
+ it * EXPAND_DELAY
+ }.reversed().asSequence()
+ val scaleXHolder = PropertyValuesHolder.ofFloat(View.SCALE_X, 1f)
+ val scaleYHolder = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1f)
+ val alphaHolder = PropertyValuesHolder.ofFloat(View.ALPHA, 1f)
+ val animators = child.children.zip(delays) { view, delay ->
+ ObjectAnimator.ofPropertyValuesHolder(
+ view,
+ scaleXHolder,
+ scaleYHolder,
+ alphaHolder
+ ).apply {
+ duration = EXPAND_DURATION
+ startDelay = delay
+ }
+ }.toList()
+ val animatorSet = AnimatorSet().apply {
+ playTogether(animators)
+ }
+ animations.add(animatorSet)
+ }
+
+ private fun createCollapseAnimation(
+ child: ViewGroup,
+ animations: MutableList<Animator>
+ ) {
+ val delays = List(child.childCount) {
+ it * COLLAPSE_DELAY
+ }.asSequence()
+ val scaleXHolder = PropertyValuesHolder.ofFloat(View.SCALE_X, 0.4f)
+ val scaleYHolder = PropertyValuesHolder.ofFloat(View.SCALE_Y, 0.4f)
+ val alphaHolder = PropertyValuesHolder.ofFloat(View.ALPHA, 0f)
+ val animators = child.children.zip(delays) { view, delay ->
+ ObjectAnimator.ofPropertyValuesHolder(
+ view,
+ scaleXHolder,
+ scaleYHolder,
+ alphaHolder
+ ).apply {
+ duration = COLLAPSE_DURATION
+ startDelay = delay
+ }
+ }.toList()
+ val animatorSet = AnimatorSet().apply {
+ playTogether(animators)
+ }
+ animations.add(animatorSet)
+ }
+}