add prefix merger
This commit is contained in:
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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in new issue
Block a user