Add locate FAB and scroll-to-selected server

https://github.com/2dust/v2rayNG/issues/5165
This commit is contained in:
2dust
2026-03-13 15:02:08 +08:00
parent 6aa26d2621
commit cd45b51487
14 changed files with 119 additions and 1 deletions
@@ -280,4 +280,38 @@ class GroupServerFragment : BaseFragment<FragmentGroupServerBinding>(),
ownerActivity.importConfigViaSub()
binding.refreshLayout.isRefreshing = false
}
/**
* Scrolls to the currently selected server in the RecyclerView
*/
fun scrollToSelectedServer() {
val selectedGuid = MmkvManager.getSelectServer()
if (selectedGuid.isNullOrEmpty()) {
ownerActivity.toast(R.string.title_file_chooser)
return
}
// Find the position of the selected server
val serversCache = mainViewModel.serversCache
val position = serversCache.indexOfFirst { it.guid == selectedGuid }
val recyclerView = binding.recyclerView
if (position >= 0) {
// Get the layout manager
val layoutManager = recyclerView.layoutManager as? GridLayoutManager
if (layoutManager != null) {
// Scroll to position with offset to center it on screen
// First scroll to position, then adjust to center
recyclerView.post {
layoutManager.scrollToPositionWithOffset(position, recyclerView.height / 3)
}
} else {
// Fallback to smooth scroll if layout manager is not GridLayoutManager
recyclerView.smoothScrollToPosition(position)
}
} else {
ownerActivity.toast(R.string.toast_server_not_found_in_group)
}
}
}
@@ -94,6 +94,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
})
binding.fab.setOnClickListener { handleFabAction() }
binding.fabLocate.setOnClickListener { locateSelectedServer() }
binding.layoutTest.setOnClickListener { handleLayoutTestClick() }
setupGroupTab()
@@ -569,6 +570,47 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
}
/**
* Locates and scrolls to the currently selected server.
* If the selected server is in a different group, automatically switches to that group first.
*/
private fun locateSelectedServer() {
val targetSubscriptionId = mainViewModel.findSubscriptionIdBySelect()
if (targetSubscriptionId.isNullOrEmpty()) {
toast(R.string.title_file_chooser)
return
}
val targetGroupIndex = groupPagerAdapter.groups.indexOfFirst { it.id == targetSubscriptionId }
if (targetGroupIndex < 0) {
toast(R.string.toast_server_not_found_in_group)
return
}
// Switch to target group if needed, then scroll to the server
if (binding.viewPager.currentItem != targetGroupIndex) {
binding.viewPager.setCurrentItem(targetGroupIndex, true)
binding.viewPager.postDelayed({ scrollToSelectedServer(targetGroupIndex) }, 1000)
} else {
scrollToSelectedServer(targetGroupIndex)
}
}
/**
* Scrolls to the selected server in the specified fragment.
* @param groupIndex The index of the group/fragment to scroll in
*/
private fun scrollToSelectedServer(groupIndex: Int) {
val itemId = groupPagerAdapter.getItemId(groupIndex)
val fragment = supportFragmentManager.findFragmentByTag("f$itemId") as? GroupServerFragment
if (fragment?.isAdded == true && fragment.view != null) {
fragment.scrollToSelectedServer()
} else {
toast(R.string.toast_fragment_not_available)
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_BUTTON_B) {
moveTaskToBack(false)
@@ -402,6 +402,17 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
reloadServerList()
}
fun findSubscriptionIdBySelect(): String? {
// Get the selected server GUID
val selectedGuid = MmkvManager.getSelectServer()
if (selectedGuid.isNullOrEmpty()) {
return null
}
val config = MmkvManager.decodeServerConfig(selectedGuid)
return config?.subscriptionId
}
fun onTestsFinished() {
viewModelScope.launch(Dispatchers.Default) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_AUTO_REMOVE_INVALID_AFTER_TEST)) {
@@ -468,4 +479,4 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
}
}
}
}
}
@@ -96,6 +96,18 @@
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/padding_spacing_dp16">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_locate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginBottom="@dimen/view_height_dp120"
android:clickable="true"
android:contentDescription="@string/title_server"
android:focusable="true"
android:src="@android:drawable/ic_menu_mylocation"
app:layout_anchorGravity="bottom|right|end" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
@@ -299,6 +299,8 @@
<string name="title_update_config_count">Update %d configurations</string>
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<string name="title_update_subscription_no_subscription">No subscriptions</string>
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
<string name="toast_fragment_not_available">Unable to locate current view</string>
<string name="tasker_start_service">بدء الخدمة</string>
<string name="tasker_setting_confirm">تأكيد</string>
@@ -298,6 +298,8 @@
<string name="title_update_config_count">Update %d configurations</string>
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<string name="title_update_subscription_no_subscription">No subscriptions</string>
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
<string name="toast_fragment_not_available">Unable to locate current view</string>
<string name="tasker_start_service">সার্ভিস শুরু করুন</string>
<string name="tasker_setting_confirm">নিশ্চিত করুন</string>
@@ -299,6 +299,8 @@
<string name="title_update_config_count">ورۊ کردن %d کانفیگ</string>
<string name="title_update_subscription_result">%1$d کانفیگ ورۊ وابی (مووفق: %2$d، شکست خرده: %3$d، رڌ وابیڌه: %4$d)</string>
<string name="title_update_subscription_no_subscription">بؽ اشتراک</string>
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
<string name="toast_fragment_not_available">Unable to locate current view</string>
<string name="tasker_start_service">ره وندن خدمات</string>
<string name="tasker_setting_confirm">قوۊل</string>
@@ -296,6 +296,8 @@
<string name="title_update_config_count">آپدیت کردن %d کانفیگ</string>
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<string name="title_update_subscription_no_subscription">No subscriptions</string>
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
<string name="toast_fragment_not_available">Unable to locate current view</string>
<string name="tasker_start_service">شروع خدمات</string>
<string name="tasker_setting_confirm">تایید</string>
@@ -297,6 +297,8 @@
<string name="title_update_config_count">Обновлено профилей: %d</string>
<string name="title_update_subscription_result">Обновлено профилей: %1$d (успешно: %2$d, ошибок: %3$d, пропущено: %4$d)</string>
<string name="title_update_subscription_no_subscription">Нет подписок</string>
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
<string name="toast_fragment_not_available">Unable to locate current view</string>
<string name="tasker_start_service">Запуск службы</string>
<string name="tasker_setting_confirm">Подтвердить</string>
@@ -299,6 +299,8 @@
<string name="title_update_config_count">Update %d configurations</string>
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<string name="title_update_subscription_no_subscription">No subscriptions</string>
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
<string name="toast_fragment_not_available">Unable to locate current view</string>
<string name="tasker_start_service">Khởi động v2rayNG</string>
<string name="tasker_setting_confirm">Xác nhận</string>
@@ -297,6 +297,8 @@
<string name="title_update_config_count">更新 %d 个配置</string>
<string name="title_update_subscription_result">更新了 %1$d 个配置(%2$d 个成功,%3$d 个失败,%4$d 个跳过)</string>
<string name="title_update_subscription_no_subscription">无订阅</string>
<string name="toast_server_not_found_in_group">当前分组中未找到选中的服务器</string>
<string name="toast_fragment_not_available">无法定位当前视图</string>
<string name="tasker_start_service">启动服务</string>
<string name="tasker_setting_confirm">确定</string>
@@ -297,6 +297,8 @@
<string name="title_update_config_count">更新 %d 個配置</string>
<string name="title_update_subscription_result">更新了 %1$d 個配置(%2$d 個成功,%3$d 個失敗,%4$d 個跳過)</string>
<string name="title_update_subscription_no_subscription">無訂閱</string>
<string name="toast_server_not_found_in_group">當前分組中未找到選中的伺服器</string>
<string name="toast_fragment_not_available">無法定位當前視圖</string>
<string name="tasker_start_service">啟動服務</string>
<string name="tasker_setting_confirm">確定</string>
@@ -7,5 +7,6 @@
<dimen name="view_height_dp36">36dp</dimen>
<dimen name="view_height_dp48">48dp</dimen>
<dimen name="view_height_dp64">64dp</dimen>
<dimen name="view_height_dp120">120dp</dimen>
<dimen name="view_height_dp160">160dp</dimen>
</resources>
@@ -303,6 +303,8 @@
<string name="title_update_config_count">Update %d configs</string>
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
<string name="title_update_subscription_no_subscription">No subscriptions</string>
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
<string name="toast_fragment_not_available">Unable to locate current view</string>
<string name="tasker_start_service">Start Service</string>
<string name="tasker_setting_confirm">Confirm</string>