From d7946326161043eeb65c8d0fd32b596f8aeabbec Mon Sep 17 00:00:00 2001 From: Mygod Date: Thu, 11 Feb 2016 21:09:43 +0800 Subject: [PATCH] Refine undo --- src/main/res/values-zh/strings.xml | 5 +- src/main/res/values/strings.xml | 5 +- .../shadowsocks/ProfileManagerActivity.scala | 47 +++++++------------ .../widget/UndoSnackbarManager.scala | 45 ++++++++++++++++++ 4 files changed, 70 insertions(+), 32 deletions(-) create mode 100644 src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml index ee30b407..cfbb5224 100755 --- a/src/main/res/values-zh/strings.xml +++ b/src/main/res/values-zh/strings.xml @@ -106,7 +106,10 @@ 导入成功 导入失败 - 已删除 + + 已删除 + 已删除 %d 项 + 撤销 启动服务 连接到当前服务器 diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 4c020eeb..7ef92294 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -112,7 +112,10 @@ Add this Shadowsocks Profile? Scan QR code Manual Settings - Removed + + Removed + %d items removed + Undo diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index 55423e0a..953af3d6 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -2,7 +2,6 @@ package com.github.shadowsocks import android.content.Intent import android.os.{Bundle, Handler} -import android.support.design.widget.Snackbar import android.support.v7.app.{AlertDialog, AppCompatActivity} import android.support.v7.widget.RecyclerView.ViewHolder import android.support.v7.widget.Toolbar.OnMenuItemClickListener @@ -11,12 +10,12 @@ import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar} import android.text.style.TextAppearanceSpan import android.text.{SpannableStringBuilder, Spanned} -import android.view.View.{OnAttachStateChangeListener, OnClickListener} import android.view.{LayoutInflater, MenuItem, View, ViewGroup} import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast} import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.database.Profile import com.github.shadowsocks.utils.{Parser, TrafficMonitor, Utils} +import com.github.shadowsocks.widget.UndoSnackbarManager import com.google.zxing.integration.android.IntentIntegrator import net.glxn.qrgen.android.QRCode @@ -33,7 +32,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe import ProfileManagerActivity._ private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener { - private var item: Profile = _ + var item: Profile = _ private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView] itemView.setOnClickListener(this) @@ -95,7 +94,6 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe } private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] { - private val recycleBin = new ArrayBuffer[(Int, Profile)] var profiles = new ArrayBuffer[Profile] profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile]) @@ -107,14 +105,14 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe new ProfileViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_profiles_item, vg, false)) def add(item: Profile) { - removedSnackbar.dismiss - commitRemoves + undoManager.flush val pos = getItemCount profiles += item notifyItemInserted(pos) } def move(from: Int, to: Int) { + undoManager.flush val step = if (from < to) 1 else -1 val first = profiles(from) var previousOrder = profiles(from).userOrder @@ -133,23 +131,16 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe } def remove(pos: Int) { - recycleBin.append((pos, profiles(pos))) profiles.remove(pos) notifyItemRemoved(pos) } - def undoRemoves { - for ((index, item) <- recycleBin.reverseIterator) { - profiles.insert(index, item) - notifyItemInserted(index) - } - recycleBin.clear + def undo(actions: Iterator[(Int, Profile)]) = for ((index, item) <- actions) { + profiles.insert(index, item) + notifyItemInserted(index) } - def commitRemoves { - for ((index, item) <- recycleBin) { - ShadowsocksApplication.profileManager.delProfile(item.id) - if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1) - } - recycleBin.clear + def commit(actions: Iterator[(Int, Profile)]) = for ((index, item) <- actions) { + ShadowsocksApplication.profileManager.delProfile(item.id) + if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1) } } @@ -157,7 +148,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe private val handler = new Handler private lazy val profilesAdapter = new ProfilesAdapter - private var removedSnackbar: Snackbar = _ + private var undoManager: UndoSnackbarManager[Profile] = _ override def onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) @@ -182,17 +173,13 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst { case (profile, i) if profile.id == ShadowsocksApplication.profileId => i }.getOrElse(-1)) - removedSnackbar = Snackbar.make(profilesList, R.string.removed, Snackbar.LENGTH_LONG) - .setAction(R.string.undo, ((v: View) => profilesAdapter.undoRemoves): OnClickListener) - removedSnackbar.getView.addOnAttachStateChangeListener(new OnAttachStateChangeListener { - def onViewDetachedFromWindow(v: View) = profilesAdapter.commitRemoves - def onViewAttachedToWindow(v: View) = () - }) + undoManager = new UndoSnackbarManager[Profile](profilesList, profilesAdapter.undo, profilesAdapter.commit) new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.START | ItemTouchHelper.END) { def onSwiped(viewHolder: ViewHolder, direction: Int) = { - profilesAdapter.remove(viewHolder.getAdapterPosition) - removedSnackbar.show + val index = viewHolder.getAdapterPosition + profilesAdapter.remove(index) + undoManager.remove(index, viewHolder.asInstanceOf[ProfileViewHolder].item) } def onMove(recyclerView: RecyclerView, viewHolder: ViewHolder, target: ViewHolder) = { profilesAdapter.move(viewHolder.getAdapterPosition, target.getAdapterPosition) @@ -224,9 +211,9 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe override def onDestroy { deattachService() - super.onDestroy + undoManager.flush ShadowsocksApplication.profileManager.setProfileAddedListener(null) - profilesAdapter.commitRemoves + super.onDestroy } override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { diff --git a/src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala b/src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala new file mode 100644 index 00000000..914ac0ad --- /dev/null +++ b/src/main/scala/com/github/shadowsocks/widget/UndoSnackbarManager.scala @@ -0,0 +1,45 @@ +package com.github.shadowsocks.widget + +import android.support.design.widget.Snackbar +import android.view.View +import com.github.shadowsocks.R + +import scala.collection.mutable.ArrayBuffer + +/** + * @author Mygod + * @param view The view to find a parent from. + * @param undo Callback for undoing removals. + * @param commit Callback for committing removals. + * @tparam T Item type. + */ +class UndoSnackbarManager[T](view: View, undo: Iterator[(Int, T)] => Unit, + commit: Iterator[(Int, T)] => Unit = null) { + private val recycleBin = new ArrayBuffer[(Int, T)] + private val removedCallback = new Snackbar.Callback { + override def onDismissed(snackbar: Snackbar, event: Int) = { + event match { + case Snackbar.Callback.DISMISS_EVENT_MANUAL | Snackbar.Callback.DISMISS_EVENT_TIMEOUT => + if (commit != null) commit(recycleBin.iterator) + recycleBin.clear + case _ => + } + last = null + } + } + private var last: Snackbar = _ + + def remove(index: Int, item: T) = { + recycleBin.append((index, item)) + val count = recycleBin.length + last = Snackbar + .make(view, view.getResources.getQuantityString(R.plurals.removed, count, count: Integer), Snackbar.LENGTH_LONG) + .setCallback(removedCallback).setAction(R.string.undo, (_ => { + undo(recycleBin.reverseIterator) + recycleBin.clear + }): View.OnClickListener) + last.show + } + + def flush = if (last != null) last.dismiss +}