Commit 0030d8aa authored by Max Lv's avatar Max Lv

Fix animation and icons

parent 77d2def9
...@@ -20,19 +20,11 @@ ...@@ -20,19 +20,11 @@
package com.github.shadowsocks.subscription package com.github.shadowsocks.subscription
import android.content.Context
import android.util.Log
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import java.io.File
import java.io.IOException
import java.io.Reader import java.io.Reader
import java.net.URL import java.net.URL
import java.net.URLConnection
class Subscription { class Subscription {
companion object { companion object {
...@@ -49,19 +41,22 @@ class Subscription { ...@@ -49,19 +41,22 @@ class Subscription {
} }
private abstract class BaseSorter<T> : SortedList.Callback<T>() { private abstract class BaseSorter<T> : SortedList.Callback<T>() {
override fun onInserted(position: Int, count: Int) { } override fun onInserted(position: Int, count: Int) {}
override fun areContentsTheSame(oldItem: T?, newItem: T?): Boolean = oldItem == newItem override fun areContentsTheSame(oldItem: T?, newItem: T?): Boolean = oldItem == newItem
override fun onMoved(fromPosition: Int, toPosition: Int) { } override fun onMoved(fromPosition: Int, toPosition: Int) {}
override fun onChanged(position: Int, count: Int) { } override fun onChanged(position: Int, count: Int) {}
override fun onRemoved(position: Int, count: Int) { } override fun onRemoved(position: Int, count: Int) {}
override fun areItemsTheSame(item1: T?, item2: T?): Boolean = item1 == item2 override fun areItemsTheSame(item1: T?, item2: T?): Boolean = item1 == item2
override fun compare(o1: T?, o2: T?): Int = override fun compare(o1: T?, o2: T?): Int =
if (o1 == null) if (o2 == null) 0 else 1 else if (o2 == null) -1 else compareNonNull(o1, o2) if (o1 == null) if (o2 == null) 0 else 1 else if (o2 == null) -1 else compareNonNull(o1, o2)
abstract fun compareNonNull(o1: T, o2: T): Int abstract fun compareNonNull(o1: T, o2: T): Int
} }
private open class DefaultSorter<T : Comparable<T>> : BaseSorter<T>() { private open class DefaultSorter<T : Comparable<T>> : BaseSorter<T>() {
override fun compareNonNull(o1: T, o2: T): Int = o1.compareTo(o2) override fun compareNonNull(o1: T, o2: T): Int = o1.compareTo(o2)
} }
private object URLSorter : BaseSorter<URL>() { private object URLSorter : BaseSorter<URL>() {
private val ordering = compareBy<URL>({ it.host }, { it.port }, { it.file }, { it.protocol }) private val ordering = compareBy<URL>({ it.host }, { it.port }, { it.file }, { it.protocol })
override fun compareNonNull(o1: URL, o2: URL): Int = ordering.compare(o1, o2) override fun compareNonNull(o1: URL, o2: URL): Int = ordering.compare(o1, o2)
......
...@@ -70,6 +70,7 @@ dependencies { ...@@ -70,6 +70,7 @@ dependencies {
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.4' implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.4'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0' implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3' implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3'
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
testImplementation "junit:junit:$junitVersion" testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion" androidTestImplementation "androidx.test:runner:$androidTestVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidEspressoVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidEspressoVersion"
......
...@@ -21,62 +21,52 @@ ...@@ -21,62 +21,52 @@
package com.github.shadowsocks.subscription package com.github.shadowsocks.subscription
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.util.Log
import android.view.* import android.view.*
import android.widget.* import android.widget.AdapterView
import android.widget.EditText
import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.shadowsocks.Core
import com.github.shadowsocks.MainActivity import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R import com.github.shadowsocks.R
import com.github.shadowsocks.ToolbarFragment import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.plugin.AlertDialogFragment import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.widget.ListHolderListener import com.github.shadowsocks.widget.ListHolderListener
import com.github.shadowsocks.widget.MainListListener import com.github.shadowsocks.widget.MainListListener
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
import java.net.IDN import me.zhanghai.android.materialprogressbar.MaterialProgressBar
import java.net.HttpURLConnection
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URL
import java.util.* import java.util.*
import java.util.regex.PatternSyntaxException
import com.github.shadowsocks.subscription.Subscription
import com.github.shadowsocks.utils.*
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.io.IOError
import java.io.IOException
import java.io.InputStream
import java.net.HttpURLConnection
class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
companion object { companion object {
private const val REQUEST_CODE_ADD = 1 private const val REQUEST_CODE_ADD = 1
private const val REQUEST_CODE_EDIT = 2 private const val REQUEST_CODE_EDIT = 2
private const val SELECTED_URLS = "com.github.shadowsocks.acl.subscription.SELECTED_URLS" } private const val SELECTED_URLS = "com.github.shadowsocks.acl.subscription.SELECTED_URLS"
}
@Parcelize @Parcelize
...@@ -113,8 +103,8 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -113,8 +103,8 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
validate() validate()
} }
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { } override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) = validate(value = s) override fun afterTextChanged(s: Editable) = validate(value = s)
override fun onNothingSelected(parent: AdapterView<*>?) = check(false) override fun onNothingSelected(parent: AdapterView<*>?) = check(false)
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = validate() override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = validate()
...@@ -167,6 +157,7 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -167,6 +157,7 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
if (selectedItems.isNotEmpty()) onLongClick(v) if (selectedItems.isNotEmpty()) onLongClick(v)
else SubDialogFragment().withArg(SubItem(item.toString())).show(this@SubscriptionFragment, REQUEST_CODE_EDIT) else SubDialogFragment().withArg(SubItem(item.toString())).show(this@SubscriptionFragment, REQUEST_CODE_EDIT)
} }
override fun onLongClick(v: View?): Boolean { override fun onLongClick(v: View?): Boolean {
if (!selectedItems.add(item)) selectedItems.remove(item) // toggle if (!selectedItems.add(item)) selectedItems.remove(item) // toggle
itemView.isSelected = !itemView.isSelected itemView.isSelected = !itemView.isSelected
...@@ -181,8 +172,10 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -181,8 +172,10 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
override fun onBindViewHolder(holder: SubViewHolder, i: Int) { override fun onBindViewHolder(holder: SubViewHolder, i: Int) {
holder.bind(subscription.urls[i]) holder.bind(subscription.urls[i])
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = SubViewHolder(LayoutInflater override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = SubViewHolder(LayoutInflater
.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false)) .from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false))
override fun getItemCount(): Int = subscription.urls.size() override fun getItemCount(): Int = subscription.urls.size()
private fun apply() { private fun apply() {
...@@ -237,30 +230,38 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -237,30 +230,38 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
private val selectedItems = HashSet<Any>() private val selectedItems = HashSet<Any>()
private val adapter by lazy { SubscriptionAdapter() } private val adapter by lazy { SubscriptionAdapter() }
private lateinit var list: RecyclerView private lateinit var list: RecyclerView
private lateinit var progress: MaterialProgressBar
private var mode: ActionMode? = null private var mode: ActionMode? = null
private lateinit var undoManager: UndoSnackbarManager<Any> private lateinit var undoManager: UndoSnackbarManager<Any>
private var fetchJob: Job? = null
private fun fetchServerFromSubscriptions() { private fun fetchServerFromSubscriptions() {
// SubscriptionSyncer.schedule() if (fetchJob?.isActive != true) {
val activity = activity as MainActivity val activity = activity as MainActivity
progress.visibility = View.VISIBLE
val job = GlobalScope.launch { fetchJob = GlobalScope.launch {
val subscription = Subscription.instance val subscription = Subscription.instance
try { try {
for (url in subscription.urls.asIterable()) { for (url in subscription.urls.asIterable()) {
val connection = url.openConnection() as HttpURLConnection val connection = url.openConnection() as HttpURLConnection
ProfileManager.createProfilesFromJson(sequenceOf(connection.inputStream)) ProfileManager.createProfilesFromJson(sequenceOf(connection.inputStream),replace = true)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
activity.snackbar(e.readableMessage).show() activity.snackbar(e.readableMessage).show()
} finally {
progress.post {
progress.visibility = View.INVISIBLE
}
}
} }
} }
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.layout_custom_rules, container, false) inflater.inflate(R.layout.layout_subscriptions, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
...@@ -278,13 +279,16 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -278,13 +279,16 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
list.layoutManager = LinearLayoutManager(activity, RecyclerView.VERTICAL, false) list.layoutManager = LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
list.itemAnimator = DefaultItemAnimator() list.itemAnimator = DefaultItemAnimator()
list.adapter = adapter list.adapter = adapter
progress = view.findViewById(R.id.indeterminate_horizontal_progress)
FastScrollerBuilder(list).useMd2Style().build() FastScrollerBuilder(list).useMd2Style().build()
undoManager = UndoSnackbarManager(activity, adapter::undo) undoManager = UndoSnackbarManager(activity, adapter::undo)
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START or ItemTouchHelper.END) { ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START or ItemTouchHelper.END) {
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int = override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int =
if (isEnabled && selectedItems.isEmpty()) super.getSwipeDirs(recyclerView, viewHolder) else 0 if (isEnabled && selectedItems.isEmpty()) super.getSwipeDirs(recyclerView, viewHolder) else 0
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) =
adapter.remove(viewHolder.adapterPosition) adapter.remove(viewHolder.adapterPosition)
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean = false target: RecyclerView.ViewHolder): Boolean = false
}).attachToRecyclerView(list) }).attachToRecyclerView(list)
...@@ -325,7 +329,7 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -325,7 +329,7 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
when (resultCode) { when (resultCode) {
DialogInterface.BUTTON_POSITIVE -> { DialogInterface.BUTTON_POSITIVE -> {
if (editing) adapter.remove(ret.replacing.toURL()) if (editing) adapter.remove(ret.replacing.toURL())
adapter.add(ret.edited.toURL())?.also { list.post { list.scrollToPosition(it) } } adapter.add(ret.edited.toURL()).also { list.post { list.scrollToPosition(it) } }
} }
DialogInterface.BUTTON_NEUTRAL -> ret.replacing.toURL().let { item -> DialogInterface.BUTTON_NEUTRAL -> ret.replacing.toURL().let { item ->
adapter.remove(item) adapter.remove(item)
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<include layout="@layout/toolbar_light_dark" />
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/indeterminate_horizontal_progress"
style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:indeterminate="true"
android:visibility="invisible"
app:mpb_progressStyle="horizontal"
app:mpb_showProgressBackground="false"
app:mpb_useIntrinsicPadding="false" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
tools:listitem="@android:layout/simple_list_item_1" />
</FrameLayout>
</LinearLayout>
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
android:icon="@drawable/ic_action_description"/> android:icon="@drawable/ic_action_description"/>
<item android:id="@+id/subscriptions" <item android:id="@+id/subscriptions"
android:title="@string/subscriptions" android:title="@string/subscriptions"
android:icon="@drawable/ic_action_description"/> android:icon="@drawable/ic_action_cloud_download"/>
<item android:id="@+id/customRules" <item android:id="@+id/customRules"
android:title="@string/custom_rules" android:title="@string/custom_rules"
android:icon="@drawable/ic_action_assignment"/> android:icon="@drawable/ic_action_assignment"/>
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
app:showAsAction="always"/> app:showAsAction="always"/>
<item android:title="@string/update_subscription" <item android:title="@string/update_subscription"
android:id="@+id/action_update_subscription" android:id="@+id/action_update_subscription"
android:icon="@drawable/ic_action_done" android:icon="@drawable/ic_action_cloud_download"
android:alphabeticShortcut="r" android:alphabeticShortcut="r"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
</menu> </menu>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment