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 package io.nekohasekai.sfa.ui.dashboard
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -7,6 +8,7 @@ import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration 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.ProfileManager
import io.nekohasekai.sfa.database.Settings import io.nekohasekai.sfa.database.Settings
import io.nekohasekai.sfa.databinding.FragmentDashboardOverviewBinding import io.nekohasekai.sfa.databinding.FragmentDashboardOverviewBinding
import io.nekohasekai.sfa.databinding.ViewClashModeButtonBinding
import io.nekohasekai.sfa.databinding.ViewProfileItemBinding import io.nekohasekai.sfa.databinding.ViewProfileItemBinding
import io.nekohasekai.sfa.ktx.errorDialogBuilder import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ktx.getAttrColor
import io.nekohasekai.sfa.ui.MainActivity import io.nekohasekai.sfa.ui.MainActivity
import io.nekohasekai.sfa.utils.CommandClient import io.nekohasekai.sfa.utils.CommandClient
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -39,9 +44,7 @@ class OverviewFragment : Fragment() {
private val clashModeClient = private val clashModeClient =
CommandClient(lifecycleScope, CommandClient.ConnectionType.ClashMode, ClashModeClient()) CommandClient(lifecycleScope, CommandClient.ConnectionType.ClashMode, ClashModeClient())
private var _adapter: Adapter? = null private var adapter: Adapter? = null
private val adapter get() = _adapter!!
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View { ): View {
@ -53,7 +56,7 @@ class OverviewFragment : Fragment() {
private fun onCreate() { private fun onCreate() {
val activity = activity ?: return val activity = activity ?: return
binding.profileList.adapter = Adapter(lifecycleScope, binding).apply { binding.profileList.adapter = Adapter(lifecycleScope, binding).apply {
_adapter = this adapter = this
reload() reload()
} }
binding.profileList.layoutManager = LinearLayoutManager(requireContext()) binding.profileList.layoutManager = LinearLayoutManager(requireContext())
@ -62,9 +65,12 @@ class OverviewFragment : Fragment() {
binding.profileList.addItemDecoration(divider) binding.profileList.addItemDecoration(divider)
activity.serviceStatus.observe(viewLifecycleOwner) { activity.serviceStatus.observe(viewLifecycleOwner) {
binding.statusContainer.isVisible = it == Status.Starting || it == Status.Started binding.statusContainer.isVisible = it == Status.Starting || it == Status.Started
if (it != Status.Started) {
binding.clashModeCard.isVisible = false
}
if (it == Status.Started) { if (it == Status.Started) {
statusClient.connect() statusClient.connect()
// clashModeClient.connect() clashModeClient.connect()
} }
} }
ProfileManager.registerCallback(this::updateProfiles) ProfileManager.registerCallback(this::updateProfiles)
@ -72,30 +78,17 @@ class OverviewFragment : Fragment() {
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_adapter = null adapter = null
_binding = null _binding = null
statusClient.disconnect() statusClient.disconnect()
// clashModeClient.disconnect() clashModeClient.disconnect()
ProfileManager.unregisterCallback(this::updateProfiles) 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() { private fun updateProfiles() {
_adapter?.reload() adapter?.reload()
} }
inner class StatusClient : CommandClient.Handler { inner class StatusClient : CommandClient.Handler {
override fun onConnected() { override fun onConnected() {
@ -137,15 +130,88 @@ class OverviewFragment : Fragment() {
inner class ClashModeClient : CommandClient.Handler { inner class ClashModeClient : CommandClient.Handler {
override fun initializeClashMode(modeList: List<String>, currentMode: String) { 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) { 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( class Adapter(
internal val scope: CoroutineScope, internal val scope: CoroutineScope,
private val parent: FragmentDashboardOverviewBinding private val parent: FragmentDashboardOverviewBinding

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -410,6 +411,44 @@
</LinearLayout> </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 <com.google.android.material.card.MaterialCardView
android:id="@+id/profileCard" android:id="@+id/profileCard"
style="?attr/materialCardViewElevatedStyle" style="?attr/materialCardViewElevatedStyle"
@ -428,7 +467,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/profile" android:text="@string/profile"
android:textAppearance="?attr/textAppearanceTitleLarge"> android:textAppearance="?attr/textAppearanceTitleSmall">
</TextView> </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>