feat: Add network permissions and security configuration for internet access

This commit is contained in:
k1ngsterr1
2026-01-09 01:00:49 +05:00
parent ed973ed485
commit 4adbbd095d
8 changed files with 253 additions and 36 deletions

View File

@@ -2,6 +2,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -11,6 +14,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RosettaAndroid"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31">
<activity
android:name=".MainActivity"

View File

@@ -96,6 +96,7 @@ fun ConfirmSeedPhraseScreen(
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(horizontal = 24.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally

View File

@@ -70,6 +70,7 @@ fun ImportSeedPhraseScreen(
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(horizontal = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {

View File

@@ -104,6 +104,7 @@ fun SetPasswordScreen(
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(horizontal = 24.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally

View File

@@ -76,6 +76,7 @@ fun UnlockScreen(
Column(
modifier = Modifier
.fillMaxSize()
.imePadding()
.padding(horizontal = 24.dp)
.statusBarsPadding(),
horizontalAlignment = Alignment.CenterHorizontally

View File

@@ -16,6 +16,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -41,6 +43,13 @@ fun WelcomeScreen(
val textColor by animateColorAsState(if (isDarkTheme) Color.White else Color.Black, animationSpec = themeAnimSpec)
val secondaryTextColor by animateColorAsState(if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666), animationSpec = themeAnimSpec)
// Sync navigation bar color with background
val view = LocalView.current
SideEffect {
val window = (view.context as? android.app.Activity)?.window
window?.navigationBarColor = backgroundColor.toArgb()
}
// Animation for Lottie
val lockComposition by rememberLottieComposition(LottieCompositionSpec.Asset("lottie/lock.json"))
val lockProgress by animateLottieCompositionAsState(

View File

@@ -19,8 +19,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -49,27 +51,50 @@ data class Chat(
val isPinned: Boolean = false
)
// Avatar colors like in React Native app
private val avatarColors = listOf(
Color(0xFF5E9FFF), // Blue
Color(0xFFFF7EB3), // Pink
Color(0xFF7B68EE), // Purple
Color(0xFF50C878), // Green
Color(0xFFFF6B6B), // Red
Color(0xFF4ECDC4), // Teal
Color(0xFFFFB347), // Orange
Color(0xFFBA55D3) // Orchid
// Avatar colors matching React Native app (Mantine inspired)
// Light theme colors (background lighter, text darker)
private val avatarColorsLight = listOf(
Color(0xFF1971c2) to Color(0xFFd0ebff), // blue
Color(0xFF0c8599) to Color(0xFFc5f6fa), // cyan
Color(0xFF9c36b5) to Color(0xFFeebefa), // grape
Color(0xFF2f9e44) to Color(0xFFd3f9d8), // green
Color(0xFF4263eb) to Color(0xFFdbe4ff), // indigo
Color(0xFF5c940d) to Color(0xFFe9fac8), // lime
Color(0xFFd9480f) to Color(0xFFffe8cc), // orange
Color(0xFFc2255c) to Color(0xFFffdeeb), // pink
Color(0xFFe03131) to Color(0xFFffe0e0), // red
Color(0xFF099268) to Color(0xFFc3fae8), // teal
Color(0xFF6741d9) to Color(0xFFe5dbff) // violet
)
// Cache для цветов аватаров - избегаем вычисления каждый раз
private val avatarColorCache = mutableMapOf<String, Color>()
// Dark theme colors (background darker, text lighter)
private val avatarColorsDark = listOf(
Color(0xFF7dd3fc) to Color(0xFF2d3548), // blue
Color(0xFF67e8f9) to Color(0xFF2d4248), // cyan
Color(0xFFd8b4fe) to Color(0xFF39334c), // grape
Color(0xFF86efac) to Color(0xFF2d3f32), // green
Color(0xFFa5b4fc) to Color(0xFF333448), // indigo
Color(0xFFbef264) to Color(0xFF383f2d), // lime
Color(0xFFfdba74) to Color(0xFF483529), // orange
Color(0xFFf9a8d4) to Color(0xFF482d3d), // pink
Color(0xFFfca5a5) to Color(0xFF482d2d), // red
Color(0xFF5eead4) to Color(0xFF2d4340), // teal
Color(0xFFc4b5fd) to Color(0xFF3a334c) // violet
)
fun getAvatarColor(name: String): Color {
return avatarColorCache.getOrPut(name) {
val index = name.hashCode().mod(avatarColors.size).let {
if (it < 0) it + avatarColors.size else it
// Cache для цветов аватаров
data class AvatarColors(val textColor: Color, val backgroundColor: Color)
private val avatarColorCache = mutableMapOf<String, AvatarColors>()
fun getAvatarColor(name: String, isDarkTheme: Boolean): AvatarColors {
val cacheKey = "${name}_${if (isDarkTheme) "dark" else "light"}"
return avatarColorCache.getOrPut(cacheKey) {
val colors = if (isDarkTheme) avatarColorsDark else avatarColorsLight
val index = name.hashCode().mod(colors.size).let {
if (it < 0) it + colors.size else it
}
avatarColors[index]
val (textColor, bgColor) = colors[index]
AvatarColors(textColor, bgColor)
}
}
@@ -113,11 +138,97 @@ fun ChatsListScreen(
onNewChat: () -> Unit,
onLogout: () -> Unit
) {
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
// Theme transition animation state
var isTransitioning by remember { mutableStateOf(false) }
var transitionProgress by remember { mutableStateOf(0f) }
var clickPosition by remember { mutableStateOf(Offset.Zero) }
var shouldUpdateStatusBar by remember { mutableStateOf(false) }
var hasInitialized by remember { mutableStateOf(false) }
var previousTheme by remember { mutableStateOf(isDarkTheme) }
var targetTheme by remember { mutableStateOf(isDarkTheme) }
LaunchedEffect(Unit) {
hasInitialized = true
}
// Theme transition animation
LaunchedEffect(isTransitioning) {
if (isTransitioning) {
shouldUpdateStatusBar = false
val duration = 800f
val startTime = System.currentTimeMillis()
while (transitionProgress < 1f) {
val elapsed = System.currentTimeMillis() - startTime
transitionProgress = (elapsed / duration).coerceAtMost(1f)
kotlinx.coroutines.delay(16)
}
shouldUpdateStatusBar = true
kotlinx.coroutines.delay(50)
isTransitioning = false
transitionProgress = 0f
shouldUpdateStatusBar = false
previousTheme = targetTheme
}
}
val view = androidx.compose.ui.platform.LocalView.current
// Animate navigation bar color starting at 80% of wave animation
LaunchedEffect(isTransitioning, transitionProgress) {
if (isTransitioning && transitionProgress >= 0.8f && !view.isInEditMode) {
val window = (view.context as android.app.Activity).window
val navProgress = ((transitionProgress - 0.8f) / 0.2f).coerceIn(0f, 1f)
val oldColor = if (previousTheme) 0xFF1A1A1A else 0xFFFFFFFF
val newColor = if (targetTheme) 0xFF1A1A1A else 0xFFFFFFFF
val r1 = (oldColor shr 16 and 0xFF)
val g1 = (oldColor shr 8 and 0xFF)
val b1 = (oldColor and 0xFF)
val r2 = (newColor shr 16 and 0xFF)
val g2 = (newColor shr 8 and 0xFF)
val b2 = (newColor and 0xFF)
val r = (r1 + (r2 - r1) * navProgress).toInt()
val g = (g1 + (g2 - g1) * navProgress).toInt()
val b = (b1 + (b2 - b1) * navProgress).toInt()
window.navigationBarColor = (0xFF000000 or (r.toLong() shl 16) or (g.toLong() shl 8) or b.toLong()).toInt()
}
}
// Update status bar icons when animation finishes
LaunchedEffect(shouldUpdateStatusBar) {
if (shouldUpdateStatusBar && !view.isInEditMode) {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
val backgroundColor by animateColorAsState(
targetValue = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF),
animationSpec = if (!hasInitialized) snap() else tween(800, easing = FastOutSlowInEasing),
label = "backgroundColor"
)
val drawerBackgroundColor = if (isDarkTheme) Color(0xFF212121) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
val textColor by animateColorAsState(
targetValue = if (isDarkTheme) Color.White else Color.Black,
animationSpec = if (!hasInitialized) snap() else tween(800, easing = FastOutSlowInEasing),
label = "textColor"
)
val secondaryTextColor by animateColorAsState(
targetValue = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666),
animationSpec = if (!hasInitialized) snap() else tween(800, easing = FastOutSlowInEasing),
label = "secondaryTextColor"
)
val dividerColor by animateColorAsState(
targetValue = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8),
animationSpec = if (!hasInitialized) snap() else tween(800, easing = FastOutSlowInEasing),
label = "dividerColor"
)
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
@@ -245,6 +356,31 @@ fun ChatsListScreen(
)
)
Box(modifier = Modifier.fillMaxSize()) {
// Base background - shows the OLD theme color during transition
Box(
modifier = Modifier
.fillMaxSize()
.background(if (isTransitioning) {
if (previousTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
} else backgroundColor)
)
// Circular reveal overlay - draws the NEW theme color expanding
if (isTransitioning) {
Canvas(modifier = Modifier.fillMaxSize()) {
val maxRadius = kotlin.math.hypot(size.width, size.height)
val radius = maxRadius * transitionProgress
// Draw the NEW theme color expanding from click point
drawCircle(
color = if (targetTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF),
radius = radius,
center = clickPosition
)
}
}
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
@@ -283,13 +419,35 @@ fun ChatsListScreen(
// Theme toggle
IconButton(
onClick = onToggleTheme
onClick = {},
modifier = Modifier.onGloballyPositioned { coordinates ->
// This will be handled by clickable below
}
) {
Icon(
if (isDarkTheme) Icons.Default.LightMode else Icons.Default.DarkMode,
contentDescription = "Toggle theme",
tint = textColor
)
Box(
modifier = Modifier
.clickable {
if (!isTransitioning) {
previousTheme = isDarkTheme
targetTheme = !isDarkTheme
// Use center of icon as click position
val screenWidth = view.width.toFloat()
val screenHeight = view.height.toFloat()
clickPosition = Offset(
screenWidth - 48.dp.value * view.resources.displayMetrics.density,
96.dp.value * view.resources.displayMetrics.density
)
isTransitioning = true
onToggleTheme()
}
}
) {
Icon(
if (isDarkTheme) Icons.Default.LightMode else Icons.Default.DarkMode,
contentDescription = "Toggle theme",
tint = textColor
)
}
}
}
@@ -434,18 +592,19 @@ fun ChatsListScreen(
.background(backgroundColor),
contentAlignment = Alignment.Center
) {
val avatarColors = getAvatarColor(accountName, isDarkTheme)
Box(
modifier = Modifier
.size(30.dp)
.clip(CircleShape)
.background(getAvatarColor(accountName)),
.background(avatarColors.backgroundColor),
contentAlignment = Alignment.Center
) {
Text(
text = getInitials(accountName),
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
color = Color.White
color = avatarColors.textColor
)
}
}
@@ -514,15 +673,50 @@ fun ChatsListScreen(
},
containerColor = backgroundColor
) { paddingValues ->
// Empty state with Lottie animation
EmptyChatsState(
isDarkTheme = isDarkTheme,
// Dev Console Button in bottom left corner
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
)
) {
// Console button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(500, delayMillis = 400)) + slideInHorizontally(
initialOffsetX = { -it },
animationSpec = tween(500, delayMillis = 400)
),
modifier = Modifier
.align(Alignment.BottomStart)
.padding(16.dp)
) {
FloatingActionButton(
onClick = { showDevConsole = true },
containerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFF5F5F5),
contentColor = if (protocolState == ProtocolState.AUTHENTICATED)
Color(0xFF4CAF50)
else
Color(0xFFFF9800),
shape = CircleShape,
modifier = Modifier.size(48.dp)
) {
Icon(
Icons.Default.Terminal,
contentDescription = "Dev Console",
modifier = Modifier.size(24.dp)
)
}
}
// Empty state with Lottie animation
EmptyChatsState(
isDarkTheme = isDarkTheme,
modifier = Modifier.fillMaxSize()
)
}
}
}
} // Close Box for circular reveal
}
@Composable
@@ -638,7 +832,7 @@ fun ChatItem(
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
val avatarColor = getAvatarColor(chat.name)
val avatarColors = getAvatarColor(chat.name, isDarkTheme)
val initials = getInitials(chat.name)
Column {
@@ -654,14 +848,14 @@ fun ChatItem(
modifier = Modifier
.size(56.dp)
.clip(CircleShape)
.background(avatarColor),
.background(avatarColors.backgroundColor),
contentAlignment = Alignment.Center
) {
Text(
text = initials,
fontSize = 20.sp,
fontWeight = FontWeight.SemiBold,
color = Color.White
color = avatarColors.textColor
)
// Online indicator

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">46.28.71.12</domain>
</domain-config>
</network-security-config>