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 сек после старта)
@@ -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"
|
||||
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 10 KiB |
@@ -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>
|
||||
|
||||