1.1.1
This commit is contained in:
10 files changed
+263
-165
No files matched your search
@@ -9,30 +9,21 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.adapter.ProxyAdapter
|
||||
import com.github.kr328.clash.adapter.ProxyChipAdapter
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.Proxy
|
||||
import com.github.kr328.clash.core.utils.Log
|
||||
import com.github.kr328.clash.pipeline.Pipeline
|
||||
import com.github.kr328.clash.pipeline.mergePrefix
|
||||
import com.github.kr328.clash.pipeline.sort
|
||||
import com.github.kr328.clash.pipeline.toAdapterElement
|
||||
import com.github.kr328.clash.preference.UiSettings
|
||||
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.utils.ScrollBinding
|
||||
import kotlinx.android.synthetic.main.activity_proxies.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
private val refreshMutex = Mutex()
|
||||
private val scrollBinding = ScrollBinding(this, this)
|
||||
private val doScrollToLastProxy by lazy {
|
||||
val selected = uiSettings.get(UiSettings.PROXY_LAST_SELECT_GROUP)
|
||||
|
||||
launch {
|
||||
scrollBinding.scrollMaster(selected)
|
||||
}
|
||||
}
|
||||
private var scrollToLast = true
|
||||
|
||||
private val mainListAdapter: ProxyAdapter
|
||||
get() = mainList.adapter as ProxyAdapter
|
||||
@@ -45,35 +36,10 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
setContentView(R.layout.activity_proxies)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
mainList.adapter = ProxyAdapter(this, { group, proxy ->
|
||||
launch {
|
||||
withClash {
|
||||
setSelectProxy(group, proxy)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
launch {
|
||||
urlTesting.add(it)
|
||||
|
||||
withClash {
|
||||
urlTesting.add(it)
|
||||
|
||||
startHealthCheck(it)
|
||||
|
||||
urlTesting.remove(it)
|
||||
|
||||
refreshList()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
mainList.adapter = ProxyAdapter(this, this::setGroupSelected, this::startUrlTesting)
|
||||
mainList.layoutManager = mainListAdapter.layoutManager
|
||||
|
||||
chipList.adapter = ProxyChipAdapter(this) {
|
||||
launch {
|
||||
scrollBinding.scrollMaster(it)
|
||||
}
|
||||
}
|
||||
chipList.adapter = ProxyChipAdapter(this, this::chipClicked)
|
||||
chipList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
|
||||
chipList.itemAnimator?.changeDuration = 0
|
||||
|
||||
@@ -195,7 +161,7 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
override val activityLabel: CharSequence?
|
||||
get() = getText(R.string.proxy)
|
||||
|
||||
override suspend fun onClashStarted() {
|
||||
override suspend fun onClashStopped(reason: String?) {
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -237,6 +203,34 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setGroupSelected(group: String, select: String) {
|
||||
launch {
|
||||
withClash {
|
||||
setSelectProxy(group, select)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startUrlTesting(group: String) {
|
||||
launch {
|
||||
urlTesting.add(group)
|
||||
|
||||
withClash {
|
||||
startHealthCheck(group)
|
||||
}
|
||||
|
||||
urlTesting.remove(group)
|
||||
|
||||
refreshList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun chipClicked(name: String) {
|
||||
launch {
|
||||
scrollBinding.scrollMaster(name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshList(scrollTop: Boolean = false) {
|
||||
launch {
|
||||
if ( !refreshMutex.tryLock() )
|
||||
@@ -249,93 +243,27 @@ class ProxiesActivity : BaseActivity(), ScrollBinding.Callback {
|
||||
queryAllProxyGroups()
|
||||
}
|
||||
|
||||
val prefixDeferred = async {
|
||||
if (uiSettings.get(UiSettings.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 merged = Pipeline(proxies, uiSettings).mergePrefix()
|
||||
val sorted = Pipeline(proxies, uiSettings).sort()
|
||||
|
||||
val sortDeferred = async {
|
||||
val groupSort = when (uiSettings.get(UiSettings.PROXY_GROUP_SORT)) {
|
||||
UiSettings.PROXY_SORT_DEFAULT ->
|
||||
ProxySorter.Order.DEFAULT
|
||||
UiSettings.PROXY_SORT_NAME ->
|
||||
ProxySorter.Order.NAME_INCREASE
|
||||
UiSettings.PROXY_SORT_DELAY ->
|
||||
ProxySorter.Order.DELAY_INCREASE
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val proxySort = when (uiSettings.get(UiSettings.PROXY_PROXY_SORT)) {
|
||||
UiSettings.PROXY_SORT_DEFAULT ->
|
||||
ProxySorter.Order.DEFAULT
|
||||
UiSettings.PROXY_SORT_NAME ->
|
||||
ProxySorter.Order.NAME_INCREASE
|
||||
UiSettings.PROXY_SORT_DELAY ->
|
||||
ProxySorter.Order.DELAY_INCREASE
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val sorter = ProxySorter(groupSort, proxySort)
|
||||
|
||||
sorter.sort(proxies).run {
|
||||
when (general.mode) {
|
||||
General.Mode.GLOBAL -> this
|
||||
General.Mode.DIRECT -> emptyList()
|
||||
General.Mode.RULE -> this.filter { it.name != "GLOBAL" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val prefix = prefixDeferred.await()
|
||||
val sorted = sortDeferred.await()
|
||||
|
||||
val newList = withContext(Dispatchers.Default) {
|
||||
sorted.map {
|
||||
ProxyAdapter.ProxyGroupInfo(it.name,
|
||||
it.proxies.map { p ->
|
||||
val r = prefix.getOrElse(it.name to p.name) {
|
||||
PrefixMerger.Result(p.name, "", p)
|
||||
}
|
||||
|
||||
val data = if ( r.content.isEmpty() ) {
|
||||
r.prefix to p.type.toString()
|
||||
}
|
||||
else {
|
||||
r.content to r.prefix
|
||||
}
|
||||
|
||||
ProxyAdapter.ProxyInfo(
|
||||
p.name,
|
||||
it.name,
|
||||
data.first,
|
||||
data.second,
|
||||
p.delay.toShort(),
|
||||
it.type == Proxy.Type.SELECT,
|
||||
p.name == it.current
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
val newList = sorted.toAdapterElement(merged.input, general)
|
||||
|
||||
mainListAdapter.applyChange(newList, urlTesting)
|
||||
|
||||
(chipList.adapter!! as ProxyChipAdapter).apply {
|
||||
chips = sorted.map { it.name }
|
||||
chips = newList.map { it.name }
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
doScrollToLastProxy
|
||||
|
||||
if ( scrollTop )
|
||||
mainList.smoothScrollToPosition(0)
|
||||
else if ( scrollToLast ) {
|
||||
scrollToLast = false
|
||||
|
||||
val selected = uiSettings.get(UiSettings.PROXY_LAST_SELECT_GROUP)
|
||||
|
||||
scrollBinding.scrollMaster(selected)
|
||||
}
|
||||
|
||||
refreshMutex.unlock()
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.kr328.clash.R
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
@@ -29,14 +28,15 @@ class ProxyAdapter(
|
||||
|
||||
data class ProxyGroupInfo(
|
||||
val name: String,
|
||||
val current: String,
|
||||
val proxies: List<ProxyInfo>
|
||||
)
|
||||
|
||||
data class ProxyInfo(
|
||||
val name: String,
|
||||
val group: String,
|
||||
val prefix: String,
|
||||
val content: String,
|
||||
val title: String,
|
||||
val summary: String,
|
||||
val delay: Short,
|
||||
val selectable: Boolean,
|
||||
val active: Boolean
|
||||
@@ -62,8 +62,6 @@ class ProxyAdapter(
|
||||
get() = info.group
|
||||
}
|
||||
|
||||
|
||||
private var rootMutex = Mutex()
|
||||
private var urlTesting: Set<String> = emptySet()
|
||||
private var renderList = mutableListOf<RenderInfo>()
|
||||
private var activeList: MutableMap<String, Int> = mutableMapOf()
|
||||
@@ -98,7 +96,7 @@ class ProxyAdapter(
|
||||
private var root = listOf<ProxyGroupInfo>()
|
||||
|
||||
private class ProxyGroupHeader(view: View) : RecyclerView.ViewHolder(view) {
|
||||
val name: TextView = view.findViewById(R.id.name)
|
||||
val title: TextView = view.findViewById(R.id.name)
|
||||
val urlTest: View = view.findViewById(R.id.urlTest)
|
||||
val urlTestProgress: View = view.findViewById(R.id.urlTestProgress)
|
||||
}
|
||||
@@ -112,8 +110,6 @@ class ProxyAdapter(
|
||||
|
||||
suspend fun applyChange(newList: List<ProxyGroupInfo>, testing: Set<String>) =
|
||||
withContext(Dispatchers.Default) {
|
||||
rootMutex.lock()
|
||||
|
||||
val newRenderList = newList
|
||||
.flatMap {
|
||||
listOf(ProxyGroupRenderInfo(it)) + it.proxies.map { p -> ProxyRenderInfo(p) }
|
||||
@@ -155,8 +151,6 @@ class ProxyAdapter(
|
||||
activeList = activeCache
|
||||
result.dispatchUpdatesTo(this@ProxyAdapter)
|
||||
}
|
||||
|
||||
rootMutex.unlock()
|
||||
}
|
||||
|
||||
fun getGroupPosition(name: String): Int {
|
||||
@@ -195,7 +189,8 @@ class ProxyAdapter(
|
||||
|
||||
groupPosition[current.name] = position
|
||||
|
||||
holder.name.text = current.info.name
|
||||
holder.title.text = context.getString(R.string.format_proxy_group_title,
|
||||
current.info.name, current.info.current)
|
||||
holder.urlTest.setOnClickListener {
|
||||
holder.urlTest.visibility = View.GONE
|
||||
holder.urlTestProgress.visibility = View.VISIBLE
|
||||
@@ -214,8 +209,8 @@ class ProxyAdapter(
|
||||
is ProxyItem -> {
|
||||
val current = renderList[position] as ProxyRenderInfo
|
||||
|
||||
holder.prefix.text = current.info.prefix
|
||||
holder.content.text = current.info.content
|
||||
holder.prefix.text = current.info.title
|
||||
holder.content.text = current.info.summary
|
||||
|
||||
if (current.info.delay > 0)
|
||||
holder.delay.text = current.info.delay.toString()
|
||||
@@ -239,14 +234,18 @@ class ProxyAdapter(
|
||||
if (current.info.selectable) {
|
||||
holder.root.setOnClickListener {
|
||||
val oldPosition = activeList[current.group] ?: return@setOnClickListener
|
||||
val groupPosition = groupPosition[current.group] ?: return@setOnClickListener
|
||||
val old = renderList[oldPosition] as ProxyRenderInfo
|
||||
val new = renderList[position] as ProxyRenderInfo
|
||||
val group = renderList[groupPosition] as ProxyGroupRenderInfo
|
||||
|
||||
renderList[oldPosition] = old.copy(info = old.info.copy(active = false))
|
||||
renderList[position] = new.copy(info = new.info.copy(active = true))
|
||||
renderList[groupPosition] = group.copy(info = group.info.copy(current = current.name))
|
||||
|
||||
notifyItemChanged(oldPosition)
|
||||
notifyItemChanged(position)
|
||||
notifyItemChanged(groupPosition)
|
||||
|
||||
onSelect(current.group, current.name)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.github.kr328.clash.pipeline
|
||||
|
||||
import com.github.kr328.clash.service.settings.BaseSettings
|
||||
|
||||
data class Pipeline<T>(val input: T, val settings: BaseSettings)
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.github.kr328.clash.pipeline
|
||||
|
||||
import com.github.kr328.clash.adapter.ProxyAdapter
|
||||
import com.github.kr328.clash.core.model.General
|
||||
import com.github.kr328.clash.core.model.Proxy
|
||||
import com.github.kr328.clash.core.model.ProxyGroup
|
||||
import com.github.kr328.clash.preference.UiSettings
|
||||
import com.github.kr328.clash.utils.PrefixMerger
|
||||
import com.github.kr328.clash.utils.ProxySorter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
data class ProxyEntry(val group: String, val name: String)
|
||||
data class ProxyMerged(val prefix: String, val content: String)
|
||||
|
||||
suspend fun Pipeline<List<ProxyGroup>>.mergePrefix(): Pipeline<Map<ProxyEntry, ProxyMerged>> {
|
||||
if ( !settings.get(UiSettings.PROXY_MERGE_PREFIX) )
|
||||
return Pipeline(emptyMap(), settings)
|
||||
|
||||
val result = coroutineScope {
|
||||
input
|
||||
.map {
|
||||
async {
|
||||
it.name to PrefixMerger.merge(it.proxies, Proxy::name)
|
||||
}
|
||||
}
|
||||
.map {
|
||||
it.await()
|
||||
}
|
||||
.flatMap {
|
||||
it.second.map { merged ->
|
||||
ProxyEntry(it.first, merged.value.name) to ProxyMerged(merged.prefix, merged.content)
|
||||
}
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
return Pipeline(result, settings)
|
||||
}
|
||||
|
||||
suspend fun Pipeline<List<ProxyGroup>>.sort(): Pipeline<List<ProxyGroup>> {
|
||||
val groupSort = when (settings.get(UiSettings.PROXY_GROUP_SORT)) {
|
||||
UiSettings.PROXY_SORT_DEFAULT ->
|
||||
ProxySorter.Order.DEFAULT
|
||||
UiSettings.PROXY_SORT_NAME ->
|
||||
ProxySorter.Order.NAME_INCREASE
|
||||
UiSettings.PROXY_SORT_DELAY ->
|
||||
ProxySorter.Order.DELAY_INCREASE
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val proxySort = when (settings.get(UiSettings.PROXY_PROXY_SORT)) {
|
||||
UiSettings.PROXY_SORT_DEFAULT ->
|
||||
ProxySorter.Order.DEFAULT
|
||||
UiSettings.PROXY_SORT_NAME ->
|
||||
ProxySorter.Order.NAME_INCREASE
|
||||
UiSettings.PROXY_SORT_DELAY ->
|
||||
ProxySorter.Order.DELAY_INCREASE
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
val sorter = ProxySorter(groupSort, proxySort)
|
||||
|
||||
return copy(input = sorter.sort(input))
|
||||
}
|
||||
|
||||
suspend fun Pipeline<List<ProxyGroup>>.toAdapterElement(prefixMerged: Map<ProxyEntry, ProxyMerged>, general: General):
|
||||
List<ProxyAdapter.ProxyGroupInfo> {
|
||||
return input.map { group ->
|
||||
val proxies = group.proxies.map { proxy ->
|
||||
val merged = prefixMerged[ProxyEntry(group.name, proxy.name)]?.takeIf {
|
||||
it.prefix.isNotBlank() && it.content.isNotBlank()
|
||||
} ?: ProxyMerged(proxy.type.toString(), proxy.name)
|
||||
|
||||
ProxyAdapter.ProxyInfo(proxy.name, group.name, merged.content, merged.prefix,
|
||||
proxy.delay.toShort(), group.type == Proxy.Type.SELECT,
|
||||
group.current == proxy.name)
|
||||
}
|
||||
|
||||
|
||||
ProxyAdapter.ProxyGroupInfo(group.name, group.current, proxies)
|
||||
}.let {
|
||||
withContext(Dispatchers.Default) {
|
||||
when ( general.mode ) {
|
||||
General.Mode.DIRECT -> emptyList()
|
||||
General.Mode.GLOBAL -> it
|
||||
General.Mode.RULE -> it.filter { it.name != "GLOBAL"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,19 @@ object PrefixMerger {
|
||||
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
|
||||
transform(it).trim().toCodePointList() to it
|
||||
}
|
||||
|
||||
val groups = mutableListOf<List<Pair<String, T>>>()
|
||||
var mergingGroup = mutableListOf<Pair<String, T>>()
|
||||
val groups = mutableListOf<List<Pair<List<Int>, T>>>()
|
||||
var mergingGroup = mutableListOf<Pair<List<Int>, T>>()
|
||||
var currentCodePoint = 0
|
||||
val result = mutableListOf<Result<T>>()
|
||||
|
||||
for (pair in pairs) {
|
||||
if (pair.first.codePointAt(0) == currentCodePoint) {
|
||||
if ( pair.first.isEmpty() )
|
||||
continue
|
||||
|
||||
if (pair.first[0] == currentCodePoint) {
|
||||
mergingGroup.add(pair)
|
||||
} else {
|
||||
if (mergingGroup.isNotEmpty()) {
|
||||
@@ -28,7 +31,7 @@ object PrefixMerger {
|
||||
mergingGroup = mutableListOf()
|
||||
}
|
||||
|
||||
currentCodePoint = pair.first.codePointAt(0)
|
||||
currentCodePoint = pair.first[0]
|
||||
mergingGroup.add(pair)
|
||||
}
|
||||
}
|
||||
@@ -38,7 +41,7 @@ object PrefixMerger {
|
||||
|
||||
for (group in groups) {
|
||||
var diffIndex = 0
|
||||
val size = group.map { it.first.length }.min() ?: 0
|
||||
val size = group.map { it.first.size }.min() ?: 0
|
||||
|
||||
diff@ for (charIndex in 0 until size) {
|
||||
for (stringIndex in 0 until (group.size - 1)) {
|
||||
@@ -50,11 +53,11 @@ object PrefixMerger {
|
||||
}
|
||||
|
||||
group.forEach {
|
||||
val prefix = it.first.substring(0, diffIndex)
|
||||
.replace(REGEX_PREFIX_TRIM, "")
|
||||
val content = it.first.substring(diffIndex)
|
||||
val prefix = it.first.subList(0, diffIndex)
|
||||
val content = it.first.subList(diffIndex, it.first.size)
|
||||
|
||||
result.add(Result(prefix, content, it.second))
|
||||
result.add(Result(prefix.asCodePointString().replace(REGEX_PREFIX_TRIM, ""),
|
||||
content.asCodePointString(), it.second))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.github.kr328.clash.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class QuickSmoothScroller(context: Context, target: Int):
|
||||
LinearSmoothScroller(context) {
|
||||
companion object {
|
||||
const val MAX_OFFSET = 2
|
||||
}
|
||||
|
||||
var started = {}
|
||||
var stopped = {}
|
||||
|
||||
init {
|
||||
targetPosition = target
|
||||
}
|
||||
|
||||
override fun getVerticalSnapPreference(): Int {
|
||||
return SNAP_TO_START
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
stopped()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
started()
|
||||
}
|
||||
|
||||
override fun onSeekTargetStep(dx: Int, dy: Int, state: RecyclerView.State, action: Action) {
|
||||
when (val lm = layoutManager) {
|
||||
is LinearLayoutManager -> {
|
||||
val current = lm.findFirstCompletelyVisibleItemPosition()
|
||||
|
||||
if (targetPosition > current && targetPosition - current > MAX_OFFSET)
|
||||
action.jumpTo(targetPosition - MAX_OFFSET)
|
||||
else if (current > targetPosition && current - targetPosition > MAX_OFFSET)
|
||||
action.jumpTo(targetPosition + MAX_OFFSET)
|
||||
}
|
||||
is GridLayoutManager -> {
|
||||
val current = lm.findFirstCompletelyVisibleItemPosition()
|
||||
|
||||
if (targetPosition > current && targetPosition - current > MAX_OFFSET)
|
||||
action.jumpTo(targetPosition - MAX_OFFSET)
|
||||
else if (current > targetPosition && current - targetPosition > MAX_OFFSET)
|
||||
action.jumpTo(targetPosition + MAX_OFFSET)
|
||||
}
|
||||
}
|
||||
|
||||
super.onSeekTargetStep(dx, dy, state, action)
|
||||
}
|
||||
}
|
||||
@@ -30,27 +30,7 @@ class ScrollBinding(
|
||||
if (position < 0)
|
||||
return
|
||||
|
||||
val scroller = (object : LinearSmoothScroller(context) {
|
||||
override fun getVerticalSnapPreference(): Int {
|
||||
return SNAP_TO_START
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
preventSlaveScroll = false
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
preventSlaveScroll = true
|
||||
}
|
||||
|
||||
init {
|
||||
targetPosition = position
|
||||
}
|
||||
})
|
||||
val scroller = QuickSmoothScroller(context, position)
|
||||
|
||||
callback.doMasterScroll(scroller, position)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.github.kr328.clash.utils
|
||||
|
||||
import java.lang.StringBuilder
|
||||
|
||||
fun String.toCodePointList(): List<Int> {
|
||||
var offset = 0
|
||||
val result = mutableListOf<Int>()
|
||||
|
||||
while ( offset < length ) {
|
||||
val codePoint = codePointAt(offset)
|
||||
result.add(codePoint)
|
||||
|
||||
offset += Character.charCount(codePoint)
|
||||
}
|
||||
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
fun List<Int>.asCodePointString(): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
forEach {
|
||||
sb.appendCodePoint(it)
|
||||
}
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
@@ -170,4 +170,6 @@
|
||||
<string name="about_split_apks_url" translatable="false">https://developer.android.com/platform/technology/app-bundle</string>
|
||||
<string name="google_play_url" translatable="false">https://play.google.com/store/apps/details?id=com.github.kr328.clash</string>
|
||||
<string name="github_releases_url" translatable="false">https://github.com/Kr328/ClashForAndroid/releases</string>
|
||||
|
||||
<string name="format_proxy_group_title" translatable="false">%s - %s</string>
|
||||
</resources>
|
||||
@@ -89,9 +89,9 @@ class TunService : VpnService(), CoroutineScope by MainScope() {
|
||||
val dnsStrings = dnsServers.map {
|
||||
when ( it ) {
|
||||
is Inet6Address ->
|
||||
"[${it.hostName}]:53"
|
||||
"[${it.hostAddress}]:53"
|
||||
else ->
|
||||
"${it.hostName}:53"
|
||||
"${it.hostAddress}:53"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user