diff --git a/app/src/main/java/com/github/kr328/clash/ProxyActivity.kt b/app/src/main/java/com/github/kr328/clash/ProxyActivity.kt index 7e9d595..6a151e3 100644 --- a/app/src/main/java/com/github/kr328/clash/ProxyActivity.kt +++ b/app/src/main/java/com/github/kr328/clash/ProxyActivity.kt @@ -1,11 +1,9 @@ package com.github.kr328.clash import android.os.Bundle -import android.view.View -import androidx.recyclerview.widget.LinearLayoutManager -import com.github.kr328.clash.adapter.ProxyGroupAdapter +import com.github.kr328.clash.adapter.ProxyAdapter import com.github.kr328.clash.core.model.ProxyPacket -import com.github.kr328.clash.model.ListProxyGroup +import com.github.kr328.clash.model.ListProxy import kotlinx.android.synthetic.main.activity_proxies.* class ProxyActivity : BaseActivity() { @@ -16,9 +14,8 @@ class ProxyActivity : BaseActivity() { setSupportActionBar(activity_proxies_toolbar) activity_proxies_list.also { - it.layoutManager = LinearLayoutManager(this) - it.adapter = ProxyGroupAdapter(this) - it.visibility = View.GONE + it.adapter = ProxyAdapter(this) + it.layoutManager = (it.adapter!! as ProxyAdapter).getLayoutManager() } activity_proxies_swipe.setOnRefreshListener { @@ -35,14 +32,6 @@ class ProxyActivity : BaseActivity() { runClash { clash -> val packet = clash.queryAllProxies() val proxies = packet.proxies - val adapterProxies = proxies - .mapValues { - ListProxyGroup.ListProxy( - it.value.name, - it.value.type.toString(), - it.value.delay - ) - } val listData = proxies .asSequence() @@ -69,25 +58,63 @@ class ProxyActivity : BaseActivity() { it.value.name }) ) - .map { - ListProxyGroup( - it.value.name, - it.value.type, - it.value.all.mapNotNull { hash -> - adapterProxies[hash] - }, - adapterProxies[it.value.now] - ) + .flatMap { + val header = ListProxy.ListProxyHeader(it.value.name, it.value.type, it.value.now) + + sequenceOf(header) + + it.value.all + .mapNotNull { + proxies[it] + } + .map { item -> + ListProxy.ListProxyItem( + item.name, + item.type.toString(), + item.delay, header + ) + } + .asSequence() + } + .mapIndexed { index, listProxy -> + if ( listProxy is ListProxy.ListProxyItem ) { + if ( listProxy.name.hashCode() == listProxy.header.now ) + listProxy.header.now = index + } + listProxy } .toList() - runOnUiThread { - (activity_proxies_list.adapter!! as ProxyGroupAdapter).apply { - data = listData - notifyDataSetChanged() + val listDataOldChanged = (activity_proxies_list.adapter!! as ProxyAdapter) + .elements + .filterIsInstance(ListProxy.ListProxyHeader::class.java) + .map { it.now } + + val listDataChanged = listData.filterIsInstance() + .map { it.now } + + val changed = if ( listDataChanged.size != listDataChanged.size ) + (0..listData.size).toList() + else { + listDataChanged.mapIndexed { index, i -> + if ( listDataOldChanged[index] != i ) + -1 + else + index + }.filterNot { + it < 0 } - activity_proxies_list.visibility = View.VISIBLE + } + + runOnUiThread { activity_proxies_swipe.isRefreshing = false + + (activity_proxies_list.adapter!! as ProxyAdapter).apply { + elements = listData + + changed.forEach { + notifyItemChanged(it) + } + } } } } diff --git a/app/src/main/java/com/github/kr328/clash/adapter/ProxyAdapter.kt b/app/src/main/java/com/github/kr328/clash/adapter/ProxyAdapter.kt index 395f3f0..6294ace 100644 --- a/app/src/main/java/com/github/kr328/clash/adapter/ProxyAdapter.kt +++ b/app/src/main/java/com/github/kr328/clash/adapter/ProxyAdapter.kt @@ -2,64 +2,121 @@ package com.github.kr328.clash.adapter import android.content.Context import android.graphics.Color +import android.graphics.drawable.GradientDrawable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.github.kr328.clash.R -import com.github.kr328.clash.model.ListProxyGroup +import com.github.kr328.clash.core.model.ProxyPacket +import com.github.kr328.clash.model.ListProxy import com.google.android.material.card.MaterialCardView +import java.lang.IllegalArgumentException -class ProxyAdapter(private val context: Context) : RecyclerView.Adapter() { - var proxies: List = emptyList() - var now: ListProxyGroup.ListProxy? = null +class ProxyAdapter(private val context: Context) : RecyclerView.Adapter() { + var elements: List = emptyList() var clickable: Boolean = false - class Holder(view: View) : RecyclerView.ViewHolder(view) { - val name: TextView = view.findViewById(R.id.adapter_proxy_name) - val type: TextView = view.findViewById(R.id.adapter_proxy_type) - val delay: TextView = view.findViewById(R.id.adapter_proxy_delay) - val card: MaterialCardView = view.findViewById(R.id.adapter_proxy_card) + class HeaderHolder(view: View): RecyclerView.ViewHolder(view) { + val name: TextView = view.findViewById(R.id.adapter_proxy_header_name) + val test: View = view.findViewById(R.id.adapter_proxy_header_url_test) } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { - return Holder(LayoutInflater.from(context).inflate(R.layout.adapter_proxy, parent, false)) + class ItemHolder(view: View) : RecyclerView.ViewHolder(view) { + val name: TextView = view.findViewById(R.id.adapter_proxy_item_name) + val type: TextView = view.findViewById(R.id.adapter_proxy_item_type) + val delay: TextView = view.findViewById(R.id.adapter_proxy_item_delay) + val card: MaterialCardView = view.findViewById(R.id.adapter_proxy_item_card) + } + + override fun getItemViewType(position: Int): Int { + return when ( elements[position] ) { + is ListProxy.ListProxyItem -> 1 + is ListProxy.ListProxyHeader -> 2 + else -> throw IllegalArgumentException("Invalid type") + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + 1 -> ItemHolder(LayoutInflater.from(context).inflate(R.layout.adapter_proxy_item, parent, false)) + 2 -> HeaderHolder(LayoutInflater.from(context).inflate(R.layout.adapter_proxy_header, parent, false)) + else -> throw IllegalArgumentException("Invalid type") + } } override fun getItemCount(): Int { - return proxies.size + return elements.size } - override fun onBindViewHolder(holder: Holder, position: Int) { - val current = proxies[position] + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (val current = elements[position]) { + is ListProxy.ListProxyItem -> bindItemView(holder as ItemHolder, current, position) + is ListProxy.ListProxyHeader -> bindHeaderView(holder as HeaderHolder, current) + } + } - if (current === now) { + fun getLayoutManager(): GridLayoutManager { + return GridLayoutManager(context, 2).apply { + spanSizeLookup = object: GridLayoutManager.SpanSizeLookup() { + override fun getSpanSize(position: Int): Int { + return when ( elements[position] ) { + is ListProxy.ListProxyHeader -> 2 + is ListProxy.ListProxyItem -> 1 + else -> throw IllegalArgumentException("Invalid type") + } + } + } + } + } + + private fun bindItemView(holder: ItemHolder, current: ListProxy.ListProxyItem, position: Int) { + if ( position == current.header.now ) { holder.card.setCardBackgroundColor(context.getColor(R.color.colorAccent)) holder.name.setTextColor(Color.WHITE) holder.type.setTextColor(Color.WHITE - 0x22222222) holder.delay.setTextColor(Color.WHITE - 0x11111111) - } else { + } + else { holder.card.setCardBackgroundColor(Color.WHITE) holder.name.setTextColor(Color.BLACK) holder.type.setTextColor(Color.LTGRAY) holder.delay.setTextColor(Color.DKGRAY) } - holder.card.isFocusable = clickable - holder.card.isClickable = clickable + if ( current.header.type == ProxyPacket.Type.SELECT ) { + holder.card.isFocusable = true + holder.card.isClickable = true + holder.card.setOnClickListener { + notifyItemChanged(current.header.now) + notifyItemChanged(position) + current.header.now = position + } + } + else { + holder.card.setOnClickListener(null) + holder.card.isFocusable = false + holder.card.isClickable = false + } holder.name.text = current.name holder.type.text = current.type - holder.delay.text = if (current.delay > 0) current.delay.toString() else "" - - if (clickable) { - holder.card.setOnClickListener { - notifyItemChanged(proxies.indexOf(now)) - notifyItemChanged(position) - - now = current + holder.delay.text = + if (current.delay > 0) { + current.delay.toString() + } else { + if ( current.header.type != ProxyPacket.Type.SELECT ) + "N/A" + else + "" } - } + + } + + private fun bindHeaderView(holder: HeaderHolder, current: ListProxy.ListProxyHeader) { + holder.name.text = current.name + holder.test.visibility = if ( current.type == ProxyPacket.Type.SELECT ) View.VISIBLE else View.GONE } } \ No newline at end of file diff --git a/app/src/main/java/com/github/kr328/clash/adapter/ProxyGroupAdapter.kt b/app/src/main/java/com/github/kr328/clash/adapter/ProxyGroupAdapter.kt deleted file mode 100644 index 420266f..0000000 --- a/app/src/main/java/com/github/kr328/clash/adapter/ProxyGroupAdapter.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.github.kr328.clash.adapter - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.github.kr328.clash.R -import com.github.kr328.clash.core.model.ProxyPacket -import com.github.kr328.clash.model.ListProxyGroup - -class ProxyGroupAdapter(private val context: Context) : - RecyclerView.Adapter() { - var data: List = emptyList() - - class Holder(view: View) : RecyclerView.ViewHolder(view) { - val name: TextView = view.findViewById(R.id.adapter_proxy_group_name) - val list: RecyclerView = view.findViewById(R.id.adapter_proxy_group_list) - val test: View = view.findViewById(R.id.adapter_proxy_group_url_test) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { - return Holder( - LayoutInflater.from(context) - .inflate(R.layout.adapter_proxy_group, parent, false) - ).apply { - list.layoutManager = object : GridLayoutManager(context, 2) { - override fun canScrollHorizontally(): Boolean = false - override fun canScrollVertically(): Boolean = false - } - list.adapter = ProxyAdapter(context) - } - } - - override fun getItemCount(): Int { - return data.size - } - - override fun onBindViewHolder(holder: Holder, position: Int) { - val current = data[position] - - holder.test.visibility = - if (current.type == ProxyPacket.Type.SELECT) View.VISIBLE else View.GONE - holder.name.text = current.name - - (holder.list.adapter as ProxyAdapter).apply { - proxies = current.proxies - now = current.now - clickable = current.type == ProxyPacket.Type.SELECT - - notifyDataSetChanged() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/github/kr328/clash/model/ListProxy.kt b/app/src/main/java/com/github/kr328/clash/model/ListProxy.kt new file mode 100644 index 0000000..1c98f9e --- /dev/null +++ b/app/src/main/java/com/github/kr328/clash/model/ListProxy.kt @@ -0,0 +1,8 @@ +package com.github.kr328.clash.model + +import com.github.kr328.clash.core.model.ProxyPacket + +interface ListProxy { + data class ListProxyHeader(val name: String, val type: ProxyPacket.Type, var now: Int): ListProxy + data class ListProxyItem(val name: String, val type: String, val delay: Long, val header: ListProxyHeader): ListProxy +} \ No newline at end of file diff --git a/app/src/main/java/com/github/kr328/clash/model/ListProxyGroup.kt b/app/src/main/java/com/github/kr328/clash/model/ListProxyGroup.kt deleted file mode 100644 index 6a6a635..0000000 --- a/app/src/main/java/com/github/kr328/clash/model/ListProxyGroup.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.kr328.clash.model - -import com.github.kr328.clash.core.model.ProxyPacket - -data class ListProxyGroup( - val name: String, - val type: ProxyPacket.Type, - val proxies: List, - var now: ListProxy? -) { - data class ListProxy(val name: String, val type: String, val delay: Long) -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_proxies.xml b/app/src/main/res/layout/activity_proxies.xml index f9dc8cb..d1c73f1 100644 --- a/app/src/main/res/layout/activity_proxies.xml +++ b/app/src/main/res/layout/activity_proxies.xml @@ -17,6 +17,8 @@ - - - diff --git a/app/src/main/res/layout/adapter_proxy.xml b/app/src/main/res/layout/adapter_proxy_item.xml similarity index 82% rename from app/src/main/res/layout/adapter_proxy.xml rename to app/src/main/res/layout/adapter_proxy_item.xml index 6c5fcb7..45aa63b 100644 --- a/app/src/main/res/layout/adapter_proxy.xml +++ b/app/src/main/res/layout/adapter_proxy_item.xml @@ -5,7 +5,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto">