diff options
Diffstat (limited to 'app/src/main/java')
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) + } +} |