Update v1.0.7

This commit is contained in:
amurcanov
2026-04-14 02:25:23 +03:00
parent d1ca465f83
commit a43d61bc38
9 changed files with 693 additions and 394 deletions
@@ -24,11 +24,23 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.amurcanov.tgwsproxy.R
import kotlin.math.roundToInt
import androidx.compose.ui.draw.scale
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.draw.clip
import androidx.compose.foundation.border
import androidx.compose.ui.graphics.Color
import android.os.Build
@Composable
fun FloatingToolbar(
currentTheme: String,
onThemeChange: (String) -> Unit,
isDynamicColor: Boolean,
onDynamicColorChange: (Boolean) -> Unit,
currentPalette: String,
onPaletteChange: (String) -> Unit,
modifier: Modifier = Modifier
) {
val configuration = LocalConfiguration.current
@@ -46,7 +58,7 @@ fun FloatingToolbar(
val tabWidthDp = 42.dp
val tabHeightDp = 52.dp
val panelWidthDp = 180.dp
val panelWidthDp = 220.dp
val tabWidthPx = remember(density) { with(density) { tabWidthDp.toPx() } }
@@ -143,6 +155,52 @@ fun FloatingToolbar(
selected = currentTheme == "dark",
onClick = { onThemeChange("dark"); isExpanded = false }
)
Divider(modifier = Modifier.padding(vertical = 4.dp), color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
val supportsDynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val showDynamicColorOn = isDynamicColor && supportsDynamicColor
val showPalettes = !showDynamicColorOn
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Динамические",
style = MaterialTheme.typography.bodySmall,
fontWeight = FontWeight.Medium,
color = if (supportsDynamicColor) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
)
Switch(
checked = showDynamicColorOn,
onCheckedChange = { onDynamicColorChange(it) },
enabled = supportsDynamicColor,
modifier = Modifier.scale(0.8f)
)
}
AnimatedVisibility(visible = showPalettes) {
Column {
Divider(modifier = Modifier.padding(vertical = 4.dp), color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f))
Text(
"Палитра",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(bottom = 6.dp, start = 4.dp)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
PaletteCircle("indigo", 0xFF5B588D, currentPalette, onPaletteChange)
PaletteCircle("forest", 0xFF5F5D68, currentPalette, onPaletteChange)
PaletteCircle("espresso", 0xFF6D4C41, currentPalette, onPaletteChange)
}
Spacer(modifier = Modifier.height(6.dp))
}
}
}
}
}
@@ -185,3 +243,23 @@ private fun ThemeOption(
}
}
}
@Composable
fun PaletteCircle(
paletteId: String,
colorHex: Long,
selectedId: String,
onClick: (String) -> Unit
) {
val isSelected = paletteId == selectedId
Box(
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
.background(Color(colorHex))
.clickable { onClick(paletteId) }
.then(
if (isSelected) Modifier.border(3.dp, MaterialTheme.colorScheme.primary, CircleShape)
else Modifier
)
)
}
@@ -125,7 +125,7 @@ private fun isNewerVersion(local: String, remote: String): Boolean {
@Composable
fun InfoTab() {
val currentVersion = "v1.0.6"
val currentVersion = "v1.0.7"
val scope = rememberCoroutineScope()
var updateResult by remember { mutableStateOf<UpdateCheckResult>(UpdateCheckResult.Idle) }
@@ -271,7 +271,8 @@ fun InfoTab() {
}
}
// ═══ Справка ═══
HelpCard()
Spacer(modifier = Modifier.height(16.dp))
}
@@ -320,3 +321,68 @@ private fun GitHubSection(
}
}
}
@Composable
private fun HelpCard() {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
) {
Column(
modifier = Modifier.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
Text(
"Справка",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
color = MaterialTheme.colorScheme.onSurface
)
HelpSection(
title = "Авто / Адреса датацентров",
text = "При включенном CloudFlare сервера (DC) настраивать не нужно — они переадресовываются автоматически. Если вы отключите CloudFlare, нажмите «Настроить адреса DC» для прямого подключения. По умолчанию DC4 зафиксирован на стабильном лондонском узле (149.154.167.220)."
)
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
HelpSection(
title = "CloudFlare CDN",
text = "Ваш трафик маскируется под HTTPS WebSockets внутри сети Cloudflare. Это способствует лучшему обходу блокировок на мобильных сетях. При использовании Wi-Fi этот режим можно отключать для повышения скорости. Делает блокировку прокси почти невозможной для DPI."
)
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
HelpSection(
title = "Пул WS",
text = "Механизм многопоточности (фоновые соединения). По умолчанию: 4 потока. Если видео или медиафайлы грузятся медленно, попробуйте увеличить пул."
)
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
HelpSection(
title = "Секретный ключ",
text = "Специальный 16-байтовый ключ шифрования MTProto. Меняйте его только в случае, если старой ссылкой для подключения завладели посторонние."
)
}
}
}
@Composable
private fun HelpSection(title: String, text: String) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
title,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
Text(
text,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
lineHeight = 20.sp
)
}
}
@@ -20,6 +20,9 @@ import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material.icons.filled.VpnKey
import androidx.compose.material.icons.filled.Public
import androidx.compose.material.icons.filled.Layers
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@@ -100,6 +103,8 @@ fun SettingsTab(settingsStore: SettingsStore) {
val savedPort by settingsStore.port.collectAsStateWithLifecycle(initialValue = "1443")
val savedPoolSize by settingsStore.poolSize.collectAsStateWithLifecycle(initialValue = 4)
val savedCfEnabled by settingsStore.cfproxyEnabled.collectAsStateWithLifecycle(initialValue = true)
val savedCustomDomainEnabled by settingsStore.customCfDomainEnabled.collectAsStateWithLifecycle(initialValue = false)
val savedCustomDomain by settingsStore.customCfDomain.collectAsStateWithLifecycle(initialValue = "")
val savedSecretKey by settingsStore.secretKey.collectAsStateWithLifecycle(initialValue = "LOADING")
var isDcAuto by rememberSaveable(savedIsDcAuto) { mutableStateOf(savedIsDcAuto) }
@@ -108,6 +113,8 @@ fun SettingsTab(settingsStore: SettingsStore) {
var portText by rememberSaveable(savedPort) { mutableStateOf(savedPort) }
var selectedPoolSize by rememberSaveable(savedPoolSize) { mutableIntStateOf(savedPoolSize) }
var cfEnabled by rememberSaveable(savedCfEnabled) { mutableStateOf(savedCfEnabled) }
var customCfDomainEnabled by rememberSaveable(savedCustomDomainEnabled) { mutableStateOf(savedCustomDomainEnabled) }
var customCfDomain by rememberSaveable(savedCustomDomain) { mutableStateOf(savedCustomDomain) }
var secretKeyText by remember(savedSecretKey) { mutableStateOf(if (savedSecretKey == "LOADING") "" else savedSecretKey) }
LaunchedEffect(savedSecretKey) {
@@ -128,19 +135,16 @@ fun SettingsTab(settingsStore: SettingsStore) {
delay(300)
settingsStore.saveAll(
isDcAuto, dc2Text, dc4Text, portText, selectedPoolSize,
cfEnabled, secretKeyText
cfEnabled, customCfDomainEnabled, customCfDomain, secretKeyText
)
}
}
var showIpSetupDialog by rememberSaveable { mutableStateOf(false) }
var showHelpDialog by rememberSaveable { mutableStateOf(false) }
val scrollState = rememberScrollState()
if (showIpSetupDialog) {
IpSetupDialog(
isDcAuto = isDcAuto,
onModeChange = { isDcAuto = it; scheduleSave() },
dc2Text = dc2Text,
onDc2Change = { dc2Text = it; scheduleSave() },
dc4Text = dc4Text,
@@ -149,17 +153,19 @@ fun SettingsTab(settingsStore: SettingsStore) {
)
}
if (showHelpDialog) {
HelpDialog(onDismiss = { showHelpDialog = false })
}
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Настройки",
style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
)
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
@@ -167,23 +173,27 @@ fun SettingsTab(settingsStore: SettingsStore) {
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"Подключение",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold
)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Default.Public, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp))
Text(
"Подключение",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold
)
}
OutlinedTextField(
value = portText,
onValueChange = { portText = it; scheduleSave() },
label = { Text("Порт") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
singleLine = true,
modifier = Modifier.fillMaxWidth().height(60.dp),
modifier = Modifier.fillMaxWidth().height(52.dp),
shape = RoundedCornerShape(14.dp),
textStyle = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Medium),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.primary,
unfocusedBorderColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
@@ -191,16 +201,20 @@ fun SettingsTab(settingsStore: SettingsStore) {
)
OutlinedButton(
onClick = { showIpSetupDialog = true },
modifier = Modifier.fillMaxWidth().height(46.dp),
enabled = !cfEnabled && !isRunning,
modifier = Modifier.fillMaxWidth().height(40.dp),
shape = RoundedCornerShape(14.dp),
colors = ButtonDefaults.outlinedButtonColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
disabledContainerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
contentColor = MaterialTheme.colorScheme.primary,
disabledContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.5f))
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = if (cfEnabled || isRunning) 0.2f else 0.5f))
) {
Icon(Icons.Default.Settings, null, Modifier.size(18.dp))
Spacer(Modifier.width(8.dp))
Text("Настроить адреса DC", fontWeight = FontWeight.SemiBold)
Text(if (cfEnabled) "Авто" else "Настроить адреса DC", fontWeight = FontWeight.SemiBold)
}
}
}
@@ -212,8 +226,8 @@ fun SettingsTab(settingsStore: SettingsStore) {
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -230,7 +244,7 @@ fun SettingsTab(settingsStore: SettingsStore) {
modifier = Modifier.size(20.dp)
)
Text(
"CloudFlare",
"CloudFlare CDN",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold
@@ -238,19 +252,14 @@ fun SettingsTab(settingsStore: SettingsStore) {
}
Switch(
checked = cfEnabled,
onCheckedChange = { cfEnabled = it; scheduleSave() },
onCheckedChange = {
cfEnabled = it
isDcAuto = it
scheduleSave()
},
enabled = !isRunning
)
}
Text(
if (cfEnabled)
"Трафик проксируется через CloudFlare — улучшает обход блокировок."
else
"Подключение к DC Telegram напрямую.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
lineHeight = 16.sp
)
}
}
@@ -261,15 +270,18 @@ fun SettingsTab(settingsStore: SettingsStore) {
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
modifier = Modifier.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"Пул WS",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold
)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Default.Layers, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(20.dp))
Text(
"Пул WS",
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.SemiBold
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
@@ -286,7 +298,7 @@ fun SettingsTab(settingsStore: SettingsStore) {
}
}
}
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f))
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f), modifier = Modifier.padding(vertical = 4.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
@@ -308,7 +320,7 @@ fun SettingsTab(settingsStore: SettingsStore) {
onValueChange = {},
readOnly = true,
singleLine = true,
modifier = Modifier.fillMaxWidth().height(56.dp),
modifier = Modifier.fillMaxWidth().height(52.dp),
shape = RoundedCornerShape(14.dp),
textStyle = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Medium),
trailingIcon = {
@@ -364,7 +376,7 @@ fun SettingsTab(settingsStore: SettingsStore) {
scope.launch {
settingsStore.saveAll(
isDcAuto, dc2Text, dc4Text, portText, selectedPoolSize,
cfEnabled, secretKeyText
cfEnabled, customCfDomainEnabled, customCfDomain, secretKeyText
)
}
val startIntent = Intent(context, ProxyService::class.java).apply {
@@ -373,15 +385,14 @@ fun SettingsTab(settingsStore: SettingsStore) {
putExtra(ProxyService.EXTRA_IPS, parsedIps)
putExtra(ProxyService.EXTRA_POOL_SIZE, selectedPoolSize)
putExtra(ProxyService.EXTRA_CFPROXY_ENABLED, cfEnabled)
// ProxyService intent expects these even if CF priority is disabled in UI
putExtra(ProxyService.EXTRA_CFPROXY_PRIORITY, true)
putExtra(ProxyService.EXTRA_CFPROXY_DOMAIN, "")
putExtra(ProxyService.EXTRA_CFPROXY_DOMAIN, if (customCfDomainEnabled && cfEnabled) customCfDomain.trim() else "")
putExtra(ProxyService.EXTRA_SECRET_KEY, secretKeyText.trim())
}
ContextCompat.startForegroundService(context, startIntent)
}
},
modifier = Modifier.fillMaxWidth().height(50.dp),
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = buttonColor)
) {
@@ -402,7 +413,7 @@ fun SettingsTab(settingsStore: SettingsStore) {
val raw = secretKeyText.trim()
if (raw.isNotEmpty()) raw else "00000000000000000000000000000000"
}
val proxyUrl = "tg://proxy?server=127.0.0.1&port=$port&secret=ee$secretForUrl"
val proxyUrl = "tg://proxy?server=127.0.0.1&port=$port&secret=dd$secretForUrl"
val telegramBtnColor by animateColorAsState(
targetValue = if (isRunning) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surfaceVariant,
animationSpec = tween(400),
@@ -411,20 +422,53 @@ fun SettingsTab(settingsStore: SettingsStore) {
Button(
onClick = { openTelegram(context, proxyUrl) },
enabled = isRunning,
modifier = Modifier.fillMaxWidth().height(50.dp),
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(16.dp),
colors = ButtonDefaults.buttonColors(containerColor = telegramBtnColor, contentColor = MaterialTheme.colorScheme.onSurface)
) {
Text("Применить в Telegram", fontWeight = FontWeight.SemiBold)
}
OutlinedButton(
onClick = { showHelpDialog = true },
modifier = Modifier.fillMaxWidth().height(46.dp),
shape = RoundedCornerShape(16.dp),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.4f))
Text(
text = "или",
modifier = Modifier.fillMaxWidth(),
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold),
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Surface(
onClick = {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText("Proxy URL", proxyUrl)
clipboard.setPrimaryClip(clip)
Toast.makeText(context, "Ссылка скопирована", Toast.LENGTH_SHORT).show()
},
shape = RoundedCornerShape(14.dp),
color = androidx.compose.ui.graphics.Color.Transparent,
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)),
modifier = Modifier.fillMaxWidth().height(52.dp)
) {
Text("Пожалуйста ознакомьтесь!", fontWeight = FontWeight.Medium)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)
) {
Text(
text = proxyUrl,
style = MaterialTheme.typography.bodySmall.copy(
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium
),
maxLines = 1,
modifier = Modifier.weight(1f)
)
Icon(
imageVector = Icons.Default.ContentCopy,
contentDescription = "Копировать",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(20.dp)
)
}
}
Spacer(Modifier.height(8.dp))
@@ -456,102 +500,10 @@ private fun PoolChip(
}
}
@Composable
private fun HelpDialog(onDismiss: () -> Unit) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Surface(
shape = RoundedCornerShape(24.dp),
color = MaterialTheme.colorScheme.surface,
tonalElevation = 8.dp,
modifier = Modifier.fillMaxWidth(0.95f)
) {
Column(
modifier = Modifier
.padding(24.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(14.dp)
) {
Text(
"Справка",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
HelpSection(
title = "Адреса датацентров",
text = "Внимание, рекомендую использовать при включенном CloudFlare - Автоматический режим получения DC от самого телеграма. " +
"В случае если вы не пользуетесь CloudFlare или он у вас не работает, переключитесь на ручное использование. " +
"По умолчанию указан DC4 149.154.167.220."
)
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
HelpSection(
title = "Порт",
text = "Локальный порт прокси. Используйте свободный порт. По умолчанию — 1443."
)
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
HelpSection(
title = "CloudFlare",
text = "Проксирует трафик через CloudFlare для обхода блокировок. Если Telegram не подключается — отключите."
)
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
HelpSection(
title = "Пул WS",
text = "Количество фоновых соединений (по умолчанию 4). Увеличьте, если скорость низкая."
)
Divider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.3f))
HelpSection(
title = "Секретный ключ",
text = "Ключ шифрования MTProto. Обновляйте только при необходимости."
)
Spacer(Modifier.height(4.dp))
Button(
onClick = onDismiss,
modifier = Modifier.fillMaxWidth().height(46.dp),
shape = RoundedCornerShape(16.dp)
) {
Text("Понятно", fontWeight = FontWeight.SemiBold)
}
}
}
}
}
@Composable
private fun HelpSection(title: String, text: String) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
title,
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Bold
)
Text(
text,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
lineHeight = 20.sp
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun IpSetupDialog(
isDcAuto: Boolean, onModeChange: (Boolean) -> Unit,
dc2Text: String, onDc2Change: (String) -> Unit,
dc4Text: String, onDc4Change: (String) -> Unit,
onDismiss: () -> Unit
@@ -585,37 +537,8 @@ private fun IpSetupDialog(
fontWeight = FontWeight.Bold
)
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(14.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline.copy(alpha = 0.1f))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (isDcAuto) "Авто DC от Telegram" else "Ручные DC",
style = MaterialTheme.typography.bodyLarge,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.primary
)
Switch(
checked = isDcAuto,
onCheckedChange = { onModeChange(it) }
)
}
}
if (!isDcAuto) {
@Composable
fun dcInput(label: String, value: String, update: (String) -> Unit) {
@Composable
fun dcInput(label: String, value: String, update: (String) -> Unit) {
Text(
label,
style = MaterialTheme.typography.bodySmall,
@@ -638,7 +561,6 @@ private fun IpSetupDialog(
dcInput("DC2", dc2Text, onDc2Change)
dcInput("DC4", dc4Text, onDc4Change)
}
Spacer(Modifier.height(4.dp))
@@ -115,6 +115,92 @@ private val DarkColorScheme = darkColorScheme(
surfaceTint = Color(0xFFD7CCC8),
)
// ═══ Тёмная палитра — «Цвет 1» ═══
private val IndigoLightColorScheme = lightColorScheme(
primary = Color(0xFF5B588D),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFE2DFFF),
onPrimaryContainer = Color(0xFF1A1744),
secondary = Color(0xFF5B588D),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFE2DFFF),
onSecondaryContainer = Color(0xFF1A1744),
background = Color(0xFFFBF8FF),
onBackground = Color(0xFF1B1B1F),
surface = Color(0xFFF6F3FA),
onSurface = Color(0xFF1B1B1F),
surfaceVariant = Color(0xFFE4E1EC),
onSurfaceVariant = Color(0xFF47464F),
outline = Color(0xFF787680),
outlineVariant = Color(0xFFC8C5D0),
)
private val IndigoDarkColorScheme = darkColorScheme(
primary = Color(0xFFC4C0FF),
onPrimary = Color(0xFF2D2A5B),
primaryContainer = Color(0xFF434073),
onPrimaryContainer = Color(0xFFE2DFFF),
secondary = Color(0xFFC4C0FF),
onSecondary = Color(0xFF2D2A5B),
secondaryContainer = Color(0xFF434073),
onSecondaryContainer = Color(0xFFE2DFFF),
background = Color(0xFF131316),
onBackground = Color(0xFFE4E1E6),
surface = Color(0xFF1B1B1F),
onSurface = Color(0xFFC8C5D0),
surfaceVariant = Color(0xFF47464F),
onSurfaceVariant = Color(0xFFC8C5D0),
outline = Color(0xFF918F9A),
outlineVariant = Color(0xFF47464F),
)
// ═══ Палитра «Цвет 2» ═══
private val ForestLightColorScheme = lightColorScheme(
primary = Color(0xFF5F5D68),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFE5E0F0),
onPrimaryContainer = Color(0xFF1C1A23),
secondary = Color(0xFF5F5D68),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFE5E0F0),
onSecondaryContainer = Color(0xFF1C1A23),
background = Color(0xFFFCF8FF),
onBackground = Color(0xFF1D1B20),
surface = Color(0xFFF7F2FA),
onSurface = Color(0xFF1D1B20),
surfaceVariant = Color(0xFFE6E0E9),
onSurfaceVariant = Color(0xFF48454E),
outline = Color(0xFF79747E),
outlineVariant = Color(0xFFCAC4D0),
)
private val ForestDarkColorScheme = darkColorScheme(
primary = Color(0xFFC8C4D3),
onPrimary = Color(0xFF312F38),
primaryContainer = Color(0xFF474550),
onPrimaryContainer = Color(0xFFE5E0F0),
secondary = Color(0xFFC8C4D3),
onSecondary = Color(0xFF312F38),
secondaryContainer = Color(0xFF474550),
onSecondaryContainer = Color(0xFFE5E0F0),
background = Color(0xFF141318),
onBackground = Color(0xFFE6E1E5),
surface = Color(0xFF1D1B20),
onSurface = Color(0xFFCAC4D0),
surfaceVariant = Color(0xFF48454E),
onSurfaceVariant = Color(0xFFCAC4D0),
outline = Color(0xFF938F99),
outlineVariant = Color(0xFF48454E),
)
private fun getAppColorScheme(palette: String, isDark: Boolean): androidx.compose.material3.ColorScheme {
return when(palette) {
"espresso" -> if (isDark) DarkColorScheme else LightColorScheme
"forest" -> if (isDark) ForestDarkColorScheme else ForestLightColorScheme
else -> if (isDark) IndigoDarkColorScheme else IndigoLightColorScheme
}
}
// ═══ Расширенные цвета для кастомных элементов ═══
object AppColors {
val connected = Color(0xFF4CAF50)
@@ -148,6 +234,7 @@ object AppColors {
fun TgWsProxyTheme(
themeMode: String = "system",
dynamicColor: Boolean = true,
themePalette: String = "indigo",
content: @Composable () -> Unit
) {
val darkTheme = when (themeMode) {
@@ -161,8 +248,7 @@ fun TgWsProxyTheme(
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
else -> getAppColorScheme(themePalette, darkTheme)
}
MaterialTheme(