Refactor group tab to ViewPager2 with fragments
Replaces the group tab and server list RecyclerView in MainActivity with a ViewPager2-based implementation using fragments. Adds GroupPagerAdapter and GroupServerFragment for per-group server lists, introduces BaseFragment for fragment view binding, and updates related logic in MainViewModel and SettingsChangeManager. Updates dependencies to include ViewPager2 and Fragment KTX, and modifies layouts to support the new structure.
This commit is contained in:
1 parent
416ee28e80
commit
de5b714a28
16 files changed
+240
-99
No files matched your search
@@ -148,6 +148,8 @@ dependencies {
|
|||||||
implementation(libs.preference.ktx)
|
implementation(libs.preference.ktx)
|
||||||
implementation(libs.recyclerview)
|
implementation(libs.recyclerview)
|
||||||
implementation(libs.androidx.swiperefreshlayout)
|
implementation(libs.androidx.swiperefreshlayout)
|
||||||
|
implementation(libs.androidx.viewpager2)
|
||||||
|
implementation(libs.androidx.fragment)
|
||||||
|
|
||||||
// UI Libraries
|
// UI Libraries
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
data class GroupMapItem(
|
||||||
|
var id: String,
|
||||||
|
var remarks: String,
|
||||||
|
)
|
||||||
@@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
|
|
||||||
object SettingsChangeManager {
|
object SettingsChangeManager {
|
||||||
private val _restartService = MutableStateFlow(false)
|
private val _restartService = MutableStateFlow(false)
|
||||||
private val _reinitGroupTab = MutableStateFlow(false)
|
private val _setupGroupTab = MutableStateFlow(false)
|
||||||
|
|
||||||
// Mark restartService as requiring a restart
|
// Mark restartService as requiring a restart
|
||||||
fun makeRestartService() {
|
fun makeRestartService() {
|
||||||
@@ -19,14 +19,14 @@ object SettingsChangeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark reinitGroupTab as requiring tab reinitialization
|
// Mark reinitGroupTab as requiring tab reinitialization
|
||||||
fun makeReinitGroupTab() {
|
fun makeSetupGroupTab() {
|
||||||
_reinitGroupTab.value = true
|
_setupGroupTab.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and clear the reinitGroupTab flag
|
// Read and clear the reinitGroupTab flag
|
||||||
fun consumeReinitGroupTab(): Boolean {
|
fun consumeSetupGroupTab(): Boolean {
|
||||||
val v = _reinitGroupTab.value
|
val v = _setupGroupTab.value
|
||||||
_reinitGroupTab.value = false
|
_setupGroupTab.value = false
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,12 +34,12 @@ import kotlin.math.sign
|
|||||||
*
|
*
|
||||||
* @author Paul Burke (ipaulpro)
|
* @author Paul Burke (ipaulpro)
|
||||||
*/
|
*/
|
||||||
class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter) : ItemTouchHelper.Callback() {
|
class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter, private val allowSwipe: Boolean = false) : ItemTouchHelper.Callback() {
|
||||||
private var mReturnAnimator: ValueAnimator? = null
|
private var mReturnAnimator: ValueAnimator? = null
|
||||||
|
|
||||||
override fun isLongPressDragEnabled(): Boolean = true
|
override fun isLongPressDragEnabled(): Boolean = true
|
||||||
|
|
||||||
override fun isItemViewSwipeEnabled(): Boolean = true
|
override fun isItemViewSwipeEnabled(): Boolean = allowSwipe
|
||||||
|
|
||||||
override fun getMovementFlags(
|
override fun getMovementFlags(
|
||||||
recyclerView: RecyclerView,
|
recyclerView: RecyclerView,
|
||||||
@@ -49,10 +49,10 @@ class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter
|
|||||||
val swipeFlags: Int
|
val swipeFlags: Int
|
||||||
if (recyclerView.layoutManager is GridLayoutManager) {
|
if (recyclerView.layoutManager is GridLayoutManager) {
|
||||||
dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
|
dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
|
||||||
swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
|
swipeFlags = if (allowSwipe) ItemTouchHelper.START or ItemTouchHelper.END else 0
|
||||||
} else {
|
} else {
|
||||||
dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
|
||||||
swipeFlags = ItemTouchHelper.START or ItemTouchHelper.END
|
swipeFlags = if (allowSwipe) ItemTouchHelper.START or ItemTouchHelper.END else 0
|
||||||
}
|
}
|
||||||
return makeMovementFlags(dragFlags, swipeFlags)
|
return makeMovementFlags(dragFlags, swipeFlags)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class BackupActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val count = MMKV.restoreAllFromDirectory(backupDir)
|
val count = MMKV.restoreAllFromDirectory(backupDir)
|
||||||
SettingsChangeManager.makeReinitGroupTab()
|
SettingsChangeManager.makeSetupGroupTab()
|
||||||
SettingsChangeManager.makeRestartService()
|
SettingsChangeManager.makeRestartService()
|
||||||
return count > 0
|
return count > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import com.v2ray.ang.helper.CustomDividerItemDecoration
|
||||||
|
|
||||||
|
abstract class BaseFragment<VB : ViewBinding> : Fragment() {
|
||||||
|
private var _binding: VB? = null
|
||||||
|
protected val binding: VB
|
||||||
|
get() = _binding!!
|
||||||
|
|
||||||
|
protected abstract fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?): VB
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = inflateBinding(inflater, container)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom divider to a RecyclerView.
|
||||||
|
*
|
||||||
|
* @param recyclerView The target RecyclerView to which the divider will be added.
|
||||||
|
* @param drawableResId The resource ID of the drawable to be used as the divider.
|
||||||
|
* @param orientation The orientation of the divider (DividerItemDecoration.VERTICAL or DividerItemDecoration.HORIZONTAL).
|
||||||
|
*/
|
||||||
|
fun addCustomDividerToRecyclerView(recyclerView: RecyclerView, drawableResId: Int, orientation: Int = DividerItemDecoration.VERTICAL) {
|
||||||
|
// Get the drawable from resources
|
||||||
|
val drawable = ContextCompat.getDrawable(requireContext(), drawableResId)
|
||||||
|
requireNotNull(drawable) { "Drawable resource not found" }
|
||||||
|
|
||||||
|
// Create a DividerItemDecoration with the specified orientation
|
||||||
|
val dividerItemDecoration = CustomDividerItemDecoration(drawable, orientation)
|
||||||
|
|
||||||
|
// Add the divider to the RecyclerView
|
||||||
|
recyclerView.addItemDecoration(dividerItemDecoration)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import com.v2ray.ang.dto.GroupMapItem
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pager adapter for subscription groups.
|
||||||
|
*/
|
||||||
|
class GroupPagerAdapter(activity: FragmentActivity, var groups: List<GroupMapItem>) : FragmentStateAdapter(activity) {
|
||||||
|
override fun getItemCount(): Int = groups.size
|
||||||
|
override fun createFragment(position: Int) = GroupServerFragment.newInstance(groups[position].id)
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun update(groups: List<GroupMapItem>) {
|
||||||
|
this.groups = groups
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.TAG
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.databinding.FragmentGroupServerBinding
|
||||||
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||||
|
import com.v2ray.ang.viewmodel.MainViewModel
|
||||||
|
|
||||||
|
class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>() {
|
||||||
|
private val mainViewModel: MainViewModel by activityViewModels()
|
||||||
|
private lateinit var adapter: MainRecyclerAdapter
|
||||||
|
private var itemTouchHelper: ItemTouchHelper? = null
|
||||||
|
private val subId: String by lazy { arguments?.getString(ARG_SUB_ID).orEmpty() }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ARG_SUB_ID = "subscriptionId"
|
||||||
|
fun newInstance(subId: String) = GroupServerFragment().apply {
|
||||||
|
arguments = Bundle().apply { putString(ARG_SUB_ID, subId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inflateBinding(inflater: LayoutInflater, container: ViewGroup?) =
|
||||||
|
FragmentGroupServerBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
|
adapter = MainRecyclerAdapter(requireActivity() as MainActivity)
|
||||||
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
|
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_DOUBLE_COLUMN_DISPLAY, false)) {
|
||||||
|
binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 2)
|
||||||
|
} else {
|
||||||
|
binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 1)
|
||||||
|
}
|
||||||
|
addCustomDividerToRecyclerView(binding.recyclerView, R.drawable.custom_divider)
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
itemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter, allowSwipe = false))
|
||||||
|
itemTouchHelper?.attachToRecyclerView(binding.recyclerView)
|
||||||
|
|
||||||
|
mainViewModel.updateListAction.observe(viewLifecycleOwner) { index ->
|
||||||
|
if (mainViewModel.subscriptionId != subId) {
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
Log.d(TAG, "GroupServerFragment updateListAction subId=$subId")
|
||||||
|
if (index >= 0) {
|
||||||
|
adapter.notifyItemChanged(index)
|
||||||
|
} else {
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainViewModel.isRunning.observe(viewLifecycleOwner) { isRunning ->
|
||||||
|
adapter.isRunning = isRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "GroupServerFragment onViewCreated: subId=$subId")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
mainViewModel.subscriptionIdChanged(subId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
@@ -23,10 +22,8 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.VPN
|
import com.v2ray.ang.AppConfig.VPN
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -37,7 +34,6 @@ import com.v2ray.ang.extension.toastError
|
|||||||
import com.v2ray.ang.handler.AngConfigManager
|
import com.v2ray.ang.handler.AngConfigManager
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.handler.SettingsChangeManager
|
import com.v2ray.ang.handler.SettingsChangeManager
|
||||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
|
||||||
import com.v2ray.ang.handler.V2RayServiceManager
|
import com.v2ray.ang.handler.V2RayServiceManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.viewmodel.MainViewModel
|
import com.v2ray.ang.viewmodel.MainViewModel
|
||||||
@@ -51,7 +47,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
ActivityMainBinding.inflate(layoutInflater)
|
ActivityMainBinding.inflate(layoutInflater)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val adapter by lazy { MainRecyclerAdapter(this) }
|
val mainViewModel: MainViewModel by viewModels()
|
||||||
|
private lateinit var groupPagerAdapter: GroupPagerAdapter
|
||||||
|
private var tabMediator: TabLayoutMediator? = null
|
||||||
|
|
||||||
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
startV2Ray()
|
startV2Ray()
|
||||||
@@ -61,26 +60,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
if (SettingsChangeManager.consumeRestartService() && mainViewModel.isRunning.value == true) {
|
if (SettingsChangeManager.consumeRestartService() && mainViewModel.isRunning.value == true) {
|
||||||
restartV2Ray()
|
restartV2Ray()
|
||||||
}
|
}
|
||||||
if (SettingsChangeManager.consumeReinitGroupTab()) {
|
if (SettingsChangeManager.consumeSetupGroupTab()) {
|
||||||
initGroupTab()
|
setupGroupTab()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private val tabGroupListener = object : TabLayout.OnTabSelectedListener {
|
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
|
||||||
val selectId = tab?.tag.toString()
|
|
||||||
if (selectId != mainViewModel.subscriptionId) {
|
|
||||||
mainViewModel.subscriptionIdChanged(selectId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTabReselected(tab: TabLayout.Tab?) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
|
||||||
val mainViewModel: MainViewModel by viewModels()
|
|
||||||
|
|
||||||
// register activity result for requesting permission
|
// register activity result for requesting permission
|
||||||
private val requestPermissionLauncher =
|
private val requestPermissionLauncher =
|
||||||
@@ -158,17 +141,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.recyclerView.setHasFixedSize(true)
|
groupPagerAdapter = GroupPagerAdapter(this, emptyList())
|
||||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_DOUBLE_COLUMN_DISPLAY, false)) {
|
binding.viewPager.adapter = groupPagerAdapter
|
||||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 2)
|
binding.viewPager.isUserInputEnabled = true
|
||||||
} else {
|
|
||||||
binding.recyclerView.layoutManager = GridLayoutManager(this, 1)
|
|
||||||
}
|
|
||||||
addCustomDividerToRecyclerView(binding.recyclerView, this, R.drawable.custom_divider)
|
|
||||||
binding.recyclerView.adapter = adapter
|
|
||||||
|
|
||||||
mItemTouchHelper = ItemTouchHelper(SimpleItemTouchHelperCallback(adapter))
|
|
||||||
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
|
|
||||||
|
|
||||||
val toggle = ActionBarDrawerToggle(
|
val toggle = ActionBarDrawerToggle(
|
||||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close
|
||||||
@@ -177,7 +152,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
toggle.syncState()
|
toggle.syncState()
|
||||||
binding.navView.setNavigationItemSelectedListener(this)
|
binding.navView.setNavigationItemSelectedListener(this)
|
||||||
|
|
||||||
initGroupTab()
|
setupGroupTab()
|
||||||
setupViewModel()
|
setupViewModel()
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
@@ -198,20 +173,12 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
private fun setupViewModel() {
|
private fun setupViewModel() {
|
||||||
mainViewModel.updateListAction.observe(this) { index ->
|
|
||||||
if (index >= 0) {
|
|
||||||
adapter.notifyItemChanged(index)
|
|
||||||
} else {
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
|
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
|
||||||
mainViewModel.isRunning.observe(this) { isRunning ->
|
mainViewModel.isRunning.observe(this) { isRunning ->
|
||||||
adapter.isRunning = isRunning
|
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
binding.fab.setImageResource(R.drawable.ic_stop_24dp)
|
binding.fab.setImageResource(R.drawable.ic_stop_24dp)
|
||||||
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_active))
|
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.color_fab_active))
|
||||||
@@ -230,27 +197,22 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
mainViewModel.initAssets(assets)
|
mainViewModel.initAssets(assets)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initGroupTab() {
|
private fun setupGroupTab() {
|
||||||
binding.tabGroup.removeOnTabSelectedListener(tabGroupListener)
|
val groups = mainViewModel.getSubscriptions(this)
|
||||||
binding.tabGroup.removeAllTabs()
|
groupPagerAdapter.update(groups)
|
||||||
binding.tabGroup.isVisible = false
|
|
||||||
|
|
||||||
val (listId, listRemarks) = mainViewModel.getSubscriptions(this)
|
tabMediator?.detach()
|
||||||
if (listId == null || listRemarks == null) {
|
tabMediator = TabLayoutMediator(binding.tabGroup, binding.viewPager) { tab, position ->
|
||||||
return
|
groupPagerAdapter.groups.getOrNull(position)?.let {
|
||||||
}
|
tab.text = it.remarks
|
||||||
|
tab.tag = it.id
|
||||||
|
}
|
||||||
|
}.also { it.attach() }
|
||||||
|
|
||||||
for (it in listRemarks.indices) {
|
val targetIndex = groups.indexOfFirst { it.id == mainViewModel.subscriptionId }.takeIf { it >= 0 } ?: (groups.size - 1)
|
||||||
val tab = binding.tabGroup.newTab()
|
binding.viewPager.setCurrentItem(targetIndex, false)
|
||||||
tab.text = listRemarks[it]
|
|
||||||
tab.tag = listId[it]
|
binding.tabGroup.isVisible = groups.size > 1
|
||||||
binding.tabGroup.addTab(tab)
|
|
||||||
}
|
|
||||||
val selectIndex =
|
|
||||||
listId.indexOf(mainViewModel.subscriptionId).takeIf { it >= 0 } ?: (listId.count() - 1)
|
|
||||||
binding.tabGroup.selectTab(binding.tabGroup.getTabAt(selectIndex))
|
|
||||||
binding.tabGroup.addOnTabSelectedListener(tabGroupListener)
|
|
||||||
binding.tabGroup.isVisible = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startV2Ray() {
|
private fun startV2Ray() {
|
||||||
@@ -261,7 +223,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
V2RayServiceManager.startVService(this)
|
V2RayServiceManager.startVService(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun restartV2Ray() {
|
fun restartV2Ray() {
|
||||||
if (mainViewModel.isRunning.value == true) {
|
if (mainViewModel.isRunning.value == true) {
|
||||||
V2RayServiceManager.stopVService(this)
|
V2RayServiceManager.stopVService(this)
|
||||||
}
|
}
|
||||||
@@ -271,12 +233,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
mainViewModel.reloadServerList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,7 +436,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
}
|
}
|
||||||
|
|
||||||
countSub > 0 -> initGroupTab()
|
countSub > 0 -> setupGroupTab()
|
||||||
else -> toastError(R.string.toast_failure)
|
else -> toastError(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
binding.pbWaiting.hide()
|
binding.pbWaiting.hide()
|
||||||
@@ -693,4 +654,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
tabMediator?.detach()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,7 @@ import com.v2ray.ang.handler.AngConfigManager
|
|||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||||
import com.v2ray.ang.handler.V2RayServiceManager
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>(), ItemTouchHelperAdapter {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
title = getString(R.string.title_sub_setting)
|
title = getString(R.string.title_sub_setting)
|
||||||
|
|
||||||
SettingsChangeManager.makeReinitGroupTab()
|
SettingsChangeManager.makeSetupGroupTab()
|
||||||
val subItem = MmkvManager.decodeSubscription(editSubId)
|
val subItem = MmkvManager.decodeSubscription(editSubId)
|
||||||
if (subItem != null) {
|
if (subItem != null) {
|
||||||
bindingServer(subItem)
|
bindingServer(subItem)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.v2ray.ang.databinding.ItemQrcodeBinding
|
|||||||
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.handler.MmkvManager
|
import com.v2ray.ang.handler.MmkvManager
|
||||||
|
import com.v2ray.ang.handler.SettingsChangeManager
|
||||||
import com.v2ray.ang.handler.SettingsManager
|
import com.v2ray.ang.handler.SettingsManager
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||||
@@ -121,6 +122,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
|||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
notifyItemRangeChanged(position, mActivity.subscriptions.size)
|
notifyItemRangeChanged(position, mActivity.subscriptions.size)
|
||||||
mActivity.refreshData()
|
mActivity.refreshData()
|
||||||
|
SettingsChangeManager.makeSetupGroupTab()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,6 +158,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
|||||||
|
|
||||||
override fun onItemMoveCompleted() {
|
override fun onItemMoveCompleted() {
|
||||||
mActivity.refreshData()
|
mActivity.refreshData()
|
||||||
|
SettingsChangeManager.makeSetupGroupTab()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemDismiss(position: Int) {
|
override fun onItemDismiss(position: Int) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.v2ray.ang.AppConfig
|
|||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.ProfileItem
|
import com.v2ray.ang.dto.ProfileItem
|
||||||
import com.v2ray.ang.dto.ServersCache
|
import com.v2ray.ang.dto.ServersCache
|
||||||
|
import com.v2ray.ang.dto.GroupMapItem
|
||||||
import com.v2ray.ang.extension.serializable
|
import com.v2ray.ang.extension.serializable
|
||||||
import com.v2ray.ang.extension.toastError
|
import com.v2ray.ang.extension.toastError
|
||||||
import com.v2ray.ang.extension.toastSuccess
|
import com.v2ray.ang.extension.toastSuccess
|
||||||
@@ -144,7 +145,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
fun updateCache() {
|
fun updateCache() {
|
||||||
serversCache.clear()
|
serversCache.clear()
|
||||||
for (guid in serverList) {
|
for (guid in serverList) {
|
||||||
var profile = MmkvManager.decodeServerConfig(guid) ?: continue
|
val profile = MmkvManager.decodeServerConfig(guid) ?: continue
|
||||||
// var profile = MmkvManager.decodeProfileConfig(guid)
|
// var profile = MmkvManager.decodeProfileConfig(guid)
|
||||||
// if (profile == null) {
|
// if (profile == null) {
|
||||||
// val config = MmkvManager.decodeServerConfig(guid) ?: continue
|
// val config = MmkvManager.decodeServerConfig(guid) ?: continue
|
||||||
@@ -266,22 +267,25 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
* @param context The context.
|
* @param context The context.
|
||||||
* @return A pair of lists containing the subscription IDs and remarks.
|
* @return A pair of lists containing the subscription IDs and remarks.
|
||||||
*/
|
*/
|
||||||
fun getSubscriptions(context: Context): Pair<MutableList<String>?, MutableList<String>?> {
|
fun getSubscriptions(context: Context): List<GroupMapItem> {
|
||||||
val subscriptions = MmkvManager.decodeSubscriptions()
|
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||||
if (subscriptionId.isNotEmpty()
|
if (subscriptionId.isNotEmpty()
|
||||||
&& !subscriptions.map { it.first }.contains(subscriptionId)
|
&& !subscriptions.map { it.first }.contains(subscriptionId)
|
||||||
) {
|
) {
|
||||||
subscriptionIdChanged("")
|
subscriptionIdChanged("")
|
||||||
}
|
}
|
||||||
if (subscriptions.isEmpty()) {
|
|
||||||
return null to null
|
|
||||||
}
|
|
||||||
val listId = subscriptions.map { it.first }.toMutableList()
|
|
||||||
listId.add(0, "")
|
|
||||||
val listRemarks = subscriptions.map { it.second.remarks }.toMutableList()
|
|
||||||
listRemarks.add(0, context.getString(R.string.filter_config_all))
|
|
||||||
|
|
||||||
return listId to listRemarks
|
val groups = mutableListOf<GroupMapItem>()
|
||||||
|
groups.add(
|
||||||
|
GroupMapItem(
|
||||||
|
id = "",
|
||||||
|
remarks = context.getString(R.string.filter_config_all)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
subscriptions.forEach { (id, item) ->
|
||||||
|
groups.add(GroupMapItem(id = id, remarks = item.remarks))
|
||||||
|
}
|
||||||
|
return groups
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -48,18 +48,17 @@
|
|||||||
android:id="@+id/tab_group"
|
android:id="@+id/tab_group"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="@dimen/padding_spacing_dp8"
|
|
||||||
app:tabIndicatorFullWidth="false"
|
app:tabIndicatorFullWidth="false"
|
||||||
app:tabMode="scrollable"
|
app:tabMode="scrollable"
|
||||||
app:tabTextAppearance="@style/TabLayoutTextStyle" />
|
app:tabTextAppearance="@style/TabLayoutTextStyle" />
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/view_pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
|
||||||
android:nextFocusRight="@+id/fab"
|
android:nextFocusRight="@+id/fab"
|
||||||
android:scrollbars="vertical" />
|
android:scrollbars="vertical"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_test"
|
android:id="@+id/layout_test"
|
||||||
@@ -67,7 +66,7 @@
|
|||||||
android:layout_height="@dimen/view_height_dp64"
|
android:layout_height="@dimen/view_height_dp64"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:nextFocusLeft="@+id/recycler_view"
|
android:nextFocusLeft="@+id/view_pager"
|
||||||
android:nextFocusRight="@+id/fab"
|
android:nextFocusRight="@+id/fab"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scrollbars="vertical" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -28,6 +28,8 @@ mockitoMockitoInline = "6.1.0"
|
|||||||
flexbox = "3.0.0"
|
flexbox = "3.0.0"
|
||||||
preferenceKtx = "1.2.1"
|
preferenceKtx = "1.2.1"
|
||||||
recyclerview = "1.4.0"
|
recyclerview = "1.4.0"
|
||||||
|
viewpager2 = "1.1.0"
|
||||||
|
fragment = "1.8.9"
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" }
|
||||||
@@ -62,6 +64,8 @@ mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "
|
|||||||
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
|
flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbox" }
|
||||||
recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
|
||||||
preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
|
preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
|
||||||
|
androidx-viewpager2 = { module = "androidx.viewpager2:viewpager2", version.ref = "viewpager2" }
|
||||||
|
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment" }
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
|||||||
Reference in new issue
Block a user