package com.wdtt.client.ui import androidx.compose.runtime.MutableState import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build import android.widget.Toast import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.GenericShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.automirrored.filled.HelpOutline import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Code import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Update import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.luminance import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.wdtt.client.BuildConfig import com.wdtt.client.R import com.wdtt.client.SettingsStore import com.wdtt.client.UPDATE_DIALOG_ACTION_POSTPONED import com.wdtt.client.UPDATE_DIALOG_ACTION_UPDATE import com.wdtt.client.WDTTColors import com.wdtt.client.fetchLatestReleaseInfo import com.wdtt.client.isNewerVersion import kotlinx.coroutines.launch import kotlin.math.PI import kotlin.math.cos import kotlin.math.min import kotlin.math.sin private const val ReleasesUrl = "https://github.com/amurcanov/proxy-turn-vk-android/releases" private const val IssuesUrl = "https://github.com/amurcanov/proxy-turn-vk-android/issues/new" private const val DeveloperProfileUrl = "https://github.com/amurcanov" private const val RepositoryUrl = "https://github.com/amurcanov/proxy-turn-vk-android" private const val DonateUrl = "" private val DonateActionButtonColor = Color(0xFF00AEA5) private val browserPackages = listOf( "com.android.chrome", "com.google.android.googlequicksearchbox", "org.mozilla.firefox", "com.yandex.browser", "ru.yandex.searchplugin", "com.yandex.browser.lite", "com.opera.browser", "com.opera.mini.native", "com.microsoft.emmx", "com.brave.browser", "com.duckduckgo.mobile.android", "com.sec.android.app.sbrowser", "com.vivaldi.browser", "com.kiwibrowser.browser", ) private val Android16BlobShape: Shape = GenericShape { size, _ -> val centerX = size.width / 2f val centerY = size.height / 2f val outerRadius = min(size.width, size.height) / 2f val innerRadius = outerRadius * 0.92f val points = 14 for (i in 0 until points * 2) { val angle = (-PI / 2.0) + (i * PI / points) val radius = if (i % 2 == 0) outerRadius else innerRadius val x = centerX + (radius * cos(angle)).toFloat() val y = centerY + (radius * sin(angle)).toFloat() if (i == 0) moveTo(x, y) else lineTo(x, y) } close() } private fun openUrlInBrowser(context: Context, url: String) { try { val pm = context.packageManager val uri = Uri.parse(url) for (pkg in browserPackages) { val intent = Intent(Intent.ACTION_VIEW, uri).apply { addCategory(Intent.CATEGORY_BROWSABLE) setPackage(pkg) } if (intent.resolveActivity(pm) != null) { context.startActivity(intent) return } } val intent = Intent(Intent.ACTION_VIEW, uri).apply { addCategory(Intent.CATEGORY_BROWSABLE) } if (intent.resolveActivity(pm) != null) context.startActivity(intent) } catch (_: Exception) { } } @Composable fun InfoTab( actionsExpandedState: MutableState = rememberSaveable { mutableStateOf(true) }, projectExpandedState: MutableState = rememberSaveable { mutableStateOf(true) } ) { val context = LocalContext.current val scope = rememberCoroutineScope() val settingsStore = remember { SettingsStore(context) } val currentVersion = remember { "v${BuildConfig.VERSION_NAME.removePrefix("v")}" } var isCheckingUpdates by remember { mutableStateOf(false) } var pendingManualRelease by remember { mutableStateOf(null) } var showHelpDialog by remember { mutableStateOf(false) } var showDonateDialog by remember { mutableStateOf(false) } var actionsExpanded by actionsExpandedState var projectExpanded by projectExpandedState val updateLatestVersion by settingsStore.updateLatestVersion.collectAsStateWithLifecycle(initialValue = "") val updateLastError by settingsStore.updateLastError.collectAsStateWithLifecycle(initialValue = "") val updateStatus = remember(isCheckingUpdates, updateLatestVersion, updateLastError, currentVersion) { when { isCheckingUpdates -> "Проверяем GitHub releases..." updateLatestVersion.isNotBlank() && isNewerVersion(currentVersion, updateLatestVersion) -> "На GitHub доступна версия $updateLatestVersion" updateLatestVersion.isNotBlank() -> "Последняя версия: $updateLatestVersion" updateLastError.isNotBlank() -> "Последняя проверка завершилась ошибкой" else -> "Проверить GitHub вручную" } } Column( modifier = Modifier .fillMaxSize() .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 28.dp) .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Row( modifier = Modifier .fillMaxWidth() .height(48.dp), verticalAlignment = Alignment.CenterVertically ) { Text( text = "Информация", style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold), color = MaterialTheme.colorScheme.onSurface ) } InfoHeroCard(currentVersion = currentVersion, onSupportClick = { showDonateDialog = true }) ExpandableSectionCard( title = "Действия", itemCount = "4 пункта", expanded = actionsExpanded, onToggle = { actionsExpanded = !actionsExpanded }, icon = { Icon( imageVector = Icons.Default.Info, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(18.dp) ) } ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { InfoActionTile( title = "Поднять вопрос", subtitle = "Открыть GitHub issue", modifier = Modifier.weight(1f), onClick = { openUrlInBrowser(context, IssuesUrl) }, icon = { Icon( painter = painterResource(id = R.drawable.ic_github), contentDescription = null, modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) } ) InfoActionTile( title = "Собрать отчёт", subtitle = "Android, ABI, версия, устройство", modifier = Modifier.weight(1f), onClick = { val clipboard = context.getSystemService(ClipboardManager::class.java) clipboard?.setPrimaryClip(ClipData.newPlainText("WDTT Report", buildSupportReport())) Toast.makeText(context, "Отчёт сформирован и скопирован", Toast.LENGTH_SHORT).show() }, icon = { Icon( imageVector = Icons.Default.ContentCopy, contentDescription = null, modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) } ) } WideActionTile( title = "Справка", subtitle = "Коротко про VPN, исключения, капчу и запуск", onClick = { showHelpDialog = true }, icon = { Icon( imageVector = Icons.AutoMirrored.Filled.HelpOutline, contentDescription = null, modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) } ) WideActionTile( title = "Проверить обновления", subtitle = updateStatus, onClick = { if (isCheckingUpdates) return@WideActionTile isCheckingUpdates = true scope.launch { val checkedAt = System.currentTimeMillis() val release = fetchLatestReleaseInfo(currentVersion) val latest = release?.versionTag settingsStore.saveUpdateState( lastCheckAt = checkedAt, latestVersion = latest ?: "", error = if (release == null) "Не удалось проверить" else "" ) isCheckingUpdates = false if (release == null) { val message = if (updateLatestVersion.isNotBlank()) { "Не удалось проверить. Последняя известная версия: $updateLatestVersion" } else { "Не удалось проверить обновления" } Toast.makeText(context, message, Toast.LENGTH_SHORT).show() return@launch } if (isNewerVersion(currentVersion, release.versionTag)) { settingsStore.saveUpdateDialogShown(release.versionTag, checkedAt) pendingManualRelease = release } else { Toast.makeText( context, "У вас уже последняя версия: ${release.versionTag}", Toast.LENGTH_SHORT ).show() } } }, icon = { Icon( imageVector = Icons.Default.Update, contentDescription = null, modifier = Modifier.size(20.dp), tint = MaterialTheme.colorScheme.onPrimaryContainer ) } ) } pendingManualRelease?.let { release -> AppUpdateDialog( release = release, onPostpone = { pendingManualRelease = null Toast.makeText(context, "Обновление отложено на 24 часа.", Toast.LENGTH_SHORT).show() scope.launch { val now = System.currentTimeMillis() settingsStore.saveUpdatePostpone( version = release.versionTag, until = now + 24L * 60L * 60L * 1000L ) settingsStore.saveUpdateDialogAction( version = release.versionTag, action = UPDATE_DIALOG_ACTION_POSTPONED, actedAt = now ) } }, onUpdate = { pendingManualRelease = null scope.launch { settingsStore.saveUpdateDialogAction( version = release.versionTag, action = UPDATE_DIALOG_ACTION_UPDATE, actedAt = System.currentTimeMillis() ) openUrlInBrowser(context, release.releaseUrl) } } ) } ExpandableSectionCard( title = "О проекте", itemCount = "3 ссылки", expanded = projectExpanded, onToggle = { projectExpanded = !projectExpanded }, icon = { Icon( imageVector = Icons.Default.Code, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(18.dp) ) } ) { ProjectLinkRow( title = "Автор Android-версии", subtitle = "GitHub профиль amurcanov", onClick = { openUrlInBrowser(context, DeveloperProfileUrl) }, icon = { Icon( imageVector = Icons.Default.Person, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(18.dp) ) } ) ProjectLinkRow( title = "Репозиторий WDTT", subtitle = "Исходники и релизы приложения", onClick = { openUrlInBrowser(context, RepositoryUrl) }, icon = { Icon( painter = painterResource(id = R.drawable.ic_github), contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(18.dp) ) } ) ProjectLinkRow( title = "Актуальные релизы", subtitle = "Страница загрузки APK", onClick = { openUrlInBrowser(context, ReleasesUrl) }, icon = { Icon( imageVector = Icons.Default.Update, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(18.dp) ) } ) } Spacer(modifier = Modifier.height(20.dp)) } if (showHelpDialog) ImportantInfoDialog(onDismiss = { showHelpDialog = false }) if (showDonateDialog) DonateDialog(onDismiss = { showDonateDialog = false }) } @Composable private fun InfoHeroCard(currentVersion: String, onSupportClick: () -> Unit) { val colors = MaterialTheme.colorScheme val isDark = colors.background.luminance() < 0.22f val heroBrush = remember(colors.primaryContainer, colors.secondaryContainer, colors.surfaceVariant) { Brush.linearGradient( listOf( colors.primaryContainer, colors.secondaryContainer, colors.surfaceVariant ) ) } val glassColor = if (isDark) colors.surface.copy(alpha = 0.46f) else Color.White.copy(alpha = 0.54f) val glassBorder = colors.outlineVariant.copy(alpha = if (isDark) 0.50f else 0.32f) Surface( shape = RoundedCornerShape(32.dp), color = Color.Transparent, contentColor = MaterialTheme.colorScheme.onSurface, shadowElevation = 10.dp, tonalElevation = 0.dp ) { Box( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(32.dp)) .background(heroBrush) .padding(22.dp) ) { Box( modifier = Modifier .align(Alignment.TopEnd) .offset(x = 30.dp, y = (-34).dp) .size(138.dp) .clip(Android16BlobShape) .background(colors.primary.copy(alpha = 0.10f)) ) Box( modifier = Modifier .align(Alignment.BottomEnd) .offset(x = 26.dp, y = 30.dp) .size(112.dp) .clip(Android16BlobShape) .background(colors.secondary.copy(alpha = 0.12f)) ) Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(18.dp) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp), verticalAlignment = Alignment.CenterVertically ) { HeroMetaPill( text = "WDTT", containerColor = glassColor, borderColor = glassBorder, modifier = Modifier.weight(1f) ) HeroMetaPill( text = currentVersion, containerColor = colors.primary.copy(alpha = if (isDark) 0.18f else 0.10f), borderColor = colors.primary.copy(alpha = if (isDark) 0.22f else 0.14f), modifier = Modifier.weight(1f) ) } Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text( text = "WDTT VPN Tunnel", style = MaterialTheme.typography.headlineSmall.copy( fontWeight = FontWeight.Black, fontSize = 30.sp, lineHeight = 34.sp ), color = colors.onSurface ) Text( text = "Android-клиент для TURN/VK туннеля с WireGuard, капчей и управлением сервером.", style = MaterialTheme.typography.bodyMedium, color = colors.onSurfaceVariant, lineHeight = 21.sp ) } Button( onClick = onSupportClick, shape = RoundedCornerShape(22.dp), colors = ButtonDefaults.buttonColors( containerColor = DonateActionButtonColor, contentColor = Color.White ), modifier = Modifier .fillMaxWidth() .height(54.dp) ) { Icon(Icons.Default.Favorite, contentDescription = null, modifier = Modifier.size(18.dp)) Spacer(modifier = Modifier.width(8.dp)) Text("Поддержать проект", fontWeight = FontWeight.Bold, fontSize = 15.sp) } } } } } @Composable private fun HeroMetaPill( text: String, containerColor: Color, borderColor: Color, modifier: Modifier = Modifier ) { Surface( shape = RoundedCornerShape(18.dp), color = containerColor, border = BorderStroke(1.dp, borderColor), modifier = modifier ) { Text( text = text, modifier = Modifier.padding(horizontal = 12.dp, vertical = 9.dp), style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Center ) } } @Composable private fun ExpandableSectionCard( title: String, itemCount: String, expanded: Boolean, onToggle: () -> Unit, icon: @Composable () -> Unit, content: @Composable ColumnScope.() -> Unit ) { val arrowRotation by animateFloatAsState( targetValue = if (expanded) 180f else 0f, label = "section_arrow_rotation" ) AppSectionCard( contentPadding = PaddingValues(horizontal = 18.dp, vertical = 18.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Row( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(24.dp)) .clickable(onClick = onToggle) .padding(vertical = 2.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { Surface(shape = RoundedCornerShape(18.dp), color = MaterialTheme.colorScheme.primaryContainer) { Box(modifier = Modifier.size(40.dp), contentAlignment = Alignment.Center) { icon() } } Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.Center) { Text( text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface ) } MetaChip(text = itemCount) Icon( imageVector = Icons.Default.KeyboardArrowDown, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier .size(24.dp) .rotate(arrowRotation) ) } AnimatedVisibility( visible = expanded, enter = expandVertically() + fadeIn(), exit = shrinkVertically() + fadeOut() ) { Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.30f)) content() } } } } @Composable private fun MetaChip(text: String) { Surface( shape = RoundedCornerShape(14.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.88f), contentColor = MaterialTheme.colorScheme.onSurface ) { Text( text = text, modifier = Modifier.padding(horizontal = 10.dp, vertical = 7.dp), style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @Composable private fun InfoActionTile( title: String, subtitle: String, modifier: Modifier = Modifier, onClick: () -> Unit, icon: @Composable () -> Unit ) { Surface( shape = RoundedCornerShape(24.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.70f), contentColor = MaterialTheme.colorScheme.onSurface, modifier = modifier .clip(RoundedCornerShape(24.dp)) .clickable(onClick = onClick) ) { Column( modifier = Modifier .fillMaxWidth() .heightIn(min = 116.dp) .padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Surface(shape = RoundedCornerShape(16.dp), color = MaterialTheme.colorScheme.primaryContainer) { Box(modifier = Modifier.size(40.dp), contentAlignment = Alignment.Center) { icon() } } Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Text( title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface ) Text( text = subtitle, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, lineHeight = 18.sp ) } } } } @Composable private fun WideActionTile( title: String, subtitle: String, onClick: () -> Unit, icon: @Composable () -> Unit ) { Surface( shape = RoundedCornerShape(24.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.70f), contentColor = MaterialTheme.colorScheme.onSurface, modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(24.dp)) .clickable(onClick = onClick) ) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 15.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { Surface(shape = RoundedCornerShape(16.dp), color = MaterialTheme.colorScheme.primaryContainer) { Box(modifier = Modifier.size(40.dp), contentAlignment = Alignment.Center) { icon() } } Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) { Text( title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface ) Text( text = subtitle, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, lineHeight = 18.sp ) } Icon( imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(18.dp) ) } } } @Composable private fun ProjectLinkRow( title: String, subtitle: String, onClick: () -> Unit, icon: @Composable () -> Unit ) { Surface( shape = RoundedCornerShape(24.dp), color = MaterialTheme.colorScheme.surface.copy(alpha = 0.64f), contentColor = MaterialTheme.colorScheme.onSurface, modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(24.dp)) .clickable(onClick = onClick) ) { Row( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 14.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically ) { Surface(shape = RoundedCornerShape(16.dp), color = MaterialTheme.colorScheme.primaryContainer) { Box( modifier = Modifier.defaultMinSize(minWidth = 40.dp, minHeight = 40.dp), contentAlignment = Alignment.Center ) { icon() } } Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) { Text( title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.Bold, color = MaterialTheme.colorScheme.onSurface ) Text( text = subtitle, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, lineHeight = 18.sp ) } Icon( imageVector = Icons.AutoMirrored.Filled.ArrowForward, contentDescription = null, tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(18.dp) ) } } } @Composable private fun DonateDialog(onDismiss: () -> Unit) { val context = LocalContext.current Dialog( onDismissRequest = onDismiss, properties = DialogProperties(usePlatformDefaultWidth = false) ) { Surface( shape = RoundedCornerShape(32.dp), color = MaterialTheme.colorScheme.surface, contentColor = MaterialTheme.colorScheme.onSurface, tonalElevation = 10.dp, shadowElevation = 14.dp, modifier = Modifier.fillMaxWidth(0.92f) ) { Column( modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp, vertical = 22.dp), verticalArrangement = Arrangement.spacedBy(20.dp) ) { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp) ) { Text( text = "Поддержка проекта", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Black, color = MaterialTheme.colorScheme.onSurface, modifier = Modifier.weight(1f) ) FilledTonalIconButton(onClick = onDismiss) { Icon(Icons.Default.Close, contentDescription = "Закрыть") } } Text( text = "Если приложение реально помогает, можно поддержать Android-версию проекта.", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, lineHeight = 20.sp ) Button( onClick = { openUrlInBrowser(context, DonateUrl) }, modifier = Modifier .fillMaxWidth() .height(62.dp), shape = RoundedCornerShape(22.dp), colors = ButtonDefaults.buttonColors( containerColor = WDTTColors.donate, contentColor = Color.White ) ) { Icon( painter = painterResource(id = R.drawable.ic_yoomoney), contentDescription = "ЮMoney", tint = Color.Unspecified, modifier = Modifier .width(126.dp) .height(28.dp) ) } } } } } private fun buildSupportReport(): String { val androidVersion = Build.VERSION.RELEASE ?: "?" val sdkInt = Build.VERSION.SDK_INT val primaryAbi = Build.SUPPORTED_ABIS.firstOrNull().orEmpty().ifBlank { "unknown" } val supportedAbis = Build.SUPPORTED_ABIS.joinToString().ifBlank { "unknown" } val manufacturer = Build.MANUFACTURER.orEmpty().ifBlank { "unknown" } val brand = Build.BRAND.orEmpty().ifBlank { "unknown" } val model = Build.MODEL.orEmpty().ifBlank { "unknown" } val device = Build.DEVICE.orEmpty().ifBlank { "unknown" } val product = Build.PRODUCT.orEmpty().ifBlank { "unknown" } val hardware = Build.HARDWARE.orEmpty().ifBlank { "unknown" } val board = Build.BOARD.orEmpty().ifBlank { "unknown" } val romDisplay = Build.DISPLAY.orEmpty().ifBlank { "unknown" } val buildId = Build.ID.orEmpty().ifBlank { "unknown" } val buildFingerprint = Build.FINGERPRINT.orEmpty().ifBlank { "unknown" } val buildType = Build.TYPE.orEmpty().ifBlank { "unknown" } val socManufacturer = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { Build.SOC_MANUFACTURER.orEmpty().ifBlank { "unknown" } } else { "n/a" } val socModel = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { Build.SOC_MODEL.orEmpty().ifBlank { "unknown" } } else { "n/a" } return buildString { appendLine("Версия приложения: ${BuildConfig.VERSION_NAME}") appendLine("Андроид: $androidVersion (SDK $sdkInt)") appendLine("Устройство: $manufacturer / $brand / $model") appendLine("Код устройства: $device") appendLine("Продукт: $product") appendLine("ABI: $primaryAbi") appendLine("Все ABI: $supportedAbis") appendLine("SoC: $socManufacturer / $socModel") appendLine("Hardware: $hardware") appendLine("Board: $board") appendLine("ROM: $romDisplay") appendLine("Build ID: $buildId") appendLine("Build type: $buildType") appendLine("Fingerprint: $buildFingerprint") }.trim() }