Improve dashboard

This commit is contained in:
世界
2024-03-12 13:59:24 +08:00
parent f0acb0999b
commit 096d5ef43d
45 changed files with 507 additions and 272 deletions

View File

@@ -14,12 +14,17 @@ import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.ProfileContent
@@ -38,6 +43,7 @@ import io.nekohasekai.sfa.databinding.ActivityMainBinding
import io.nekohasekai.sfa.ktx.errorDialogBuilder
import io.nekohasekai.sfa.ktx.hasPermission
import io.nekohasekai.sfa.ui.profile.NewProfileActivity
import io.nekohasekai.sfa.ui.settings.CoreFragment
import io.nekohasekai.sfa.ui.shared.AbstractActivity
import io.nekohasekai.sfa.vendor.Vendor
import kotlinx.coroutines.Dispatchers
@@ -47,13 +53,15 @@ import java.io.File
import java.util.Date
import java.util.LinkedList
class MainActivity : AbstractActivity(), ServiceConnection.Callback {
class MainActivity : AbstractActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
ServiceConnection.Callback {
companion object {
private const val TAG = "MainActivity"
}
private lateinit var binding: ActivityMainBinding
internal lateinit var binding: ActivityMainBinding
private val connection = ServiceConnection(this, this)
val logList = LinkedList<String>()
@@ -65,9 +73,12 @@ class MainActivity : AbstractActivity(), ServiceConnection.Callback {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
val navController = findNavController(R.id.nav_host_fragment_activity_my)
navController.setGraph(R.navigation.mobile_navigation)
navController.navigate(R.id.navigation_dashboard)
navController.addOnDestinationChangedListener(::onDestinationChanged)
val appBarConfiguration =
AppBarConfiguration(
setOf(
@@ -86,6 +97,31 @@ class MainActivity : AbstractActivity(), ServiceConnection.Callback {
onNewIntent(intent)
}
private fun onDestinationChanged(
navController: NavController,
navDestination: NavDestination,
bundle: Bundle?
) {
val destinationId = navDestination.id
binding.dashboardTabContainer.isVisible = destinationId == R.id.navigation_dashboard
}
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
val navController = findNavController(R.id.nav_host_fragment_activity_my)
when (pref.fragment) {
CoreFragment::class.java.name -> {
navController.navigate(R.id.navigation_settings_core)
return true
}
else -> return false
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val uri = intent.data ?: return
@@ -445,4 +481,5 @@ class MainActivity : AbstractActivity(), ServiceConnection.Callback {
super.onDestroy()
}
}

View File

@@ -12,7 +12,6 @@ import androidx.core.view.isInvisible
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 androidx.recyclerview.widget.SimpleItemAnimator
@@ -133,6 +132,8 @@ class GroupsFragment : Fragment(), CommandClient.Handler {
lateinit var group: OutboundGroup
lateinit var items: MutableList<OutboundGroupItem>
lateinit var adapter: ItemAdapter
@SuppressLint("NotifyDataSetChanged")
fun bind(group: OutboundGroup) {
this.group = group
binding.groupName.text = group.tag
@@ -153,10 +154,23 @@ class GroupsFragment : Fragment(), CommandClient.Handler {
while (itemIterator.hasNext()) {
items.add(itemIterator.next())
}
adapter = ItemAdapter(this, group, items)
binding.itemList.adapter = adapter
(binding.itemList.itemAnimator as SimpleItemAnimator).supportsChangeAnimations = false
binding.itemList.layoutManager = GridLayoutManager(binding.root.context, 2)
if (!::adapter.isInitialized) {
adapter = ItemAdapter(this, group, items)
binding.itemList.adapter = adapter
/* val divider =
MaterialDividerItemDecoration(
binding.root.context,
LinearLayoutManager.VERTICAL
)
divider.isLastItemDecorated = false
binding.itemList.addItemDecoration(divider)*/
(binding.itemList.itemAnimator as SimpleItemAnimator).supportsChangeAnimations =
false
binding.itemList.layoutManager = LinearLayoutManager(binding.root.context)
} else {
adapter.items = items
adapter.notifyDataSetChanged()
}
updateExpand()
}
@@ -220,7 +234,7 @@ class GroupsFragment : Fragment(), CommandClient.Handler {
private class ItemAdapter(
val groupView: GroupView,
val group: OutboundGroup,
val items: List<OutboundGroupItem>
var items: List<OutboundGroupItem> = emptyList()
) :
RecyclerView.Adapter<ItemGroupView>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemGroupView {

View File

@@ -70,42 +70,20 @@ class OverviewFragment : Fragment() {
Status.Stopped -> {
binding.clashModeCard.isVisible = false
binding.systemProxyCard.isVisible = false
binding.fab.setImageResource(R.drawable.ic_play_arrow_24)
binding.fab.show()
}
Status.Starting -> {
binding.fab.hide()
}
Status.Started -> {
statusClient.connect()
clashModeClient.connect()
reloadSystemProxyStatus()
binding.fab.setImageResource(R.drawable.ic_stop_24)
binding.fab.show()
}
Status.Stopping -> {
binding.fab.hide()
}
else -> {}
}
}
binding.fab.setOnClickListener {
when (activity.serviceStatus.value) {
Status.Stopped -> {
activity.startService()
}
Status.Started -> {
BoxService.stop()
}
else -> {}
}
}
ProfileManager.registerCallback(this::updateProfiles)
}

View File

@@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.bg.BoxService
import io.nekohasekai.sfa.constant.Status
import io.nekohasekai.sfa.databinding.FragmentDashboardBinding
import io.nekohasekai.sfa.ui.MainActivity
@@ -20,6 +21,7 @@ class DashboardFragment : Fragment(R.layout.fragment_dashboard) {
private val activity: MainActivity? get() = super.getActivity() as MainActivity?
private var binding: FragmentDashboardBinding? = null
private var mediator: TabLayoutMediator? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
@@ -34,21 +36,46 @@ class DashboardFragment : Fragment(R.layout.fragment_dashboard) {
val binding = binding ?: return
binding.dashboardPager.adapter = Adapter(this)
binding.dashboardPager.offscreenPageLimit = Page.values().size
TabLayoutMediator(binding.dashboardTabLayout, binding.dashboardPager) { tab, position ->
mediator = TabLayoutMediator(
activity.binding.dashboardTabLayout,
binding.dashboardPager
) { tab, position ->
tab.setText(Page.values()[position].titleRes)
}.attach()
}.apply { attach() }
activity.serviceStatus.observe(viewLifecycleOwner) {
when (it) {
Status.Stopped -> {
disablePager()
binding.fab.setImageResource(R.drawable.ic_play_arrow_24)
binding.fab.show()
}
Status.Starting -> {
binding.fab.hide()
}
Status.Started -> {
enablePager()
binding.fab.setImageResource(R.drawable.ic_stop_24)
binding.fab.show()
}
Status.Stopping -> {
disablePager()
binding.fab.hide()
}
else -> {}
}
}
binding.fab.setOnClickListener {
when (activity.serviceStatus.value) {
Status.Stopped -> {
activity.startService()
}
Status.Started -> {
BoxService.stop()
}
else -> {}
@@ -58,18 +85,21 @@ class DashboardFragment : Fragment(R.layout.fragment_dashboard) {
override fun onDestroyView() {
super.onDestroyView()
mediator?.detach()
binding = null
}
private fun enablePager() {
val activity = activity ?: return
val binding = binding ?: return
binding.dashboardTabLayout.isVisible = true
activity.binding.dashboardTabLayout.isVisible = true
binding.dashboardPager.isUserInputEnabled = true
}
private fun disablePager() {
val activity = activity ?: return
val binding = binding ?: return
binding.dashboardTabLayout.isVisible = false
activity.binding.dashboardTabLayout.isVisible = false
binding.dashboardPager.isUserInputEnabled = false
binding.dashboardPager.setCurrentItem(0, false)
}

View File

@@ -0,0 +1,9 @@
package io.nekohasekai.sfa.ui.main
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.ui.settings.AbstractSettingsFragment
class SettingsFragment0 : AbstractSettingsFragment(R.xml.preferences_settings) {
}

View File

@@ -0,0 +1,27 @@
package io.nekohasekai.sfa.ui.settings
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.XmlRes
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import io.nekohasekai.sfa.R
open class AbstractSettingsFragment(@XmlRes val resId: Int) : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(resId, rootKey)
}
override fun onCreateRecyclerView(
inflater: LayoutInflater,
parent: ViewGroup,
savedInstanceState: Bundle?
): RecyclerView {
val recyclerView = inflater
.inflate(R.layout.view_prefenence_screen, parent, false) as RecyclerView
recyclerView.layoutManager = onCreateLayoutManager()
return recyclerView
}
}

View File

@@ -0,0 +1,6 @@
package io.nekohasekai.sfa.ui.settings
import androidx.fragment.app.Fragment
class CoreFragment : Fragment() {
}

View File

@@ -0,0 +1,45 @@
package io.nekohasekai.sfa.ui.settings
import android.content.Context
import android.util.AttributeSet
import android.util.TypedValue
import androidx.preference.Preference
import io.nekohasekai.sfa.ktx.getAttrColor
class Preference : Preference {
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
icon?.setTint(context.getAttrColor(com.google.android.material.R.attr.colorOnSurface))
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : this(
context,
attrs,
defStyleAttr,
0
)
constructor(context: Context, attrs: AttributeSet?) : this(
context, attrs, getAttr(
context, androidx.preference.R.attr.preferenceStyle,
android.R.attr.preferenceStyle
)
)
companion object {
private fun getAttr(context: Context, attr: Int, fallbackAttr: Int): Int {
val value = TypedValue()
context.theme.resolveAttribute(attr, value, true)
if (value.resourceId != 0) {
return attr
}
return fallbackAttr
}
}
}

View File

@@ -5,7 +5,6 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import com.google.android.material.color.DynamicColors
import com.google.android.material.elevation.SurfaceColors
import io.nekohasekai.sfa.R
import io.nekohasekai.sfa.ktx.getAttrColor
@@ -16,9 +15,10 @@ abstract class AbstractActivity : AppCompatActivity() {
DynamicColors.applyToActivityIfAvailable(this)
val color = SurfaceColors.SURFACE_2.getColor(this)
window.statusBarColor = color
window.navigationBarColor = color
val colorSurfaceContainer =
getAttrColor(com.google.android.material.R.attr.colorSurfaceContainer)
window.statusBarColor = colorSurfaceContainer
window.navigationBarColor = colorSurfaceContainer
supportActionBar?.setHomeAsUpIndicator(AppCompatResources.getDrawable(
this@AbstractActivity,