mirror of
https://github.com/SagerNet/sing-box-for-android.git
synced 2025-04-03 20:07:38 +03:00
Add mode selector
This commit is contained in:
parent
065c62118e
commit
4c0012a20d
3 changed files with 145 additions and 24 deletions
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
16
app/src/main/res/layout/view_clash_mode_button.xml
Normal file
16
app/src/main/res/layout/view_clash_mode_button.xml
Normal 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>
|
Loading…
Add table
Add a link
Reference in a new issue