Add mode selector

This commit is contained in:
世界 2023-08-25 17:51:01 +08:00
parent 065c62118e
commit 4c0012a20d
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
3 changed files with 145 additions and 24 deletions

View file

@ -1,5 +1,6 @@
package io.nekohasekai.sfa.ui.dashboard
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -7,6 +8,7 @@ import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration
@ -19,12 +21,15 @@ import io.nekohasekai.sfa.database.Profile
import io.nekohasekai.sfa.database.ProfileManager
import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.databinding.FragmentDashboardOverviewBinding
import io.nekohasekai.sfa.databinding.ViewClashModeButtonBinding
import io.nekohasekai.sfa.databinding.ViewProfileItemBinding
import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ktx.getAttrColor
import io.nekohasekai.sfa.ui.MainActivity
import io.nekohasekai.sfa.utils.CommandClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -39,9 +44,7 @@ class OverviewFragment : Fragment() {
private val clashModeClient =
CommandClient(lifecycleScope, CommandClient.ConnectionType.ClashMode, ClashModeClient())
private var _adapter: Adapter? = null
private val adapter get() = _adapter!!
private var adapter: Adapter? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
@ -53,7 +56,7 @@ class OverviewFragment : Fragment() {
private fun onCreate() {
val activity = activity ?: return
binding.profileList.adapter = Adapter(lifecycleScope, binding).apply {
_adapter = this
adapter = this
reload()
}
binding.profileList.layoutManager = LinearLayoutManager(requireContext())
@ -62,9 +65,12 @@ class OverviewFragment : Fragment() {
binding.profileList.addItemDecoration(divider)
activity.serviceStatus.observe(viewLifecycleOwner) {
binding.statusContainer.isVisible = it == Status.Starting || it == Status.Started
if (it != Status.Started) {
binding.clashModeCard.isVisible = false
}
if (it == Status.Started) {
statusClient.connect()
// clashModeClient.connect()
clashModeClient.connect()
}
}
ProfileManager.registerCallback(this::updateProfiles)
@ -72,30 +78,17 @@ class OverviewFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
_adapter = null
adapter = null
_binding = null
statusClient.disconnect()
// clashModeClient.disconnect()
clashModeClient.disconnect()
ProfileManager.unregisterCallback(this::updateProfiles)
}
override fun onPause() {
super.onPause()
statusClient.disconnect()
// clashModeClient.disconnect()
}
override fun onResume() {
super.onResume()
statusClient.connect()
// clashModeClient.connect()
}
private fun updateProfiles() {
_adapter?.reload()
adapter?.reload()
}
inner class StatusClient : CommandClient.Handler {
override fun onConnected() {
@ -137,15 +130,88 @@ class OverviewFragment : Fragment() {
inner class ClashModeClient : CommandClient.Handler {
override fun initializeClashMode(modeList: List<String>, currentMode: String) {
// TODO: initialize mode selector here
if (modeList.size > 1) {
lifecycleScope.launch(Dispatchers.Main) {
binding.clashModeCard.isVisible = true
binding.clashModeList.adapter = ClashModeAdapter(modeList, currentMode)
binding.clashModeList.layoutManager =
GridLayoutManager(
requireContext(),
if (modeList.size < 3) modeList.size else 3
)
}
} else {
lifecycleScope.launch(Dispatchers.Main) {
binding.clashModeCard.isVisible = false
}
}
}
@SuppressLint("NotifyDataSetChanged")
override fun updateClashMode(newMode: String) {
// TODO: update mode here
val adapter = binding.clashModeList.adapter as? ClashModeAdapter ?: return
adapter.selected = newMode
lifecycleScope.launch(Dispatchers.Main) {
adapter.notifyDataSetChanged()
}
}
}
private inner class ClashModeAdapter(
val items: List<String>,
var selected: String
) :
RecyclerView.Adapter<ClashModeItemView>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ClashModeItemView {
return ClashModeItemView(
ViewClashModeButtonBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ClashModeItemView, position: Int) {
holder.bind(items[position], selected)
}
}
private inner class ClashModeItemView(val binding: ViewClashModeButtonBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: String, selected: String) {
binding.clashModeButton.text = item
if (item != selected) {
binding.clashModeButton.setBackgroundColor(
binding.root.context.getAttrColor(com.google.android.material.R.attr.colorButtonNormal)
)
binding.clashModeButton.setOnClickListener {
runCatching {
Libbox.newStandaloneCommandClient().setClashMode(item)
clashModeClient.connect()
}.onFailure {
GlobalScope.launch(Dispatchers.Main) {
binding.root.context.errorDialogBuilder(it).show()
}
}
}
} else {
binding.clashModeButton.setBackgroundColor(
binding.root.context.getAttrColor(com.google.android.material.R.attr.colorAccent)
)
binding.clashModeButton.isClickable = false
}
}
}
class Adapter(
internal val scope: CoroutineScope,
private val parent: FragmentDashboardOverviewBinding

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -410,6 +411,44 @@
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/clashModeCard"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mode"
android:textAppearance="?attr/textAppearanceTitleSmall">
</TextView>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/clashModeList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:itemCount="3"
tools:listitem="@layout/view_clash_mode_button" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileCard"
style="?attr/materialCardViewElevatedStyle"
@ -428,7 +467,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/profile"
android:textAppearance="?attr/textAppearanceTitleLarge">
android:textAppearance="?attr/textAppearanceTitleSmall">
</TextView>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center">
<Button
android:id="@+id/clashModeButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Direct" />
</LinearLayout>