diff options
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/ui')
6 files changed, 160 insertions, 292 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/OnOffItemAnimator.kt b/app/src/main/java/com/zeapo/pwdstore/ui/OnOffItemAnimator.kt new file mode 100644 index 00000000..e394e656 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/OnOffItemAnimator.kt @@ -0,0 +1,71 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.ui + +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.RecyclerView + +class OnOffItemAnimator : DefaultItemAnimator() { + + var isEnabled: Boolean = true + set(value) { + // Defer update until no animation is running anymore. + isRunning { field = value } + } + + private fun dontAnimate(viewHolder: RecyclerView.ViewHolder): Boolean { + dispatchAnimationFinished(viewHolder) + return false + } + + override fun animateAppearance( + viewHolder: RecyclerView.ViewHolder, + preLayoutInfo: ItemHolderInfo?, + postLayoutInfo: ItemHolderInfo + ): Boolean { + return if (isEnabled) { + super.animateAppearance(viewHolder, preLayoutInfo, postLayoutInfo) + } else { + dontAnimate(viewHolder) + } + } + + override fun animateChange( + oldHolder: RecyclerView.ViewHolder, + newHolder: RecyclerView.ViewHolder, + preInfo: ItemHolderInfo, + postInfo: ItemHolderInfo + ): Boolean { + return if (isEnabled) { + super.animateChange(oldHolder, newHolder, preInfo, postInfo) + } else { + dontAnimate(oldHolder) + } + } + + override fun animateDisappearance( + viewHolder: RecyclerView.ViewHolder, + preLayoutInfo: ItemHolderInfo, + postLayoutInfo: ItemHolderInfo? + ): Boolean { + return if (isEnabled) { + super.animateDisappearance(viewHolder, preLayoutInfo, postLayoutInfo) + } else { + dontAnimate(viewHolder) + } + } + + override fun animatePersistence( + viewHolder: RecyclerView.ViewHolder, + preInfo: ItemHolderInfo, + postInfo: ItemHolderInfo + ): Boolean { + return if (isEnabled) { + super.animatePersistence(viewHolder, preInfo, postInfo) + } else { + dontAnimate(viewHolder) + } + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/adapters/EntryRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/ui/adapters/EntryRecyclerAdapter.kt deleted file mode 100644 index 9cf477e2..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/ui/adapters/EntryRecyclerAdapter.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.ui.adapters - -import android.content.SharedPreferences -import android.text.SpannableString -import android.text.style.RelativeSizeSpan -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.widget.AppCompatImageView -import androidx.appcompat.widget.AppCompatTextView -import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.RecyclerView -import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.utils.PasswordItem -import com.zeapo.pwdstore.widget.MultiselectableConstraintLayout -import java.io.File -import java.util.ArrayList -import java.util.Locale -import java.util.TreeSet -import me.zhanghai.android.fastscroll.PopupTextProvider - -abstract class EntryRecyclerAdapter internal constructor(val values: ArrayList<PasswordItem>) : RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder>(), PopupTextProvider { - internal val selectedItems: MutableSet<Int> = TreeSet() - internal var settings: SharedPreferences? = null - - // Return the size of your dataset (invoked by the layout manager) - override fun getItemCount(): Int { - return values.size - } - - override fun getPopupText(position: Int): String { - return values[position].name[0].toString().toUpperCase(Locale.getDefault()) - } - - fun clear() { - this.values.clear() - this.notifyDataSetChanged() - } - - fun addAll(list: ArrayList<PasswordItem>) { - this.values.addAll(list) - this.notifyDataSetChanged() - } - - fun add(item: PasswordItem) { - this.values.add(item) - this.notifyItemInserted(itemCount) - } - - internal fun toggleSelection(position: Int) { - if (!selectedItems.remove(position)) { - selectedItems.add(position) - } - } - - // use this after an item is removed to update the positions of items in set - // that followed the removed position - fun updateSelectedItems(position: Int, selectedItems: MutableSet<Int>) { - val temp = TreeSet<Int>() - for (selected in selectedItems) { - if (selected > position) { - temp.add(selected - 1) - } else { - temp.add(selected) - } - } - selectedItems.clear() - selectedItems.addAll(temp) - } - - fun remove(position: Int) { - this.values.removeAt(position) - this.notifyItemRemoved(position) - - // keep selectedItems updated so we know what to notifyItemChanged - // (instead of just using notifyDataSetChanged) - updateSelectedItems(position, selectedItems) - } - - internal open fun getOnLongClickListener(holder: ViewHolder, pass: PasswordItem): View.OnLongClickListener { - return View.OnLongClickListener { false } - } - - // Replace the contents of a view (invoked by the layout manager) - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - settings = settings - ?: PreferenceManager.getDefaultSharedPreferences(holder.view.context.applicationContext) - val pass = values[position] - val showHidden = settings?.getBoolean("show_hidden_folders", false) ?: false - holder.name.text = pass.toString() - if (pass.type == PasswordItem.TYPE_CATEGORY) { - holder.typeImage.setImageResource(R.drawable.ic_multiple_files_24dp) - holder.folderIndicator.visibility = View.VISIBLE - val children = pass.file.listFiles { pathname -> - !(!showHidden && (pathname.isDirectory && pathname.isHidden)) - } ?: emptyArray<File>() - val childCount = children.size - holder.childCount.visibility = if (childCount > 0) View.VISIBLE else View.GONE - holder.childCount.text = "$childCount" - } else { - holder.typeImage.setImageResource(R.drawable.ic_action_secure_24dp) - val parentPath = pass.fullPathToParent.replace("(^/)|(/$)".toRegex(), "") - val source = "$parentPath\n$pass" - val spannable = SpannableString(source) - spannable.setSpan(RelativeSizeSpan(0.7f), 0, parentPath.length, 0) - holder.name.text = spannable - holder.childCount.visibility = View.GONE - holder.folderIndicator.visibility = View.GONE - } - holder.view.setOnClickListener(getOnClickListener(holder, pass)) - holder.view.setOnLongClickListener(getOnLongClickListener(holder, pass)) - - // after removal, everything is rebound for some reason; views are shuffled? - val selected = selectedItems.contains(position) - holder.view.isSelected = selected - (holder.itemView as MultiselectableConstraintLayout).setMultiSelected(selected) - } - - protected abstract fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener - - // Create new views (invoked by the layout manager) - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): ViewHolder { - // create a new view - val v = LayoutInflater.from(parent.context) - .inflate(R.layout.password_row_layout, parent, false) - return ViewHolder(v) - } - - /* - Provide a reference to the views for each data item - Complex data items may need more than one view per item, and - you provide access to all the views for a data item in a view holder - each data item is just a string in this case - */ - class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) { - val name: AppCompatTextView = view.findViewById(R.id.label) - val typeImage: AppCompatImageView = view.findViewById(R.id.type_image) - val childCount: AppCompatTextView = view.findViewById(R.id.child_count) - val folderIndicator: AppCompatImageView = view.findViewById(R.id.folder_indicator) - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/adapters/FolderRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/ui/adapters/FolderRecyclerAdapter.kt deleted file mode 100644 index 18aef509..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/ui/adapters/FolderRecyclerAdapter.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.ui.adapters - -import android.view.View -import com.zeapo.pwdstore.SelectFolderFragment -import com.zeapo.pwdstore.utils.PasswordItem -import java.util.ArrayList - -class FolderRecyclerAdapter( - private val listener: SelectFolderFragment.OnFragmentInteractionListener, - values: ArrayList<PasswordItem> -) : EntryRecyclerAdapter(values) { - - override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener { - return View.OnClickListener { - listener.onFragmentInteraction(pass) - notifyItemChanged(holder.adapterPosition) - } - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/adapters/PasswordItemRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/ui/adapters/PasswordItemRecyclerAdapter.kt new file mode 100644 index 00000000..9a914be9 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/adapters/PasswordItemRecyclerAdapter.kt @@ -0,0 +1,88 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package com.zeapo.pwdstore.ui.adapters + +import android.text.SpannableString +import android.text.style.RelativeSizeSpan +import android.view.MotionEvent +import android.view.View +import androidx.appcompat.widget.AppCompatImageView +import androidx.appcompat.widget.AppCompatTextView +import androidx.preference.PreferenceManager +import androidx.recyclerview.selection.ItemDetailsLookup +import androidx.recyclerview.selection.Selection +import androidx.recyclerview.widget.RecyclerView +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.SearchableRepositoryAdapter +import com.zeapo.pwdstore.stableId +import com.zeapo.pwdstore.utils.PasswordItem +import java.io.File + +open class PasswordItemRecyclerAdapter : + SearchableRepositoryAdapter<PasswordItemRecyclerAdapter.PasswordItemViewHolder>( + R.layout.password_row_layout, + ::PasswordItemViewHolder, + PasswordItemViewHolder::bind + ) { + + fun makeSelectable(recyclerView: RecyclerView) { + makeSelectable(recyclerView, ::PasswordItemDetailsLookup) + } + + override fun onItemClicked(listener: (holder: PasswordItemViewHolder, item: PasswordItem) -> Unit): PasswordItemRecyclerAdapter { + return super.onItemClicked(listener) as PasswordItemRecyclerAdapter + } + + override fun onSelectionChanged(listener: (selection: Selection<String>) -> Unit): PasswordItemRecyclerAdapter { + return super.onSelectionChanged(listener) as PasswordItemRecyclerAdapter + } + + class PasswordItemViewHolder(view: View) : RecyclerView.ViewHolder(view) { + private val name: AppCompatTextView = itemView.findViewById(R.id.label) + private val typeImage: AppCompatImageView = itemView.findViewById(R.id.type_image) + private val childCount: AppCompatTextView = itemView.findViewById(R.id.child_count) + private val folderIndicator: AppCompatImageView = + itemView.findViewById(R.id.folder_indicator) + lateinit var itemDetails: ItemDetailsLookup.ItemDetails<String> + + fun bind(item: PasswordItem) { + val settings = + PreferenceManager.getDefaultSharedPreferences(itemView.context.applicationContext) + val showHidden = settings.getBoolean("show_hidden_folders", false) + name.text = item.toString() + if (item.type == PasswordItem.TYPE_CATEGORY) { + typeImage.setImageResource(R.drawable.ic_multiple_files_24dp) + folderIndicator.visibility = View.VISIBLE + val children = item.file.listFiles { pathname -> + !(!showHidden && (pathname.isDirectory && pathname.isHidden)) + } ?: emptyArray<File>() + val count = children.size + childCount.visibility = if (count > 0) View.VISIBLE else View.GONE + childCount.text = "$count" + } else { + typeImage.setImageResource(R.drawable.ic_action_secure_24dp) + val parentPath = item.fullPathToParent.replace("(^/)|(/$)".toRegex(), "") + val source = "$parentPath\n$item" + val spannable = SpannableString(source) + spannable.setSpan(RelativeSizeSpan(0.7f), 0, parentPath.length, 0) + name.text = spannable + childCount.visibility = View.GONE + folderIndicator.visibility = View.GONE + } + itemDetails = object : ItemDetailsLookup.ItemDetails<String>() { + override fun getPosition() = absoluteAdapterPosition + override fun getSelectionKey() = item.stableId + } + } + } + + class PasswordItemDetailsLookup(private val recyclerView: RecyclerView) : + ItemDetailsLookup<String>() { + override fun getItemDetails(event: MotionEvent): ItemDetails<String>? { + val view = recyclerView.findChildViewUnder(event.x, event.y) ?: return null + return (recyclerView.getChildViewHolder(view) as PasswordItemViewHolder).itemDetails + } + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/adapters/PasswordRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/ui/adapters/PasswordRecyclerAdapter.kt deleted file mode 100644 index 39d73563..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/ui/adapters/PasswordRecyclerAdapter.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.ui.adapters - -import android.view.Menu -import android.view.MenuItem -import android.view.View -import androidx.appcompat.view.ActionMode -import com.zeapo.pwdstore.PasswordFragment -import com.zeapo.pwdstore.PasswordStore -import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.utils.PasswordItem -import java.util.ArrayList -import java.util.TreeSet - -class PasswordRecyclerAdapter( - private val activity: PasswordStore, - private val listener: PasswordFragment.OnFragmentInteractionListener, - values: ArrayList<PasswordItem> -) : EntryRecyclerAdapter(values) { - var actionMode: ActionMode? = null - private var canEdit: Boolean = false - 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 - mode.menuInflater.inflate(R.menu.context_pass, menu) - // hide the fab - activity.findViewById<View>(R.id.fab).visibility = View.GONE - return true - } - - // Called each time the action mode is shown. Always called after onCreateActionMode, but - // may be called multiple times if the mode is invalidated. - override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { - menu.findItem(R.id.menu_edit_password).isVisible = canEdit - return true // Return false if nothing is done - } - - // Called when the user selects a contextual menu item - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_delete_password -> { - activity.deletePasswords(this@PasswordRecyclerAdapter, TreeSet(selectedItems)) - mode.finish() // Action picked, so close the CAB - return true - } - R.id.menu_edit_password -> { - activity.editPassword(values[selectedItems.iterator().next()]) - mode.finish() - return true - } - R.id.menu_move_password -> { - val selectedPasswords = ArrayList<PasswordItem>() - for (id in selectedItems) { - selectedPasswords.add(values[id]) - } - activity.movePasswords(selectedPasswords) - return false - } - else -> return false - } - } - - // Called when the user exits the action mode - override fun onDestroyActionMode(mode: ActionMode) { - val it = selectedItems.iterator() - while (it.hasNext()) { - // need the setSelected line in onBind - notifyItemChanged(it.next()) - it.remove() - } - actionMode = null - // show the fab - activity.findViewById<View>(R.id.fab).visibility = View.VISIBLE - } - } - - override fun getOnLongClickListener(holder: ViewHolder, pass: PasswordItem): View.OnLongClickListener { - return View.OnLongClickListener { - if (actionMode != null) { - return@OnLongClickListener false - } - toggleSelection(holder.adapterPosition) - canEdit = pass.type == PasswordItem.TYPE_PASSWORD - // Start the CAB using the ActionMode.Callback - actionMode = activity.startSupportActionMode(actionModeCallback) - actionMode?.title = "" + selectedItems.size - actionMode?.invalidate() - notifyItemChanged(holder.adapterPosition) - true - } - } - - override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener { - return View.OnClickListener { - if (actionMode != null) { - toggleSelection(holder.adapterPosition) - actionMode?.title = "" + selectedItems.size - if (selectedItems.isEmpty()) { - actionMode?.finish() - } else if (selectedItems.size == 1 && (canEdit.not())) { - if (values[selectedItems.iterator().next()].type == PasswordItem.TYPE_PASSWORD) { - canEdit = true - actionMode?.invalidate() - } - } else if (selectedItems.size >= 1 && canEdit) { - canEdit = false - actionMode?.invalidate() - } - } else { - listener.onFragmentInteraction(pass) - } - notifyItemChanged(holder.adapterPosition) - } - } -} 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 index bff229b9..1426ef1e 100644 --- a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt @@ -48,7 +48,7 @@ class FolderCreationDialogFragment : DialogFragment() { val materialTextView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text) val folderName = materialTextView.text.toString() File("$currentDir/$folderName").mkdir() - (requireActivity() as PasswordStore).updateListAdapter() + (requireActivity() as PasswordStore).refreshPasswordList() dismiss() } |
