diff --git a/V2rayNG/app/build.gradle.kts b/V2rayNG/app/build.gradle.kts index 8612a5dd..28b7edf3 100644 --- a/V2rayNG/app/build.gradle.kts +++ b/V2rayNG/app/build.gradle.kts @@ -148,6 +148,8 @@ dependencies { implementation(libs.preference.ktx) implementation(libs.recyclerview) implementation(libs.androidx.swiperefreshlayout) + implementation(libs.androidx.viewpager2) + implementation(libs.androidx.fragment) // UI Libraries implementation(libs.material) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/dto/GroupMapItem.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/GroupMapItem.kt new file mode 100644 index 00000000..5cbf267e --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/dto/GroupMapItem.kt @@ -0,0 +1,6 @@ +package com.v2ray.ang.dto + +data class GroupMapItem( + var id: String, + var remarks: String, +) \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsChangeManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsChangeManager.kt index 75d0dcc4..5a748141 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsChangeManager.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/handler/SettingsChangeManager.kt @@ -4,7 +4,7 @@ import kotlinx.coroutines.flow.MutableStateFlow object SettingsChangeManager { private val _restartService = MutableStateFlow(false) - private val _reinitGroupTab = MutableStateFlow(false) + private val _setupGroupTab = MutableStateFlow(false) // Mark restartService as requiring a restart fun makeRestartService() { @@ -19,14 +19,14 @@ object SettingsChangeManager { } // Mark reinitGroupTab as requiring tab reinitialization - fun makeReinitGroupTab() { - _reinitGroupTab.value = true + fun makeSetupGroupTab() { + _setupGroupTab.value = true } // Read and clear the reinitGroupTab flag - fun consumeReinitGroupTab(): Boolean { - val v = _reinitGroupTab.value - _reinitGroupTab.value = false + fun consumeSetupGroupTab(): Boolean { + val v = _setupGroupTab.value + _setupGroupTab.value = false return v } } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.kt index b98129a7..2160660b 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/helper/SimpleItemTouchHelperCallback.kt @@ -34,12 +34,12 @@ import kotlin.math.sign * * @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 override fun isLongPressDragEnabled(): Boolean = true - override fun isItemViewSwipeEnabled(): Boolean = true + override fun isItemViewSwipeEnabled(): Boolean = allowSwipe override fun getMovementFlags( recyclerView: RecyclerView, @@ -49,10 +49,10 @@ class SimpleItemTouchHelperCallback(private val mAdapter: ItemTouchHelperAdapter val swipeFlags: Int if (recyclerView.layoutManager is GridLayoutManager) { 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 { 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) } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt index 1e7557f8..cf550c48 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BackupActivity.kt @@ -170,7 +170,7 @@ class BackupActivity : BaseActivity() { } val count = MMKV.restoreAllFromDirectory(backupDir) - SettingsChangeManager.makeReinitGroupTab() + SettingsChangeManager.makeSetupGroupTab() SettingsChangeManager.makeRestartService() return count > 0 } diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BaseFragment.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BaseFragment.kt new file mode 100644 index 00000000..8938c169 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/BaseFragment.kt @@ -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 : 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) + } +} \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupPagerAdapter.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupPagerAdapter.kt new file mode 100644 index 00000000..43ddbddf --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupPagerAdapter.kt @@ -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) : FragmentStateAdapter(activity) { + override fun getItemCount(): Int = groups.size + override fun createFragment(position: Int) = GroupServerFragment.newInstance(groups[position].id) + + @SuppressLint("NotifyDataSetChanged") + fun update(groups: List) { + this.groups = groups + notifyDataSetChanged() + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupServerFragment.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupServerFragment.kt new file mode 100644 index 00000000..1734f4f6 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/GroupServerFragment.kt @@ -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() { + 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) + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt index e485cfc8..6f7dcfb9 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainActivity.kt @@ -1,7 +1,6 @@ package com.v2ray.ang.ui import android.Manifest -import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.content.res.ColorStateList @@ -23,10 +22,8 @@ import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.core.view.isVisible 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.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.VPN 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.MmkvManager import com.v2ray.ang.handler.SettingsChangeManager -import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.handler.V2RayServiceManager import com.v2ray.ang.util.Utils import com.v2ray.ang.viewmodel.MainViewModel @@ -51,7 +47,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList 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()) { if (it.resultCode == RESULT_OK) { startV2Ray() @@ -61,26 +60,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList if (SettingsChangeManager.consumeRestartService() && mainViewModel.isRunning.value == true) { restartV2Ray() } - if (SettingsChangeManager.consumeReinitGroupTab()) { - initGroupTab() + if (SettingsChangeManager.consumeSetupGroupTab()) { + 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 private val requestPermissionLauncher = @@ -158,17 +141,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } } - binding.recyclerView.setHasFixedSize(true) - if (MmkvManager.decodeSettingsBool(AppConfig.PREF_DOUBLE_COLUMN_DISPLAY, false)) { - binding.recyclerView.layoutManager = GridLayoutManager(this, 2) - } 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) + groupPagerAdapter = GroupPagerAdapter(this, emptyList()) + binding.viewPager.adapter = groupPagerAdapter + binding.viewPager.isUserInputEnabled = true val toggle = ActionBarDrawerToggle( 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() binding.navView.setNavigationItemSelectedListener(this) - initGroupTab() + setupGroupTab() setupViewModel() 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() { - mainViewModel.updateListAction.observe(this) { index -> - if (index >= 0) { - adapter.notifyItemChanged(index) - } else { - adapter.notifyDataSetChanged() - } - } mainViewModel.updateTestResultAction.observe(this) { setTestState(it) } mainViewModel.isRunning.observe(this) { isRunning -> - adapter.isRunning = isRunning if (isRunning) { binding.fab.setImageResource(R.drawable.ic_stop_24dp) 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) } - private fun initGroupTab() { - binding.tabGroup.removeOnTabSelectedListener(tabGroupListener) - binding.tabGroup.removeAllTabs() - binding.tabGroup.isVisible = false + private fun setupGroupTab() { + val groups = mainViewModel.getSubscriptions(this) + groupPagerAdapter.update(groups) - val (listId, listRemarks) = mainViewModel.getSubscriptions(this) - if (listId == null || listRemarks == null) { - return - } + tabMediator?.detach() + tabMediator = TabLayoutMediator(binding.tabGroup, binding.viewPager) { tab, position -> + groupPagerAdapter.groups.getOrNull(position)?.let { + tab.text = it.remarks + tab.tag = it.id + } + }.also { it.attach() } - for (it in listRemarks.indices) { - val tab = binding.tabGroup.newTab() - tab.text = listRemarks[it] - tab.tag = listId[it] - 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 + val targetIndex = groups.indexOfFirst { it.id == mainViewModel.subscriptionId }.takeIf { it >= 0 } ?: (groups.size - 1) + binding.viewPager.setCurrentItem(targetIndex, false) + + binding.tabGroup.isVisible = groups.size > 1 } private fun startV2Ray() { @@ -261,7 +223,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList V2RayServiceManager.startVService(this) } - public fun restartV2Ray() { + fun restartV2Ray() { if (mainViewModel.isRunning.value == true) { V2RayServiceManager.stopVService(this) } @@ -271,12 +233,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList } } - public override fun onResume() { + override fun onResume() { super.onResume() - mainViewModel.reloadServerList() } - public override fun onPause() { + override fun onPause() { super.onPause() } @@ -475,7 +436,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList mainViewModel.reloadServerList() } - countSub > 0 -> initGroupTab() + countSub > 0 -> setupGroupTab() else -> toastError(R.string.toast_failure) } binding.pbWaiting.hide() @@ -693,4 +654,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList binding.drawerLayout.closeDrawer(GravityCompat.START) return true } + + override fun onDestroy() { + tabMediator?.detach() + super.onDestroy() + } } \ No newline at end of file diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt index 9be24f4c..931498ac 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/MainRecyclerAdapter.kt @@ -26,9 +26,7 @@ import com.v2ray.ang.handler.AngConfigManager import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperViewHolder -import com.v2ray.ang.handler.V2RayServiceManager import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter(), ItemTouchHelperAdapter { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt index 61c6acb3..fadb7d89 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubEditActivity.kt @@ -31,7 +31,7 @@ class SubEditActivity : BaseActivity() { setContentView(binding.root) title = getString(R.string.title_sub_setting) - SettingsChangeManager.makeReinitGroupTab() + SettingsChangeManager.makeSetupGroupTab() val subItem = MmkvManager.decodeSubscription(editSubId) if (subItem != null) { bindingServer(subItem) diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt index 41a5b6c7..3f3077f8 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/ui/SubSettingRecyclerAdapter.kt @@ -16,6 +16,7 @@ import com.v2ray.ang.databinding.ItemQrcodeBinding import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding import com.v2ray.ang.extension.toast import com.v2ray.ang.handler.MmkvManager +import com.v2ray.ang.handler.SettingsChangeManager import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperViewHolder @@ -121,6 +122,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView notifyItemRemoved(position) notifyItemRangeChanged(position, mActivity.subscriptions.size) mActivity.refreshData() + SettingsChangeManager.makeSetupGroupTab() } } } @@ -156,6 +158,7 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView override fun onItemMoveCompleted() { mActivity.refreshData() + SettingsChangeManager.makeSetupGroupTab() } override fun onItemDismiss(position: Int) { diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt index ec5cb7ee..e47c6cb9 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/viewmodel/MainViewModel.kt @@ -16,6 +16,7 @@ import com.v2ray.ang.AppConfig import com.v2ray.ang.R import com.v2ray.ang.dto.ProfileItem import com.v2ray.ang.dto.ServersCache +import com.v2ray.ang.dto.GroupMapItem import com.v2ray.ang.extension.serializable import com.v2ray.ang.extension.toastError import com.v2ray.ang.extension.toastSuccess @@ -144,7 +145,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { fun updateCache() { serversCache.clear() for (guid in serverList) { - var profile = MmkvManager.decodeServerConfig(guid) ?: continue + val profile = MmkvManager.decodeServerConfig(guid) ?: continue // var profile = MmkvManager.decodeProfileConfig(guid) // if (profile == null) { // val config = MmkvManager.decodeServerConfig(guid) ?: continue @@ -266,22 +267,25 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { * @param context The context. * @return A pair of lists containing the subscription IDs and remarks. */ - fun getSubscriptions(context: Context): Pair?, MutableList?> { + fun getSubscriptions(context: Context): List { val subscriptions = MmkvManager.decodeSubscriptions() if (subscriptionId.isNotEmpty() && !subscriptions.map { it.first }.contains(subscriptionId) ) { 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() + 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 } /** diff --git a/V2rayNG/app/src/main/res/layout/activity_main.xml b/V2rayNG/app/src/main/res/layout/activity_main.xml index ebc0f219..3963e07f 100644 --- a/V2rayNG/app/src/main/res/layout/activity_main.xml +++ b/V2rayNG/app/src/main/res/layout/activity_main.xml @@ -48,18 +48,17 @@ android:id="@+id/tab_group" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="@dimen/padding_spacing_dp8" app:tabIndicatorFullWidth="false" app:tabMode="scrollable" app:tabTextAppearance="@style/TabLayoutTextStyle" /> - + android:scrollbars="vertical" + android:layout_weight="1" /> diff --git a/V2rayNG/app/src/main/res/layout/fragment_group_server.xml b/V2rayNG/app/src/main/res/layout/fragment_group_server.xml new file mode 100644 index 00000000..ab9436d7 --- /dev/null +++ b/V2rayNG/app/src/main/res/layout/fragment_group_server.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/V2rayNG/gradle/libs.versions.toml b/V2rayNG/gradle/libs.versions.toml index 8840181f..6edc742e 100644 --- a/V2rayNG/gradle/libs.versions.toml +++ b/V2rayNG/gradle/libs.versions.toml @@ -28,6 +28,8 @@ mockitoMockitoInline = "6.1.0" flexbox = "3.0.0" preferenceKtx = "1.2.1" recyclerview = "1.4.0" +viewpager2 = "1.1.0" +fragment = "1.8.9" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 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" } recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" } 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] android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }