[WIP] UI refactor

This commit is contained in:
Kr328 committed 2020-02-15 01:03:23 +08:00
1 parent 88e0425736
commit 01d9d231b5
15 files changed
+227 -75

No files matched your search

@@ -1,16 +1,19 @@
package com.github.kr328.clash
import android.os.Bundle
import androidx.core.view.isEmpty
import android.util.DisplayMetrics
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import com.github.kr328.clash.adapter.AbstractProxyAdapter
import com.github.kr328.clash.adapter.GridProxyAdapter
import com.github.kr328.clash.core.model.ProxyGroup
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.adapter.ProxyChipAdapter
import com.github.kr328.clash.remote.withClash
import com.github.kr328.clash.utils.ProxySorter
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.activity_proxies.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -18,6 +21,7 @@ class ProxiesActivity : BaseActivity() {
private val activity: ProxiesActivity
get() = this
private var maskedGroup: MutableSet<String> = mutableSetOf()
private val updateChipChannel = Channel<Unit>(Channel.CONFLATED)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -34,6 +38,58 @@ class ProxiesActivity : BaseActivity() {
}
}
}
mainList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
updateChipChannel.offer(Unit)
}
})
chipList.adapter = ProxyChipAdapter(this) {
launch {
(mainList.adapter!! as AbstractProxyAdapter).getGroupPosition(it)?.also {
val scroller = (object : LinearSmoothScroller(activity) {
override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
return 40f / displayMetrics!!.densityDpi
}
init {
targetPosition = it
}
})
mainList.layoutManager?.startSmoothScroll(scroller)
}
}
}
chipList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
chipList.itemAnimator?.changeDuration = 80
launch {
var currentChecked = ""
while (isActive) {
updateChipChannel.receive()
val currentGroup = (mainList.adapter!! as AbstractProxyAdapter).getCurrentGroup()
if (currentChecked == currentGroup)
continue
currentChecked = currentGroup
(chipList.adapter!! as ProxyChipAdapter).apply {
selected = currentChecked
(chipList.layoutManager!! as LinearLayoutManager)
.scrollToPositionWithOffset(chips.indexOf(currentChecked), 0)
}
}
}
}
override fun onStart() {
@@ -58,45 +114,13 @@ class ProxiesActivity : BaseActivity() {
sorter.sort(proxies.toList())
}
if (chipGroup.isEmpty()) {
filtered.map(ProxyGroup::name).forEach {
val chip = Chip(activity).apply {
text = it
chipBackgroundColor = getColorStateList(R.color.proxies_chip_colors)
rippleColor = getColorStateList(R.color.proxies_chip_colors)
setTextColor(getColorStateList(R.color.proxies_chip_text_colors))
checkedIcon = null
isCheckable = true
isClickable = true
isFocusable = true
isChecked = !maskedGroup.contains(it)
setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
maskedGroup.remove(it)
} else {
maskedGroup.add(it)
}
refreshList(it)
}
}
chipGroup.addView(chip)
}
}
(mainList.adapter!! as AbstractProxyAdapter).apply {
root = filtered.filter { !maskedGroup.contains(it.name) }
applyChange()
if (scrollTo != null) {
scrollToGroup(scrollTo)
}
}
(chipList.adapter!! as ProxyChipAdapter).chips = filtered.map { it.name }
}
}
}
@@ -7,5 +7,6 @@ interface AbstractProxyAdapter {
var onSelectProxyListener: suspend (String, String) -> Unit
suspend fun applyChange()
suspend fun scrollToGroup(name: String)
suspend fun getGroupPosition(name: String): Int?
suspend fun getCurrentGroup(): String
}
@@ -14,7 +14,6 @@ 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
@@ -25,13 +24,18 @@ class GridProxyAdapter(private val context: ProxiesActivity, spanCount: Int = 2)
RecyclerView.Adapter<RecyclerView.ViewHolder>(), AbstractProxyAdapter {
private interface RenderInfo {
val name: String
val group: String
}
private data class ProxyGroupInfo(override val name: String) : RenderInfo {
override val group: String
get() = name
}
private data class ProxyGroupInfo(override val name: String) : RenderInfo
private data class ProxyInfo(
override val name: String,
val type: Proxy.Type,
val group: String,
override val group: String,
val selectable: Boolean,
val delay: Short,
val active: Boolean
@@ -115,17 +119,24 @@ class GridProxyAdapter(private val context: ProxiesActivity, spanCount: Int = 2)
}
}
override suspend fun scrollToGroup(name: String) {
val position = withContext(Dispatchers.Default) {
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 ProxyGroupInfo && p.name == name)
index
else
-1
}.singleOrNull { it >= 0 }
} ?: return
}
}
layoutManager.scrollToPositionWithOffset(position, 0)
override suspend fun getCurrentGroup(): String {
val position = layoutManager.findFirstCompletelyVisibleItemPosition()
if (position < 0)
return ""
return renderList[position].group
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@@ -0,0 +1,75 @@
package com.github.kr328.clash.adapter
import android.content.Context
import android.graphics.Color
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import com.github.kr328.clash.R
import com.google.android.material.card.MaterialCardView
class ProxyChipAdapter(
private val context: Context,
private val onClick: (String) -> Unit
) :
RecyclerView.Adapter<ProxyChipAdapter.Holder>() {
var chips = listOf<String>()
var selected: String = ""
set(value) {
val lastIndex = chips.indexOf(field)
val newIndex = chips.indexOf(value)
field = value
if (lastIndex >= 0)
notifyItemChanged(lastIndex)
if (newIndex >= 0)
notifyItemChanged(newIndex)
}
@ColorInt
private val colorOnSurface: Int
init {
val typedValue = TypedValue()
context.theme.resolveAttribute(R.attr.colorOnSurface, typedValue, true)
colorOnSurface = typedValue.data
}
class Holder(root: View) : RecyclerView.ViewHolder(root) {
val card: MaterialCardView = root.findViewById(R.id.root)
val title: TextView = root.findViewById(android.R.id.title)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val layoutInflater = LayoutInflater.from(context)
return Holder(layoutInflater.inflate(R.layout.adapter_proxies_chip, parent, false))
}
override fun getItemCount(): Int {
return chips.size
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val current = chips[position]
holder.title.text = current
holder.card.setOnClickListener {
onClick(current)
}
if (selected == current) {
holder.title.setTextColor(Color.WHITE)
holder.card.setCardBackgroundColor(context.getColor(R.color.primaryCardColorStarted))
} else {
holder.title.setTextColor(colorOnSurface)
holder.card.setCardBackgroundColor(context.getColor(R.color.chipBackgroundColor))
}
}
}
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha android:fromAlpha="1.0" android:toAlpha="0.5" />
<alpha android:fromAlpha="0.5" android:toAlpha="1.0" />
</set>
+3 -12
View File
@@ -17,20 +17,11 @@
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<HorizontalScrollView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/chipList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:overScrollMode="never">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:paddingStart="15dp"
android:paddingEnd="15dp" />
</HorizontalScrollView>
android:layout_marginBottom="10dp"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainList"
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
app:cardCornerRadius="12dp"
android:focusable="true"
android:clickable="true">
<TextView
android:id="@android:id/title"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:minEms="3"
android:gravity="center"/>
</com.google.android.material.card.MaterialCardView>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
+8
View File
@@ -74,6 +74,14 @@
<string name="new_profile">New Profile</string>
<string name="edit_profile">Edit Profile</string>
<string name="clash_start_failure">Clash Start Failure</string>
<string name="refresh">Refresh</string>
<string name="group_order">Group Order</string>
<string name="proxy_order">Proxy Order</string>
<string name="default_">Default</string>
<string name="delay">Delay</string>
<string name="direct">Direct</string>
<string name="global">Global</string>
<string name="rule">Rule</string>
<string name="start_url_provider_failure">Start URL Provider Failure</string>
@@ -2,7 +2,6 @@ package com.github.kr328.clash.core
import android.content.Context
import bridge.Bridge
import bridge.EventPoll
import com.github.kr328.clash.core.event.EventStream
import com.github.kr328.clash.core.event.LogEvent
import com.github.kr328.clash.core.model.General
@@ -15,7 +14,6 @@ import com.github.kr328.clash.core.transact.ProxyGroupCollectionImpl
import kotlinx.coroutines.CompletableDeferred
import java.io.File
import java.io.InputStream
import java.util.concurrent.CompletableFuture
object Clash {
private var initialized = false
@@ -1,8 +1,5 @@
package com.github.kr328.clash.core.model
import android.os.Parcel
import android.os.Parcelable
import com.github.kr328.clash.core.serialization.MergedParcels
import kotlinx.serialization.Serializable
@Serializable
@@ -6,7 +6,7 @@ import com.github.kr328.clash.core.serialization.MergedParcels
import kotlinx.serialization.Serializable
@Serializable
data class ProxyGroupList(val list: List<ProxyGroup>): Parcelable {
data class ProxyGroupList(val list: List<ProxyGroup>) : Parcelable {
override fun writeToParcel(parcel: Parcel, flags: Int) {
MergedParcels.dump(serializer(), this, parcel)
}
@@ -1,7 +1,6 @@
package com.github.kr328.clash.core.serialization
import android.os.Parcel
import com.github.kr328.clash.core.utils.Log
import kotlinx.serialization.*
import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule
@@ -18,8 +17,6 @@ object MergedParcels : AbstractSerialFormat(EmptyModule) {
parcel.writeStringList(encoder.getStringList())
parcel.appendFrom(data, 0, data.dataSize())
Log.i("Send ${parcel.dataSize()} bytes")
} finally {
data.recycle()
}
@@ -2,7 +2,6 @@ package com.github.kr328.clash.core.transact
import bridge.DoneCallback
import kotlinx.coroutines.CompletableDeferred
import java.util.concurrent.CompletableFuture
class DoneCallbackImpl : DoneCallback, CompletableDeferred<Unit> by CompletableDeferred() {
override fun doneWithError(e: Exception?) {
@@ -3,10 +3,21 @@ package com.github.kr328.clash.core.utils
import com.github.kr328.clash.core.Constants.TAG
object Log {
fun i(message: String, throwable: Throwable? = null) = android.util.Log.i(TAG, message, throwable)
fun w(message: String, throwable: Throwable? = null) = android.util.Log.w(TAG, message, throwable)
fun e(message: String, throwable: Throwable? = null) = android.util.Log.e(TAG, message, throwable)
fun d(message: String, throwable: Throwable? = null) = android.util.Log.d(TAG, message, throwable)
fun v(message: String, throwable: Throwable? = null) = android.util.Log.v(TAG, message, throwable)
fun wtf(message: String, throwable: Throwable? = null) = android.util.Log.wtf(TAG, message, throwable)
fun i(message: String, throwable: Throwable? = null) =
android.util.Log.i(TAG, message, throwable)
fun w(message: String, throwable: Throwable? = null) =
android.util.Log.w(TAG, message, throwable)
fun e(message: String, throwable: Throwable? = null) =
android.util.Log.e(TAG, message, throwable)
fun d(message: String, throwable: Throwable? = null) =
android.util.Log.d(TAG, message, throwable)
fun v(message: String, throwable: Throwable? = null) =
android.util.Log.v(TAG, message, throwable)
fun wtf(message: String, throwable: Throwable? = null) =
android.util.Log.wtf(TAG, message, throwable)
}