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 {