From 5d4b6b4fb550215c5ccd6a7b7ecae27067bc29d8 Mon Sep 17 00:00:00 2001 From: Kr328 <39107975+Kr328@users.noreply.github.com> Date: Sun, 16 Feb 2020 15:54:22 +0800 Subject: [PATCH] add prefix merger --- .../com/github/kr328/clash/ProxiesActivity.kt | 61 ++++++- .../clash/adapter/AbstractProxyAdapter.kt | 19 ++- .../kr328/clash/adapter/GridProxyAdapter.kt | 151 +++++++++--------- .../kr328/clash/preference/UiPreferences.kt | 14 +- .../github/kr328/clash/utils/PrefixMerger.kt | 66 ++++++++ .../main/res/layout/adapter_grid_proxy.xml | 6 +- app/src/main/res/menu/proxies.xml | 7 + app/src/main/res/values/strings.xml | 2 + 8 files changed, 238 insertions(+), 88 deletions(-) create mode 100644 app/src/main/java/com/github/kr328/clash/utils/PrefixMerger.kt diff --git a/app/src/main/java/com/github/kr328/clash/ProxiesActivity.kt b/app/src/main/java/com/github/kr328/clash/ProxiesActivity.kt index dd0cf84..c069564 100644 --- a/app/src/main/java/com/github/kr328/clash/ProxiesActivity.kt +++ b/app/src/main/java/com/github/kr328/clash/ProxiesActivity.kt @@ -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() diff --git a/app/src/main/java/com/github/kr328/clash/adapter/AbstractProxyAdapter.kt b/app/src/main/java/com/github/kr328/clash/adapter/AbstractProxyAdapter.kt index 06c980a..1232d20 100644 --- a/app/src/main/java/com/github/kr328/clash/adapter/AbstractProxyAdapter.kt +++ b/app/src/main/java/com/github/kr328/clash/adapter/AbstractProxyAdapter.kt @@ -1,12 +1,23 @@ package com.github.kr328.clash.adapter -import com.github.kr328.clash.core.model.ProxyGroup - interface AbstractProxyAdapter { - var root: List + data class ProxyGroupInfo( + val name: String, + val proxies: List + ) + 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) suspend fun getGroupPosition(name: String): Int? suspend fun getCurrentGroup(): String } \ No newline at end of file diff --git a/app/src/main/java/com/github/kr328/clash/adapter/GridProxyAdapter.kt b/app/src/main/java/com/github/kr328/clash/adapter/GridProxyAdapter.kt index 465b482..cf9aeed 100644 --- a/app/src/main/java/com/github/kr328/clash/adapter/GridProxyAdapter.kt +++ b/app/src/main/java/com/github/kr328/clash/adapter/GridProxyAdapter.kt @@ -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() @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() + private var root = listOf() 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) = + 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() } } diff --git a/app/src/main/java/com/github/kr328/clash/preference/UiPreferences.kt b/app/src/main/java/com/github/kr328/clash/preference/UiPreferences.kt index a894e34..316a8c2 100644 --- a/app/src/main/java/com/github/kr328/clash/preference/UiPreferences.kt +++ b/app/src/main/java/com/github/kr328/clash/preference/UiPreferences.kt @@ -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 { @@ -34,6 +35,17 @@ class UiPreferences(context: Context) { } } + class BooleanEntry(private val key: String, private val defaultValue: Boolean = false): + Entry { + 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 > put(e: E, value: T) { e.put(editor, value) diff --git a/app/src/main/java/com/github/kr328/clash/utils/PrefixMerger.kt b/app/src/main/java/com/github/kr328/clash/utils/PrefixMerger.kt new file mode 100644 index 0000000..60d92fa --- /dev/null +++ b/app/src/main/java/com/github/kr328/clash/utils/PrefixMerger.kt @@ -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(val prefix: String, val content: String, val value: T) + + suspend fun merge(values: List, transform: (T) -> String): List> = + withContext(Dispatchers.Default) { + val pairs = values.map { + transform(it).trim() to it + } + + val groups = mutableListOf>>() + var mergingGroup = mutableListOf>() + var currentChar: Char = 0.toChar() + val result = mutableListOf>() + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_grid_proxy.xml b/app/src/main/res/layout/adapter_grid_proxy.xml index 4323479..9567198 100644 --- a/app/src/main/res/layout/adapter_grid_proxy.xml +++ b/app/src/main/res/layout/adapter_grid_proxy.xml @@ -19,7 +19,7 @@ android:layout_height="wrap_content"> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be9f96d..31c4f93 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -84,6 +84,8 @@ Sort Group Sort Proxy Default + Utils + Merge Prefix Start URL Provider Failure