diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2020-04-19 13:16:25 +0530 |
---|---|---|
committer | Harsh Shandilya <me@msfjarvis.dev> | 2020-04-19 14:11:19 +0530 |
commit | 17385892cbe4e0359c45a6221036dd5d6c9cb283 (patch) | |
tree | c0d1b8d3f8f7a7104bd897be08b6ae8a2ffbe54f | |
parent | eb5a30c3a9f1474e452d28032c238cc3738eb3d8 (diff) |
PasswordFragment: Replace fab options with descriptive bottom sheet
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
8 files changed, 141 insertions, 191 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt index 5e12697d..2b1551fa 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.kt @@ -22,13 +22,13 @@ import androidx.lifecycle.observe import androidx.recyclerview.widget.FixOnItemTouchDispatchRecyclerView import androidx.recyclerview.widget.LinearLayoutManager import androidx.swiperefreshlayout.widget.SwipeRefreshLayout -import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.snackbar.Snackbar import com.zeapo.pwdstore.databinding.PasswordRecyclerViewBinding import com.zeapo.pwdstore.git.BaseGitActivity import com.zeapo.pwdstore.git.GitOperationActivity import com.zeapo.pwdstore.ui.OnOffItemAnimator import com.zeapo.pwdstore.ui.adapters.PasswordItemRecyclerAdapter +import com.zeapo.pwdstore.ui.dialogs.ItemCreationBottomSheet import com.zeapo.pwdstore.utils.PasswordItem import com.zeapo.pwdstore.utils.PasswordRepository import java.io.File @@ -57,17 +57,10 @@ class PasswordFragment : Fragment() { ): View? { _binding = PasswordRecyclerViewBinding.inflate(inflater, container, false) initializePasswordList() - val fab = binding.fab - fab.setOnClickListener { - toggleFabExpand(fab) - } - binding.createFolder.setOnClickListener { - requireStore().createFolder() - toggleFabExpand(fab) - } - binding.createPassword.setOnClickListener { - requireStore().createPassword() - toggleFabExpand(fab) + binding.fab.setOnClickListener { + ItemCreationBottomSheet().apply { + setTargetFragment(this@PasswordFragment, 1000) + }.show(parentFragmentManager, "BOTTOM_SHEET") } return binding.root } @@ -145,14 +138,7 @@ class PasswordFragment : Fragment() { super.onDestroyView() } - private fun toggleFabExpand(fab: FloatingActionButton) = with(fab) { - isExpanded = !isExpanded - isActivated = isExpanded - animate().rotationBy(if (isExpanded) -45f else 45f).setDuration(100).start() - } - private val actionModeCallback = object : ActionMode.Callback { - // Called when the action mode is created; startActionMode() was called override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { // Inflate a menu resource providing context menu items @@ -282,6 +268,10 @@ class PasswordFragment : Fragment() { actionMode?.finish() } + fun createFolder() = requireStore().createFolder() + + fun createPassword() = requireStore().createPassword() + interface OnFragmentInteractionListener { fun onFragmentInteraction(item: PasswordItem) } diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/ItemCreationBottomSheet.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/ItemCreationBottomSheet.kt new file mode 100644 index 00000000..55a767d8 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/ItemCreationBottomSheet.kt @@ -0,0 +1,76 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.ui.dialogs + +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.zeapo.pwdstore.PasswordFragment +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.utils.resolveAttribute + +class ItemCreationBottomSheet : BottomSheetDialogFragment() { + private var behavior: BottomSheetBehavior<FrameLayout>? = null + private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() { + override fun onSlide(bottomSheet: View, slideOffset: Float) { + } + + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_COLLAPSED) { + dismiss() + } + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + if (savedInstanceState != null) dismiss() + return inflater.inflate(R.layout.item_create_sheet, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + view.viewTreeObserver.removeOnGlobalLayoutListener(this) + val dialog = dialog as BottomSheetDialog? ?: return + behavior = dialog.behavior + behavior?.apply { + state = BottomSheetBehavior.STATE_EXPANDED + peekHeight = 0 + addBottomSheetCallback(bottomSheetCallback) + } + dialog.findViewById<View>(R.id.create_folder)?.setOnClickListener { + (requireTargetFragment() as PasswordFragment).createFolder() + dismiss() + } + dialog.findViewById<View>(R.id.create_password)?.setOnClickListener { + (requireTargetFragment() as PasswordFragment).createPassword() + dismiss() + } + } + }) + val gradientDrawable = GradientDrawable().apply { + setColor(requireContext().resolveAttribute(android.R.attr.windowBackground)) + } + view.background = gradientDrawable + } + + override fun dismiss() { + super.dismiss() + behavior?.removeBottomSheetCallback(bottomSheetCallback) + } + + private fun requireTargetFragment(): Fragment = requireNotNull(targetFragment) { + "A target fragment must be set for $this" + } +} 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 deleted file mode 100644 index 6cefda14..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/widget/fab/EmitExpandableTransformationBehaviour.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright © 2014-2020 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) - } -} diff --git a/app/src/main/res/layout/item_create_sheet.xml b/app/src/main/res/layout/item_create_sheet.xml new file mode 100644 index 00000000..244537ad --- /dev/null +++ b/app/src/main/res/layout/item_create_sheet.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<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_height="match_parent"> + + <com.google.android.material.button.MaterialButton + style="@style/Widget.MaterialComponents.Button.TextButton" + android:id="@+id/create_folder" + android:layout_width="match_parent" + android:layout_height="@dimen/bottom_sheet_item_height" + android:text="@string/bottom_sheet_create_new_folder" + android:textAlignment="viewStart" + android:textColor="?attr/colorOnSurface" + android:layout_margin="@dimen/normal_margin" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:icon="@drawable/ic_action_new_folder" + app:iconPadding="@dimen/normal_margin" + app:iconTint="?attr/colorSecondary" + app:rippleColor="?attr/colorSecondary" /> + + <com.google.android.material.button.MaterialButton + style="@style/Widget.MaterialComponents.Button.TextButton" + android:id="@+id/create_password" + android:layout_width="match_parent" + android:layout_height="@dimen/bottom_sheet_item_height" + android:text="@string/bottom_sheet_create_new_password" + android:textAlignment="viewStart" + android:textColor="?attr/colorOnSurface" + android:layout_margin="@dimen/normal_margin" + app:layout_constraintTop_toBottomOf="@id/create_folder" + app:icon="@drawable/ic_action_new_password" + app:iconPadding="@dimen/normal_margin" + app:iconTint="?attr/colorSecondary" + app:rippleColor="?attr/colorSecondary" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/password_recycler_view.xml b/app/src/main/res/layout/password_recycler_view.xml index 8348bea2..ca2b6550 100644 --- a/app/src/main/res/layout/password_recycler_view.xml +++ b/app/src/main/res/layout/password_recycler_view.xml @@ -24,37 +24,6 @@ tools:listitem="@layout/password_row_layout" /> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="top|center_horizontal" - android:layout_marginBottom="16dp" - android:orientation="vertical" - android:visibility="invisible" - android:clipChildren="false" - android:clipToPadding="false" - app:layout_anchor="@id/fab" - app:layout_anchorGravity="top|center_horizontal" - app:layout_behavior="com.zeapo.pwdstore.widget.fab.EmitExpandableTransformationBehavior" - tools:visibility="visible"> - - <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/create_password" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/fab_margin" - app:fabSize="mini" - app:srcCompat="@drawable/ic_action_new_password" /> - - <com.google.android.material.floatingactionbutton.FloatingActionButton - android:id="@+id/create_folder" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/fab_margin" - app:fabSize="mini" - app:srcCompat="@drawable/ic_action_new_folder" /> - </LinearLayout> - <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:src="@drawable/ic_add_white_48dp" diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 15c6287c..0e24bea2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -4,4 +4,6 @@ <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="fab_compat_margin">16dp</dimen> <dimen name="fab_margin">8dp</dimen> + <dimen name="normal_margin">8dp</dimen> + <dimen name="bottom_sheet_item_height">56dp</dimen> </resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index acedf8e3..87357f86 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -360,4 +360,6 @@ <string name="git_operation_unable_to_open_ssh_key_title">Unable to open the ssh-key</string> <string name="git_operation_unable_to_open_ssh_key_message">Please check that it was imported.</string> <string name="git_operation_wrong_passphrase">Wrong passphrase</string> + <string name="bottom_sheet_create_new_folder">Create new folder</string> + <string name="bottom_sheet_create_new_password">Create new password</string> </resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b5a03bb4..7058dcff 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -21,6 +21,7 @@ <item name="materialAlertDialogTheme">@style/AppTheme.Dialog</item> <item name="actionBarPopupTheme">@style/ThemeOverlay.MaterialComponents.ActionBar</item> <item name="textInputStyle">@style/TextInputLayoutBase</item> + <item name="bottomSheetDialogTheme">@style/BottomSheetDialogTheme</item> </style> <style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert"> @@ -33,6 +34,18 @@ <item name="background">@color/primary_color</item> </style> + <style name="BottomSheetDialogTheme" parent="ThemeOverlay.MaterialComponents.BottomSheetDialog"> + <item name="android:windowIsFloating">false</item> + <item name="android:navigationBarColor">@android:color/transparent</item> + <item name="android:statusBarColor">@android:color/transparent</item> + <item name="android:windowTranslucentNavigation">false</item> + <item name="android:windowIsTranslucent">false</item> + <item name="android:backgroundDimEnabled">true</item> + <item name="android:backgroundDimAmount">0.5</item> + <item name="android:windowTranslucentStatus">false</item> + <item name="android:colorBackground">@android:color/transparent</item> + </style> + <style name="NoBackgroundTheme" parent="@style/AppTheme"> <item name="android:background">@android:color/transparent</item> <item name="android:backgroundDimEnabled">true</item> |