2 Commits

Author SHA1 Message Date
zarazaex69 b30fc13b0d fix: race cond errors 2026-05-07 17:13:24 +03:00
zarazaex69 a9f5844b84 style: change selected item styling to transparent bg with primary
border
2026-05-07 16:51:02 +03:00
6 changed files with 70 additions and 59 deletions
@@ -612,18 +612,19 @@ object AngConfigManager {
Log.i(AppConfig.TAG, url)
val userAgent = it.subscription.userAgent
val timeout = if (url.startsWith("https://key.zarazaex.xyz/sub")) 3000 else 6000
val proxyTimeout = if (url.startsWith("https://key.zarazaex.xyz/sub")) 3000 else 5000
val directTimeout = if (url.startsWith("https://key.zarazaex.xyz/sub")) 3000 else 11000
var configText = try {
val httpPort = SettingsManager.getHttpPort()
HttpUtil.getUrlContentWithUserAgent(url, userAgent, timeout, httpPort)
HttpUtil.getUrlContentWithUserAgent(url, userAgent, proxyTimeout, httpPort)
} catch (e: Exception) {
Log.e(AppConfig.ANG_PACKAGE, "Update subscription: proxy not ready or other error", e)
""
}
if (configText.isEmpty()) {
configText = try {
HttpUtil.getUrlContentWithUserAgent(url, userAgent, timeout)
HttpUtil.getUrlContentWithUserAgent(url, userAgent, directTimeout)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Update subscription: Failed to get URL content with user agent", e)
""
@@ -56,6 +56,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
private var isLiteTesting = false
private var easterEggClickCount = 0
private var isEasterEggActive = false
private var liteActionJob: kotlinx.coroutines.Job? = null
/** Был ли VPN уже запущен в предыдущем колбэке — чтобы детектировать момент подключения */
private var wasRunning = false
@@ -318,15 +319,19 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
private fun handleFabAction() {
// Если идёт подключение (isLoading) — позволяем прервать и остановить сервис
if (isFabOperationInProgress) {
Log.d(AppConfig.TAG, "FAB: cancel in-progress, stopping service")
isFabOperationInProgress = false
lifecycleScope.launch {
V2RayServiceManager.stopVService(this@MainActivity)
}
return
}
isFabOperationInProgress = true
val isRunning = mainViewModel.isRunning.value == true
// Блокируем все кнопки сразу
setButtonsEnabled(false)
applyRunningState(isLoading = true, isRunning = false)
lifecycleScope.launch {
@@ -357,11 +362,20 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
private fun handleLiteAction() {
// If testing is in progress - stop it
if (mainViewModel.isTesting.value == true) {
// Отмена на любом этапе: обновление подписок или тест
if (mainViewModel.isTesting.value == true || liteActionJob?.isActive == true) {
liteActionJob?.cancel()
liteActionJob = null
mainViewModel.cancelAllTests()
showStatus("Тест остановлен")
isLiteTesting = false
isFabOperationInProgress = false
showStatus("Остановлено")
setButtonsEnabled(true)
binding.btnSummaryLite.setIconResource(R.drawable.bolt_24)
binding.btnSummaryLite.backgroundTintList = ColorStateList.valueOf(
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0)
)
hideLoading()
return
}
@@ -370,10 +384,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
isFabOperationInProgress = true
// Блокируем все кнопки сразу при нажатии
setButtonsEnabled(false)
lifecycleScope.launch {
liteActionJob = lifecycleScope.launch {
try {
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this@MainActivity)
@@ -382,33 +393,36 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
showStatus("Обновление профилей...")
showLoading()
// Иконка молнии → стоп пока идёт обновление
binding.btnSummaryLite.setIconResource(R.drawable.ic_stop_24dp)
isLiteTesting = true
launch(Dispatchers.IO) {
val result = mainViewModel.updateConfigViaSubAll()
val removed = mainViewModel.removeDuplicateByIpAll()
withContext(Dispatchers.Main) {
mainViewModel.reloadServerList()
if (result.configCount > 0) {
val status = if (removed > 0)
"Обновлено ${result.configCount} профилей, удалено $removed дубл. IP. Запуск теста..."
else
"Обновлено ${result.configCount} профилей. Запуск теста..."
showStatus(status)
} else {
showStatus("Запуск теста...")
}
hideLoading()
val result = withContext(Dispatchers.IO) { mainViewModel.updateConfigViaSubAll() }
val removed = withContext(Dispatchers.IO) { mainViewModel.removeDuplicateByIpAll() }
showStatus("Выполняется замер задержки. Ожидаем завершения...")
mainViewModel.testAllRealPing()
}
mainViewModel.reloadServerList()
if (result.configCount > 0) {
val status = if (removed > 0)
"Обновлено ${result.configCount} профилей, удалено $removed дубл. IP. Запуск теста..."
else
"Обновлено ${result.configCount} профилей. Запуск теста..."
showStatus(status)
} else {
showStatus("Запуск теста...")
}
delay(1500)
hideLoading()
showStatus("Выполняется замер задержки. Ожидаем завершения...")
mainViewModel.testAllRealPing()
} catch (e: kotlinx.coroutines.CancellationException) {
// Пользователь нажал стоп — уже обработано выше
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Error in handleLiteAction", e)
isLiteTesting = false
hideLoading()
} finally {
isFabOperationInProgress = false
liteActionJob = null
}
}
}
@@ -493,7 +507,15 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
com.google.android.material.color.MaterialColors.getColor(this, com.google.android.material.R.attr.colorSecondaryContainer, 0)
)
if (isLoading) {
setButtonsEnabled(false)
// Во время загрузки FAB и молния остаются доступны для отмены
binding.fab.isEnabled = true
binding.fab.alpha = 1.0f
val menu = binding.toolbar.menu
menu.findItem(R.id.real_ping_all)?.let { it.isEnabled = false; it.icon?.alpha = 128 }
menu.findItem(R.id.filter_by_country)?.let { it.isEnabled = false; it.icon?.alpha = 128 }
menu.findItem(R.id.sub_update)?.let { it.isEnabled = false; it.icon?.alpha = 128 }
binding.btnSummaryLite.isEnabled = true
binding.btnSummaryLite.alpha = 1.0f
binding.fab.backgroundTintList = secContainer
setStatusDot(DotState.LOADING)
return
@@ -5,7 +5,6 @@ import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.graphics.ColorUtils
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.color.MaterialColors
@@ -184,31 +183,18 @@ class MainRecyclerAdapter(
(holder.itemMainBinding.tvTestResult.layoutParams as? ViewGroup.MarginLayoutParams)?.marginStart =
if (addressText.isEmpty()) 0 else 6.dpToPx(context)
// Keep selected state very quiet: only a soft surface step, no hard outline.
// Keep regular list items on the page surface; selected state is a quiet surface pill.
val isSelected = guid == MmkvManager.getSelectServer()
holder.itemMainBinding.cardContainer.apply {
val surfaceColor = MaterialColors.getColor(
context,
com.google.android.material.R.attr.colorSurfaceContainerLow,
Color.TRANSPARENT
)
val selectedSurfaceColor = MaterialColors.getColor(
val selectedColor = MaterialColors.getColor(
context,
com.google.android.material.R.attr.colorSurfaceContainerHigh,
surfaceColor
Color.TRANSPARENT
)
setCardBackgroundColor(if (isSelected) selectedSurfaceColor else surfaceColor)
strokeWidth = if (isSelected) 1.dpToPx(context) else 0
strokeColor = if (isSelected) {
MaterialColors.getColor(
context,
com.google.android.material.R.attr.colorOutlineVariant,
Color.TRANSPARENT
)
} else {
Color.TRANSPARENT
}
setCardBackgroundColor(if (isSelected) selectedColor else Color.TRANSPARENT)
strokeWidth = 0
strokeColor = Color.TRANSPARENT
}
// subscription remarks
@@ -95,9 +95,11 @@
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="56dp"
android:minHeight="56dp"
android:background="?attr/colorSurface"
app:tabIndicatorFullWidth="true"
app:tabIndicatorHeight="3dp"
app:tabMode="fixed"
app:tabGravity="fill"
app:tabMaxWidth="0dp"
@@ -5,16 +5,16 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingTop="5dp"
android:paddingTop="6dp"
android:paddingEnd="12dp"
android:paddingBottom="5dp"
android:paddingBottom="6dp"
android:gravity="center_vertical">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="?attr/colorSurfaceContainerLow"
app:cardBackgroundColor="@android:color/transparent"
app:cardCornerRadius="14dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
@@ -31,9 +31,9 @@
android:nextFocusRight="@+id/layout_share"
android:orientation="horizontal"
android:paddingStart="0dp"
android:paddingTop="11dp"
android:paddingTop="13dp"
android:paddingEnd="4dp"
android:paddingBottom="11dp">
android:paddingBottom="13dp">
<LinearLayout
android:layout_width="0dp"
+1 -1
View File
@@ -144,7 +144,7 @@
<!-- Tab label -->
<style name="TabLayoutTextStyle" parent="TextAppearance.Material3.LabelLarge">
<item name="android:textSize">13sp</item>
<item name="android:textSize">14sp</item>
</style>
</resources>