fix: множество исправлений UI и логики

- Адаптивная иконка: пересоздана с safe-zone отступами (62%) для всех плотностей — лого не обрезается кругом на Android 16
- Строгий таймаут 6 сек: measureOutboundDelayInternal теперь использует context с таймаутом — больше нет бесконечных зависаний пинга
- Индикатор теста: 'Проверено успешно: X / Y' вместо 'Number of running test tasks: left / total'
- Текст начала теста: 'Тестирование N серверов' вместо 'Testing N configs'
- Цвет прогресс-бара: адаптирован к теме (md_theme_primary) вместо оранжевого
- Все диалоги переведены на MaterialAlertDialogBuilder — скруглённые углы, Material3 стиль
- Кнопка FAB '>': блокирует ВСЕ кнопки сразу при нажатии (не только себя)
- Кнопка молнии: блокирует всё сразу при нажатии, а не после обновления списков
- 'Подключаемся к быстрейшему серверу': блокирует UI до завершения подключения
- sub_update кнопка: блокирует интерфейс на время обновления, разблокирует по завершении
- isRunning: applyRunningState теперь явно разблокирует/блокирует кнопки при любом переходе состояния
- Обновление подписок через VPN: при каждом подключении автоматически обновляем подписки через VPN-прокси (через 2 сек после старта)
This commit is contained in:
zarazaex69
2026-05-06 00:02:38 +03:00
parent 5123d996f4
commit 9f5f3580e0
11 changed files with 67 additions and 17 deletions
@@ -135,8 +135,7 @@ class RealPingWorkerService(
private fun sendBatchUpdate(update: PingProgressUpdate) {
MessageUtil.sendMsg2UI(context, AppConfig.MSG_MEASURE_CONFIG_BATCH, update)
val left = (update.total - update.finished).coerceAtLeast(0)
MessageUtil.sendMsg2UI(context, AppConfig.MSG_MEASURE_CONFIG_NOTIFY, "$left / ${update.total}")
MessageUtil.sendMsg2UI(context, AppConfig.MSG_MEASURE_CONFIG_NOTIFY, "${update.finished} / ${update.total}")
}
fun cancel() {
@@ -2,7 +2,7 @@ package xyz.zarazaex.olc.ui
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.lifecycle.lifecycleScope
import xyz.zarazaex.olc.AppConfig
import xyz.zarazaex.olc.BuildConfig
@@ -64,7 +64,7 @@ class CheckUpdateActivity : BaseActivity() {
private fun showUpdateDialog(result: CheckUpdateResult) {
val message = result.releaseNotes?.let { MarkdownUtil.parseBasic(it) } ?: ""
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
.setMessage(message)
.setPositiveButton(R.string.update_now) { _, _ ->
@@ -72,7 +72,7 @@ class CheckUpdateActivity : BaseActivity() {
Utils.openUri(this, it)
}
}
.setNegativeButton(android.R.string.cancel, null)
.setNegativeButton("Позже", null)
.show()
}
}
@@ -13,8 +13,8 @@ import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
@@ -51,6 +51,8 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
private var isLiteTesting = false
private var easterEggClickCount = 0
private var isEasterEggActive = false
/** Был ли VPN уже запущен в предыдущем колбэке — чтобы детектировать момент подключения */
private var wasRunning = false
val mainViewModel: MainViewModel by viewModels()
private lateinit var groupPagerAdapter: GroupPagerAdapter
@@ -209,15 +211,24 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
MmkvManager.setSelectServer(firstReachable.guid)
mainViewModel.reloadServerList() // reload AFTER selection so indicator renders correctly
showStatus("Подключаемся к быстрейшему серверу")
// Блокируем кнопки на время подключения
setButtonsEnabled(false)
applyRunningState(isLoading = true, isRunning = false)
startV2RayWithPermission()
} else {
mainViewModel.reloadServerList()
showStatus("Нет доступных серверов!")
setButtonsEnabled(true)
}
}
}
mainViewModel.isRunning.observe(this) { isRunning ->
applyRunningState(false, isRunning)
// Как только VPN только что подключился — обновляем подписки через него
if (isRunning && !wasRunning) {
updateSubsViaVpn()
}
wasRunning = isRunning
}
mainViewModel.startListenBroadcast()
mainViewModel.initAssets(assets)
@@ -280,6 +291,8 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
val isRunning = mainViewModel.isRunning.value == true
// Блокируем все кнопки сразу
setButtonsEnabled(false)
applyRunningState(isLoading = true, isRunning = false)
lifecycleScope.launch {
@@ -294,6 +307,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
} catch (e: Exception) {
Log.e(AppConfig.TAG, "FAB: error", e)
applyRunningState(isLoading = false, isRunning = mainViewModel.isRunning.value == true)
setButtonsEnabled(true)
} finally {
isFabOperationInProgress = false
}
@@ -323,6 +337,9 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
isFabOperationInProgress = true
// Блокируем все кнопки сразу при нажатии
setButtonsEnabled(false)
lifecycleScope.launch {
try {
if (mainViewModel.isRunning.value == true) {
@@ -438,10 +455,11 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
return ColorStateList.valueOf(color)
}
private fun applyRunningState(isLoading: Boolean, isRunning: Boolean) {
private fun applyRunningState(isLoading: Boolean, isRunning: Boolean) {
if (isLoading) {
binding.fab.isEnabled = false
binding.fab.alpha = 0.5f
setButtonsEnabled(false)
return
}
@@ -456,6 +474,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
// Молния всегда активна — пользователь может запустить тест даже при работающем VPN
binding.btnSummaryLite.isEnabled = true
binding.btnSummaryLite.alpha = 1.0f
setButtonsEnabled(true)
} else {
binding.fab.isEnabled = true
binding.fab.alpha = 1.0f
@@ -466,6 +485,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
binding.layoutTest.isFocusable = false
binding.btnSummaryLite.isEnabled = true
binding.btnSummaryLite.alpha = 1.0f
setButtonsEnabled(true)
}
}
@@ -519,6 +539,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
R.id.sub_update -> {
setButtonsEnabled(false)
importConfigViaSub()
true
}
@@ -617,6 +638,29 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
return true
}
/**
* Обновляет подписки через уже поднятый VPN (httpPort > 0).
* Вызывается сразу после того, как VPN перешёл в состояние isRunning = true.
*/
private fun updateSubsViaVpn() {
lifecycleScope.launch(Dispatchers.IO) {
// Даём VPN пару секунд инициализироваться
delay(2000)
Log.d(AppConfig.TAG, "updateSubsViaVpn: starting post-connect subscription update")
val result = mainViewModel.updateConfigViaSubAll()
if (result.configCount > 0) {
val removed = mainViewModel.removeDuplicateByIpAll()
withContext(Dispatchers.Main) {
mainViewModel.reloadServerList()
val msg = if (removed > 0)
"Подписки обновлены: ${result.configCount} профилей, удалено $removed дубл. IP"
else
"Подписки обновлены: ${result.configCount} профилей"
showStatus(msg)
}
}
}
}
private fun importAllSubsOnStartup() {
showLoading()
@@ -663,6 +707,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
mainViewModel.reloadServerList()
}
hideLoading()
setButtonsEnabled(true)
}
}
return true
@@ -683,7 +728,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
private fun delAllConfig() {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
MaterialAlertDialogBuilder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
showLoading()
lifecycleScope.launch(Dispatchers.IO) {
@@ -702,7 +747,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
private fun delDuplicateConfig() {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
MaterialAlertDialogBuilder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
showLoading()
lifecycleScope.launch(Dispatchers.IO) {
@@ -855,7 +900,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
val checked = BooleanArray(codes.size) { codes[it] in excludedByFilter }
androidx.appcompat.app.AlertDialog.Builder(this@MainActivity)
MaterialAlertDialogBuilder(this@MainActivity)
.setTitle("Исключить страны")
.setMultiChoiceItems(labels, checked) { _, which, isChecked ->
checked[which] = isChecked
@@ -874,7 +919,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
mainViewModel.applyCountryFilter(emptySet())
showStatus("Показаны все страны")
}
.setNegativeButton(android.R.string.cancel, null)
.setNegativeButton("Отмена", null)
.show()
}
}
@@ -899,7 +944,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
private fun showUpdateAvailableDialog(result: xyz.zarazaex.olc.dto.CheckUpdateResult) {
val message = result.releaseNotes?.let { xyz.zarazaex.olc.util.MarkdownUtil.parseBasic(it) } ?: ""
AlertDialog.Builder(this)
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.update_new_version_found, result.latestVersion))
.setMessage(message)
.setPositiveButton(R.string.update_now) { _, _ ->
@@ -907,7 +952,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
Utils.openUri(this, it)
}
}
.setNegativeButton(android.R.string.cancel, null)
.setNegativeButton("Позже", null)
.show()
}
@@ -47,7 +47,7 @@
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="invisible"
app:indicatorColor="@color/color_fab_active" />
app:indicatorColor="@color/md_theme_primary" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_group"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+2 -2
View File
@@ -341,14 +341,14 @@
<string name="connection_test_pending">Check Connectivity</string>
<string name="connection_test_testing">Testing…</string>
<string name="connection_test_testing_count">Testing %d configs</string>
<string name="connection_test_testing_count">Тестирование %d серверов</string>
<string name="connection_test_available">Success: Connection took %dms</string>
<string name="connection_test_error">Fail to detect internet connection: %s</string>
<string name="connection_test_fail">Internet Unavailable</string>
<string name="connection_test_error_status_code">Error code: #%d</string>
<string name="connection_connected">Connected, tap to check connection</string>
<string name="connection_not_connected">Готово к подключению</string>
<string name="connection_runing_task_left">Number of running test tasks: %s</string>
<string name="connection_runing_task_left">Проверено успешно: %s</string>
<string name="import_subscription_success">Subscription imported Successfully</string>
<string name="import_subscription_failure">Import subscription failed</string>