add prefix merger

This commit is contained in:
Kr328 committed 2020-02-16 15:54:22 +08:00
1 parent d5a0299d91
commit 5d4b6b4fb5
8 files changed
+238 -88

No files matched your search

@@ -8,20 +8,22 @@ import com.github.kr328.clash.adapter.AbstractProxyAdapter
import com.github.kr328.clash.adapter.GridProxyAdapter
import com.github.kr328.clash.adapter.ProxyChipAdapter
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.core.model.Proxy
import com.github.kr328.clash.preference.UiPreferences
import com.github.kr328.clash.remote.withClash
import com.github.kr328.clash.utils.PrefixMerger
import com.github.kr328.clash.utils.ProxySorter
import com.github.kr328.clash.view.ProxiesTabMediator
import kotlinx.android.synthetic.main.activity_proxies.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ProxiesActivity : BaseActivity() {
private lateinit var mediator: ProxiesTabMediator
private val doScrollToLastProxy by lazy {
val selected = uiPreference.get(UiPreferences.LAST_SELECT_GROUP)
val selected = uiPreference.get(UiPreferences.PROXY_LAST_SELECT_GROUP)
launch {
mediator.scrollToDirect(selected)
@@ -63,7 +65,10 @@ class ProxiesActivity : BaseActivity() {
override fun onStop() {
uiPreference.edit {
put(UiPreferences.LAST_SELECT_GROUP, (chipList.adapter!! as ProxyChipAdapter).selected)
put(
UiPreferences.PROXY_LAST_SELECT_GROUP,
(chipList.adapter!! as ProxyChipAdapter).selected
)
}
super.onStop()
@@ -136,6 +141,17 @@ class ProxiesActivity : BaseActivity() {
put(UiPreferences.PROXY_PROXY_SORT, UiPreferences.PROXY_SORT_DELAY)
}
}
R.id.utilsMergePrefix -> {
item.isChecked = !item.isChecked
uiPreference.edit {
put(UiPreferences.PROXY_MERGE_PREFIX, item.isChecked)
}
refreshList()
return@launch
}
else -> return@launch
}
@@ -182,6 +198,9 @@ class ProxiesActivity : BaseActivity() {
UiPreferences.PROXY_SORT_DELAY ->
findItem(R.id.proxyDelay).isChecked = true
}
findItem(R.id.utilsMergePrefix).isChecked =
uiPreference.get(UiPreferences.PROXY_MERGE_PREFIX)
}
}
}
@@ -195,6 +214,16 @@ class ProxiesActivity : BaseActivity() {
queryAllProxyGroups()
}
val prefix = if (uiPreference.get(UiPreferences.PROXY_MERGE_PREFIX)) {
proxies.map {
async { PrefixMerger.merge(it.proxies.map { p -> it.name to p.name }) { it.second } }
}.flatMap {
it.await()
}.map {
it.value to it
}.toMap()
} else emptyMap()
val groupSort = when (uiPreference.get(UiPreferences.PROXY_GROUP_SORT)) {
UiPreferences.PROXY_SORT_DEFAULT ->
ProxySorter.Order.DEFAULT
@@ -218,7 +247,7 @@ class ProxiesActivity : BaseActivity() {
val sorter = ProxySorter(groupSort, proxySort)
val sorted = withContext(Dispatchers.Default) {
sorter.sort(proxies.toList())
sorter.sort(proxies)
}.run {
when (general.mode) {
General.Mode.GLOBAL -> this
@@ -227,12 +256,30 @@ class ProxiesActivity : BaseActivity() {
}
}
(mainList.adapter!! as AbstractProxyAdapter).apply {
root = sorted
val newList = withContext(Dispatchers.Default) {
sorted.map {
AbstractProxyAdapter.ProxyGroupInfo(it.name,
it.proxies.map { p ->
val r = prefix.getOrElse(it.name to p.name) {
PrefixMerger.Result(p.name, "", p)
}
applyChange()
AbstractProxyAdapter.ProxyInfo(
p.name,
it.name,
r.prefix,
if (r.content.isEmpty()) p.type.toString() else r.content,
p.delay.toShort(),
it.type == Proxy.Type.SELECT,
p.name == it.current
)
}
)
}
}
(mainList.adapter!! as AbstractProxyAdapter).applyChange(newList)
(chipList.adapter!! as ProxyChipAdapter).apply {
chips = sorted.map { it.name }
notifyDataSetChanged()
@@ -1,12 +1,23 @@
package com.github.kr328.clash.adapter
import com.github.kr328.clash.core.model.ProxyGroup
interface AbstractProxyAdapter {
var root: List<ProxyGroup>
data class ProxyGroupInfo(
val name: String,
val proxies: List<ProxyInfo>
)
data class ProxyInfo(
val name: String,
val group: String,
val prefix: String,
val content: String,
val delay: Short,
val selectable: Boolean,
val active: Boolean
)
var onSelectProxyListener: suspend (String, String) -> Unit
suspend fun applyChange()
suspend fun applyChange(newList: List<ProxyGroupInfo>)
suspend fun getGroupPosition(name: String): Int?
suspend fun getCurrentGroup(): String
}
@@ -12,11 +12,11 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.github.kr328.clash.ProxiesActivity
import com.github.kr328.clash.R
import com.github.kr328.clash.core.model.Proxy
import com.github.kr328.clash.core.model.ProxyGroup
import com.github.kr328.clash.core.utils.Log
import com.google.android.material.card.MaterialCardView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
@@ -27,20 +27,22 @@ class GridProxyAdapter(private val context: ProxiesActivity, spanCount: Int = 2)
val group: String
}
private data class ProxyGroupInfo(override val name: String) : RenderInfo {
private data class ProxyGroupRenderInfo(val info: AbstractProxyAdapter.ProxyGroupInfo) :
RenderInfo {
override val name: String
get() = info.name
override val group: String
get() = name
get() = info.name
}
private data class ProxyInfo(
override val name: String,
val type: Proxy.Type,
override val group: String,
val selectable: Boolean,
val delay: Short,
val active: Boolean
) : RenderInfo
private data class ProxyRenderInfo(val info: AbstractProxyAdapter.ProxyInfo) : RenderInfo {
override val name: String
get() = info.name
override val group: String
get() = info.group
}
private var rootMutex = Mutex()
private var renderList = emptyList<RenderInfo>()
@ColorInt
private val colorSurface: Int
@@ -61,15 +63,15 @@ class GridProxyAdapter(private val context: ProxiesActivity, spanCount: Int = 2)
spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (renderList[position]) {
is ProxyGroupInfo -> spanCount
is ProxyInfo -> 1
is ProxyGroupRenderInfo -> spanCount
is ProxyRenderInfo -> 1
else -> throw IllegalArgumentException()
}
}
}
}
override var root = listOf<ProxyGroup>()
private var root = listOf<AbstractProxyAdapter.ProxyGroupInfo>()
override var onSelectProxyListener: suspend (String, String) -> Unit = { _, _ -> }
private class ProxyGroupHeader(view: View) : RecyclerView.ViewHolder(view) {
@@ -79,50 +81,47 @@ class GridProxyAdapter(private val context: ProxiesActivity, spanCount: Int = 2)
private class ProxyItem(view: View) : RecyclerView.ViewHolder(view) {
val root: MaterialCardView = view.findViewById(R.id.root)
val name: TextView = view.findViewById(R.id.name)
val type: TextView = view.findViewById(R.id.type)
val prefix: TextView = view.findViewById(R.id.prefix)
val content: TextView = view.findViewById(R.id.content)
val delay: TextView = view.findViewById(R.id.delay)
}
override suspend fun applyChange() = withContext(Dispatchers.Default) {
val newRenderList = root
.flatMap {
listOf(ProxyGroupInfo(it.name)) + it.proxies.map { p ->
ProxyInfo(
p.name,
p.type,
it.name,
it.type == Proxy.Type.SELECT,
p.delay.toShort(),
it.current == p.name
)
override suspend fun applyChange(newList: List<AbstractProxyAdapter.ProxyGroupInfo>) =
withContext(Dispatchers.Default) {
rootMutex.lock()
val newRenderList = newList
.flatMap {
listOf(ProxyGroupRenderInfo(it)) + it.proxies.map { p -> ProxyRenderInfo(p) }
}
val oldRenderList = renderList
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldRenderList[oldItemPosition]::class == newRenderList[newItemPosition]::class &&
oldRenderList[oldItemPosition].name == newRenderList[newItemPosition].name
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldRenderList[oldItemPosition] == newRenderList[newItemPosition]
override fun getOldListSize(): Int = oldRenderList.size
override fun getNewListSize(): Int = newRenderList.size
})
withContext(Dispatchers.Main) {
root = newList
renderList = newRenderList
result.dispatchUpdatesTo(this@GridProxyAdapter)
}
val oldRenderList = renderList
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldRenderList[oldItemPosition]::class == newRenderList[newItemPosition]::class &&
oldRenderList[oldItemPosition].name == newRenderList[newItemPosition].name
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldRenderList[oldItemPosition] == newRenderList[newItemPosition]
override fun getOldListSize(): Int = oldRenderList.size
override fun getNewListSize(): Int = newRenderList.size
})
withContext(Dispatchers.Main) {
renderList = newRenderList
result.dispatchUpdatesTo(this@GridProxyAdapter)
rootMutex.unlock()
}
}
override suspend fun getGroupPosition(name: String): Int? {
return withContext(Dispatchers.Default) {
renderList.mapIndexed { index, p ->
if (p is ProxyGroupInfo && p.name == name)
if (p is ProxyGroupRenderInfo && p.name == name)
index
else
-1
@@ -158,49 +157,55 @@ class GridProxyAdapter(private val context: ProxiesActivity, spanCount: Int = 2)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ProxyGroupHeader -> {
val current = renderList[position] as ProxyGroupInfo
val current = renderList[position] as ProxyGroupRenderInfo
holder.name.text = current.name
holder.name.text = current.info.name
holder.urlTest.setOnClickListener {
}
}
is ProxyItem -> {
val current = renderList[position] as ProxyInfo
val current = renderList[position] as ProxyRenderInfo
holder.name.text = current.name
holder.type.text = current.type.toString()
holder.prefix.text = current.info.prefix
holder.content.text = current.info.content
if (current.delay > 0)
holder.delay.text = current.delay.toString()
if (current.info.delay > 0)
holder.delay.text = current.info.delay.toString()
else
holder.delay.text = "N/A"
holder.delay.text = ""
if (current.active) {
holder.name.setTextColor(Color.WHITE)
holder.type.setTextColor(Color.WHITE)
if (current.info.active) {
holder.prefix.setTextColor(Color.WHITE)
holder.content.setTextColor(Color.WHITE)
holder.delay.setTextColor(Color.WHITE)
holder.root.setCardBackgroundColor(context.getColor(R.color.primaryCardColorStarted))
} else {
holder.name.setTextColor(colorOnSurface)
holder.type.setTextColor(colorOnSurface)
holder.prefix.setTextColor(colorOnSurface)
holder.content.setTextColor(colorOnSurface)
holder.delay.setTextColor(colorOnSurface)
holder.root.setCardBackgroundColor(colorSurface)
}
if (current.selectable) {
if (current.info.selectable) {
holder.root.setOnClickListener {
root = root.map {
if (it.name == current.group) {
it.copy(current = current.name)
} else {
it
}
}
context.launch {
applyChange()
rootMutex.lock()
val n = withContext(Dispatchers.Default) {
root.map {
if (it.name == current.group) {
it.copy(proxies = it.proxies.map { p ->
p.copy(active = p.name == current.name)
})
} else {
it
}
}
}
rootMutex.unlock()
applyChange(n)
onSelectProxyListener(current.group, current.name)
}
@@ -219,8 +224,8 @@ class GridProxyAdapter(private val context: ProxiesActivity, spanCount: Int = 2)
override fun getItemCount(): Int = renderList.size
override fun getItemViewType(position: Int): Int {
return when (renderList[position]) {
is ProxyGroupInfo -> 1
is ProxyInfo -> 2
is ProxyGroupRenderInfo -> 1
is ProxyRenderInfo -> 2
else -> throw IllegalArgumentException()
}
}
@@ -15,7 +15,8 @@ class UiPreferences(context: Context) {
val PROXY_GROUP_SORT = StringEntry("proxy_group_sort", PROXY_SORT_DEFAULT)
val PROXY_PROXY_SORT = StringEntry("proxy_proxy_sort", PROXY_SORT_DEFAULT)
val LAST_SELECT_GROUP = StringEntry("last_select_group", "")
val PROXY_LAST_SELECT_GROUP = StringEntry("proxy_last_select_group", "")
val PROXY_MERGE_PREFIX = BooleanEntry("proxy_merge_prefix", true)
}
interface Entry<T> {
@@ -34,6 +35,17 @@ class UiPreferences(context: Context) {
}
}
class BooleanEntry(private val key: String, private val defaultValue: Boolean = false):
Entry<Boolean> {
override fun get(sharedPreferences: SharedPreferences): Boolean {
return sharedPreferences.getBoolean(key, defaultValue)
}
override fun put(editor: SharedPreferences.Editor, value: Boolean) {
editor.putBoolean(key, value)
}
}
class Editor(private val editor: SharedPreferences.Editor) {
fun <T, E : Entry<T>> put(e: E, value: T) {
e.put(editor, value)
@@ -0,0 +1,66 @@
package com.github.kr328.clash.utils
import com.github.kr328.clash.core.utils.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
object PrefixMerger {
private val REGEX_PREFIX_TRIM = Regex("[-]*$")
data class Result<T>(val prefix: String, val content: String, val value: T)
suspend fun <T> merge(values: List<T>, transform: (T) -> String): List<Result<T>> =
withContext(Dispatchers.Default) {
val pairs = values.map {
transform(it).trim() to it
}
val groups = mutableListOf<List<Pair<String, T>>>()
var mergingGroup = mutableListOf<Pair<String, T>>()
var currentChar: Char = 0.toChar()
val result = mutableListOf<Result<T>>()
for (pair in pairs) {
if (pair.first[0] == currentChar) {
mergingGroup.add(pair)
} else {
if (mergingGroup.isNotEmpty()) {
groups.add(mergingGroup)
mergingGroup = mutableListOf()
}
currentChar = pair.first[0]
mergingGroup.add(pair)
}
}
if ( mergingGroup.isNotEmpty() )
groups.add(mergingGroup)
for (group in groups) {
var diffIndex = 0
diff@ for (charIndex in group[0].first.indices) {
for (stringIndex in 0 until (group.size - 1)) {
if (group[stringIndex].first[charIndex] != group[stringIndex + 1].first[charIndex])
break@diff
}
diffIndex++
}
group.forEach {
result.add(
Result(
it.first.substring(0, diffIndex)
.replace(REGEX_PREFIX_TRIM, ""),
it.first.substring(diffIndex),
it.second
)
)
}
}
result
}
}
@@ -19,7 +19,7 @@
android:layout_height="wrap_content">
<TextView
android:id="@+id/name"
android:id="@+id/prefix"
android:textColor="?attr/colorOnSurface"
android:singleLine="true"
android:layout_width="wrap_content"
@@ -28,8 +28,8 @@
android:layout_alignParentStart="true" />
<TextView
android:id="@+id/type"
android:layout_below="@id/name"
android:id="@+id/content"
android:layout_below="@id/prefix"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
+7
View File
@@ -28,4 +28,11 @@
</group>
</menu>
</item>
<item android:title="@string/utils">
<menu>
<group android:checkableBehavior="all">
<item android:id="@+id/utilsMergePrefix" android:title="@string/merge_prefix"/>
</group>
</menu>
</item>
</menu>
+2
View File
@@ -84,6 +84,8 @@
<string name="sort_group">Sort Group</string>
<string name="sort_proxy">Sort Proxy</string>
<string name="default_">Default</string>
<string name="utils">Utils</string>
<string name="merge_prefix">Merge Prefix</string>
<string name="start_url_provider_failure">Start URL Provider Failure</string>