diff --git a/build.sbt b/build.sbt index 4f84deaf..94a39eec 100644 --- a/build.sbt +++ b/build.sbt @@ -37,6 +37,7 @@ libraryDependencies ++= "com.android.support" % "design" % "25.1.0" :: "com.android.support" % "gridlayout-v7" % "25.1.0" :: "com.android.support" % "preference-v14" % "25.1.0" :: + "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: "com.evernote" % "android-job" % "1.1.3" :: "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: "com.google.android.gms" % "play-services-ads" % "10.0.1" :: diff --git a/src/main/res/layout/layout_apps.xml b/src/main/res/layout/layout_apps.xml index ec8d350e..afec71f2 100644 --- a/src/main/res/layout/layout_apps.xml +++ b/src/main/res/layout/layout_apps.xml @@ -1,6 +1,7 @@ + diff --git a/src/main/res/layout/layout_apps_item.xml b/src/main/res/layout/layout_apps_item.xml index ec11118c..21870bb3 100644 --- a/src/main/res/layout/layout_apps_item.xml +++ b/src/main/res/layout/layout_apps_item.xml @@ -28,6 +28,6 @@ android:focusable="false" android:focusableInTouchMode="false" android:clickable="false" - android:layout_marginEnd="8dp"/> + android:layout_marginEnd="16dp"/> diff --git a/src/main/res/layout/layout_custom_rules.xml b/src/main/res/layout/layout_custom_rules.xml new file mode 100644 index 00000000..510e9dba --- /dev/null +++ b/src/main/res/layout/layout_custom_rules.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/src/main/res/layout/layout_list.xml b/src/main/res/layout/layout_list.xml index bfe42dca..f4b10ab8 100644 --- a/src/main/res/layout/layout_list.xml +++ b/src/main/res/layout/layout_list.xml @@ -4,8 +4,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - 14sp + diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala index c3818e17..da79bf5a 100644 --- a/src/main/scala/com/github/shadowsocks/AppManager.scala +++ b/src/main/scala/com/github/shadowsocks/AppManager.scala @@ -23,6 +23,7 @@ package com.github.shadowsocks import java.util.concurrent.atomic.AtomicBoolean import android.Manifest.permission +import android.animation.{Animator, AnimatorListenerAdapter} import android.app.TaskStackBuilder import android.content._ import android.content.pm.PackageManager @@ -33,6 +34,7 @@ import android.support.v7.widget.Toolbar.OnMenuItemClickListener import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar} import android.view._ import android.widget._ +import com.futuremind.recyclerviewfastscroll.{FastScroller, SectionTitleProvider} import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.{Key, Utils} @@ -102,7 +104,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { } } - private final class AppsAdapter extends RecyclerView.Adapter[AppViewHolder] { + private final class AppsAdapter extends RecyclerView.Adapter[AppViewHolder] with SectionTitleProvider { private val apps = getApps(getPackageManager).sortWith((a, b) => { val aProxied = proxiedApps.contains(a.packageName) if (aProxied ^ proxiedApps.contains(b.packageName)) aProxied else a.name.compareToIgnoreCase(b.name) < 0 @@ -112,12 +114,14 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { def onBindViewHolder(vh: AppViewHolder, i: Int): Unit = vh.bind(apps(i)) def onCreateViewHolder(vg: ViewGroup, i: Int) = new AppViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_apps_item, vg, false)) + def getSectionTitle(i: Int): String = apps(i).name.substring(0, 1) } private var proxiedApps: mutable.HashSet[String] = _ private var toolbar: Toolbar = _ private var bypassSwitch: Switch = _ private var appListView: RecyclerView = _ + private var fastScroller: FastScroller = _ private var loadingView: View = _ private val appsLoading = new AtomicBoolean private var handler: Handler = _ @@ -170,6 +174,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { app.editor.putString(Key.individual, apps).putBoolean(Key.dirty, true).apply() Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show() appListView.setVisibility(View.GONE) + fastScroller.setVisibility(View.GONE) loadingView.setVisibility(View.VISIBLE) initProxiedApps(apps) reloadApps() @@ -220,9 +225,11 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { initProxiedApps() loadingView = findViewById(R.id.loading) - appListView = findViewById(R.id.applistview).asInstanceOf[RecyclerView] + appListView = findViewById(R.id.list).asInstanceOf[RecyclerView] appListView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)) appListView.setItemAnimator(new DefaultItemAnimator) + fastScroller = findViewById(R.id.fastscroller).asInstanceOf[FastScroller] + fastScroller.setRecyclerView(appListView) instance = this loadAppsAsync() @@ -239,7 +246,16 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener { } while (!appsLoading.compareAndSet(true, false)) handler.post(() => { appListView.setAdapter(adapter) - Utils.crossFade(AppManager.this, loadingView, appListView) + val shortAnimTime = getResources.getInteger(android.R.integer.config_shortAnimTime) + appListView.setAlpha(0) + appListView.setVisibility(View.VISIBLE) + appListView.animate().alpha(1).setDuration(shortAnimTime) + fastScroller.setAlpha(0) + fastScroller.setVisibility(View.VISIBLE) + fastScroller.animate().alpha(1).setDuration(shortAnimTime) + loadingView.animate().alpha(0).setDuration(shortAnimTime).setListener(new AnimatorListenerAdapter { + override def onAnimationEnd(animation: Animator): Unit = loadingView.setVisibility(View.GONE) + }) }) } } diff --git a/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala b/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala index 76aa3907..9754f72e 100644 --- a/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala +++ b/src/main/scala/com/github/shadowsocks/acl/CustomRulesFragment.scala @@ -12,6 +12,7 @@ import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar} import android.view._ import android.widget.{EditText, Spinner, TextView, Toast} +import com.futuremind.recyclerviewfastscroll.{FastScroller, SectionTitleProvider} import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.utils.State import com.github.shadowsocks.widget.UndoSnackbarManager @@ -103,7 +104,7 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi } } - private final class AclRulesAdapter extends RecyclerView.Adapter[AclRuleViewHolder] { + private final class AclRulesAdapter extends RecyclerView.Adapter[AclRuleViewHolder] with SectionTitleProvider { private val acl = Acl.customRules private var savePending: Boolean = _ private def apply() = if (!savePending) { @@ -192,6 +193,17 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi onSelectedItemsUpdated() notifyDataSetChanged() } + + def getSectionTitle(i: Int): String = { + val j = i - acl.subnets.size + (if (j < 0) acl.subnets(i).address.toString.charAt(0).toString else { + val hostname = acl.proxyHostnames(i) + PATTERN_DOMAIN.findFirstMatchIn(hostname) match { + case Some(m) => m.matched.replaceAll("\\\\.", ".") // don't convert IDN yet + case None => hostname + } + }).substring(0, 1) + } } private lazy val adapter = new AclRulesAdapter() @@ -201,7 +213,7 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi private lazy val clipboard = getActivity.getSystemService(Context.CLIPBOARD_SERVICE).asInstanceOf[ClipboardManager] override def onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle): View = - inflater.inflate(R.layout.layout_list, container, false) + inflater.inflate(R.layout.layout_custom_rules, container, false) override def onViewCreated(view: View, savedInstanceState: Bundle) { super.onViewCreated(view, savedInstanceState) if (savedInstanceState != null) { @@ -224,6 +236,8 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi list.setLayoutManager(new LinearLayoutManager(getActivity, LinearLayoutManager.VERTICAL, false)) list.setItemAnimator(new DefaultItemAnimator) list.setAdapter(adapter) + val fastScroller = view.findViewById(R.id.fastscroller).asInstanceOf[FastScroller] + fastScroller.setRecyclerView(list) undoManager = new UndoSnackbarManager[AnyRef](getActivity.findViewById(R.id.snackbar), adapter.undo) new ItemTouchHelper(new SimpleCallback(0, ItemTouchHelper.START | ItemTouchHelper.END) { override def getSwipeDirs(recyclerView: RecyclerView, viewHolder: ViewHolder): Int = diff --git a/src/main/scala/com/github/shadowsocks/utils/Utils.scala b/src/main/scala/com/github/shadowsocks/utils/Utils.scala index 4f61de70..99c2ed8f 100644 --- a/src/main/scala/com/github/shadowsocks/utils/Utils.scala +++ b/src/main/scala/com/github/shadowsocks/utils/Utils.scala @@ -82,16 +82,6 @@ object Utils { toast } - def crossFade(context: Context, from: View, to: View) { - def shortAnimTime = context.getResources.getInteger(android.R.integer.config_shortAnimTime) - to.setAlpha(0) - to.setVisibility(View.VISIBLE) - to.animate().alpha(1).setDuration(shortAnimTime) - from.animate().alpha(0).setDuration(shortAnimTime).setListener(new AnimatorListenerAdapter { - override def onAnimationEnd(animation: Animator): Unit = from.setVisibility(View.GONE) - }) - } - def readAllLines(f: File): String = { val scanner = new Scanner(f) try {