feat: Enhance logging and debugging capabilities across Protocol and UI components

This commit is contained in:
2026-01-09 00:34:45 +05:00
parent 28a0d7a601
commit 87cee5b9c3
7 changed files with 467 additions and 164 deletions

View File

@@ -63,8 +63,8 @@ fun SetPasswordScreen(
}
val passwordsMatch = password == confirmPassword && password.isNotEmpty()
val passwordStrong = password.length >= 6
val canContinue = passwordsMatch && passwordStrong && !isCreating
val isPasswordWeak = password.isNotEmpty() && password.length < 6
val canContinue = passwordsMatch && !isCreating
Box(
modifier = Modifier
@@ -223,32 +223,60 @@ fun SetPasswordScreen(
animationSpec = tween(400, delayMillis = 350)
)
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val strength = when {
password.length < 6 -> "Weak"
password.length < 10 -> "Medium"
else -> "Strong"
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
val strength = when {
password.length < 6 -> "Weak"
password.length < 10 -> "Medium"
else -> "Strong"
}
val strengthColor = when {
password.length < 6 -> Color(0xFFE53935)
password.length < 10 -> Color(0xFFFFA726)
else -> Color(0xFF4CAF50)
}
Icon(
imageVector = Icons.Default.Shield,
contentDescription = null,
tint = strengthColor,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "Password strength: $strength",
fontSize = 12.sp,
color = strengthColor
)
}
val strengthColor = when {
password.length < 6 -> Color(0xFFE53935)
password.length < 10 -> Color(0xFFFFA726)
else -> Color(0xFF4CAF50)
// Warning for weak passwords
if (isPasswordWeak) {
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFFE53935).copy(alpha = 0.1f))
.padding(8.dp),
verticalAlignment = Alignment.Top
) {
Icon(
imageVector = Icons.Default.Warning,
contentDescription = null,
tint = Color(0xFFE53935),
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Your password is too weak. Consider using at least 6 characters for better security.",
fontSize = 11.sp,
color = Color(0xFFE53935),
lineHeight = 14.sp
)
}
}
Icon(
imageVector = Icons.Default.Shield,
contentDescription = null,
tint = strengthColor,
modifier = Modifier.size(16.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "Password strength: $strength",
fontSize = 12.sp,
color = strengthColor
)
}
}
}
@@ -394,10 +422,6 @@ fun SetPasswordScreen(
) {
Button(
onClick = {
if (!passwordStrong) {
error = "Password must be at least 6 characters"
return@Button
}
if (!passwordsMatch) {
error = "Passwords don't match"
return@Button

View File

@@ -117,8 +117,8 @@ fun WelcomeScreen(
)
) {
Text(
text = "Rosetta uses cryptographic keys\nto secure your messages.\n\nNo account registration,\nno phone number required.",
fontSize = 15.sp,
text = "Secure messaging with\ncryptographic keys",
fontSize = 16.sp,
color = secondaryTextColor,
textAlign = TextAlign.Center,
lineHeight = 24.sp,
@@ -126,14 +126,46 @@ fun WelcomeScreen(
)
}
Spacer(modifier = Modifier.weight(0.3f))
Spacer(modifier = Modifier.height(24.dp))
// Features list with icons - placed above buttons
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(600, delayMillis = 400))
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
CompactFeatureItem(
icon = Icons.Default.Security,
text = "Encrypted",
isDarkTheme = isDarkTheme,
textColor = textColor
)
CompactFeatureItem(
icon = Icons.Default.NoAccounts,
text = "No Phone",
isDarkTheme = isDarkTheme,
textColor = textColor
)
CompactFeatureItem(
icon = Icons.Default.Key,
text = "Your Keys",
isDarkTheme = isDarkTheme,
textColor = textColor
)
}
}
Spacer(modifier = Modifier.height(32.dp))
// Create Seed Button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(600, delayMillis = 400)) + slideInVertically(
enter = fadeIn(tween(600, delayMillis = 500)) + slideInVertically(
initialOffsetY = { 100 },
animationSpec = tween(600, delayMillis = 400)
animationSpec = tween(600, delayMillis = 500)
)
) {
Button(
@@ -145,14 +177,18 @@ fun WelcomeScreen(
containerColor = PrimaryBlue,
contentColor = Color.White
),
shape = RoundedCornerShape(12.dp)
shape = RoundedCornerShape(16.dp),
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 0.dp,
pressedElevation = 0.dp
)
) {
Icon(
imageVector = Icons.Default.Key,
contentDescription = null,
modifier = Modifier.size(20.dp)
modifier = Modifier.size(22.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "Generate New Seed Phrase",
fontSize = 16.sp,
@@ -161,70 +197,35 @@ fun WelcomeScreen(
}
}
Spacer(modifier = Modifier.height(16.dp))
Spacer(modifier = Modifier.height(12.dp))
// Import Seed Button
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(600, delayMillis = 500)) + slideInVertically(
enter = fadeIn(tween(600, delayMillis = 600)) + slideInVertically(
initialOffsetY = { 100 },
animationSpec = tween(600, delayMillis = 500)
animationSpec = tween(600, delayMillis = 600)
)
) {
OutlinedButton(
TextButton(
onClick = onImportSeed,
modifier = Modifier
.fillMaxWidth()
.height(56.dp),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = PrimaryBlue
),
border = ButtonDefaults.outlinedButtonBorder.copy(
brush = Brush.horizontalGradient(listOf(PrimaryBlue, PrimaryBlue))
),
shape = RoundedCornerShape(12.dp)
shape = RoundedCornerShape(16.dp)
) {
Icon(
imageVector = Icons.Default.Download,
contentDescription = null,
modifier = Modifier.size(20.dp)
modifier = Modifier.size(20.dp),
tint = PrimaryBlue
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "I Already Have a Seed Phrase",
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold
)
}
}
Spacer(modifier = Modifier.height(24.dp))
// Info text
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(600, delayMillis = 600))
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(if (isDarkTheme) AuthSurface else AuthSurfaceLight)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = "Your seed phrase is the master key to your account. Keep it safe and never share it.",
fontSize = 14.sp,
color = secondaryTextColor,
lineHeight = 18.sp
fontSize = 15.sp,
fontWeight = FontWeight.Medium,
color = PrimaryBlue
)
}
}
@@ -233,3 +234,84 @@ fun WelcomeScreen(
}
}
}
@Composable
private fun CompactFeatureItem(
icon: androidx.compose.ui.graphics.vector.ImageVector,
text: String,
isDarkTheme: Boolean,
textColor: Color
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(PrimaryBlue.copy(alpha = 0.12f)),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(24.dp)
)
}
Text(
text = text,
fontSize = 13.sp,
color = textColor.copy(alpha = 0.8f),
fontWeight = FontWeight.Medium,
textAlign = TextAlign.Center
)
}
}
@Composable
private fun FeatureItem(
icon: androidx.compose.ui.graphics.vector.ImageVector,
text: String,
isDarkTheme: Boolean,
textColor: Color
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(PrimaryBlue.copy(alpha = 0.15f)),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(20.dp)
)
}
Spacer(modifier = Modifier.width(16.dp))
Text(
text = text,
fontSize = 15.sp,
color = textColor,
fontWeight = FontWeight.Medium
)
}
}
)
}
Spacer(modifier = Modifier.width(16.dp))
Text(
text = text,
fontSize = 15.sp,
color = textColor,
fontWeight = FontWeight.Medium
)
}
}

View File

@@ -124,6 +124,12 @@ fun ChatsListScreen(
// Protocol connection state
val protocolState by ProtocolManager.state.collectAsState()
val debugLogs by ProtocolManager.debugLogs.collectAsState()
// Dev console state
var showDevConsole by remember { mutableStateOf(false) }
var titleClickCount by remember { mutableStateOf(0) }
var lastClickTime by remember { mutableStateOf(0L) }
var visible by remember { mutableStateOf(false) }
@@ -131,6 +137,95 @@ fun ChatsListScreen(
visible = true
}
// Dev console dialog
if (showDevConsole) {
AlertDialog(
onDismissRequest = { showDevConsole = false },
title = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("Dev Console", fontWeight = FontWeight.Bold)
Text(
text = protocolState.name,
fontSize = 12.sp,
color = when (protocolState) {
ProtocolState.AUTHENTICATED -> Color(0xFF4CAF50)
ProtocolState.CONNECTING, ProtocolState.HANDSHAKING -> Color(0xFFFFA726)
else -> Color(0xFFFF5722)
}
)
}
},
text = {
Column {
Box(
modifier = Modifier
.fillMaxWidth()
.height(400.dp)
.background(Color(0xFF1A1A1A), RoundedCornerShape(8.dp))
.padding(8.dp)
) {
val scrollState = rememberScrollState()
LaunchedEffect(debugLogs.size) {
scrollState.animateScrollTo(scrollState.maxValue)
}
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState)
) {
if (debugLogs.isEmpty()) {
Text(
"No logs yet...",
color = Color.Gray,
fontSize = 12.sp
)
} else {
debugLogs.forEach { log ->
Text(
text = log,
color = when {
log.contains("") -> Color(0xFF4CAF50)
log.contains("") -> Color(0xFFFF5722)
log.contains("⚠️") -> Color(0xFFFFA726)
log.contains("📤") -> Color(0xFF2196F3)
log.contains("📥") -> Color(0xFF9C27B0)
else -> Color.White
},
fontSize = 11.sp,
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
modifier = Modifier.padding(vertical = 1.dp)
)
}
}
}
}
}
},
confirmButton = {
Row {
TextButton(onClick = { ProtocolManager.clearLogs() }) {
Text("Clear")
}
TextButton(onClick = {
ProtocolManager.connect()
}) {
Text("Reconnect")
}
TextButton(onClick = { showDevConsole = false }) {
Text("Close")
}
}
},
containerColor = if (isDarkTheme) Color(0xFF212121) else Color.White
)
}
// Drawer menu items
val menuItems = listOf(
DrawerMenuItem(
@@ -211,17 +306,73 @@ fun ChatsListScreen(
}
// Menu items
menuItems.forEach { item ->
DrawerItem(
icon = item.icon,
title = item.title,
onClick = {
Column(
modifier = Modifier
.fillMaxHeight()
.weight(1f)
) {
Spacer(modifier = Modifier.height(8.dp))
menuItems.forEachIndexed { index, item ->
DrawerItem(
icon = item.icon,
title = item.title,
onClick = {
scope.launch { drawerState.close() }
item.onClick()
},
isDarkTheme = isDarkTheme
)
// Add separator between items (except after last)
if (index < menuItems.size - 1) {
Divider(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
thickness = 0.5.dp,
color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
)
}
}
}
// Logout button at bottom
Divider(
modifier = Modifier.padding(horizontal = 16.dp),
thickness = 0.5.dp,
color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
)
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0x20FF3B30))
.clickable {
scope.launch { drawerState.close() }
item.onClick()
},
isDarkTheme = isDarkTheme
onLogout()
}
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
Icons.Default.Logout,
contentDescription = "Logout",
tint = Color(0xFFFF3B30),
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = "Log Out",
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color(0xFFFF3B30)
)
}
Spacer(modifier = Modifier.height(16.dp))
}
}
) {
@@ -247,9 +398,22 @@ fun ChatsListScreen(
}
},
title = {
// Stories / Title area
// Stories / Title area - Triple click to open dev console
Row(
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.clickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime < 500) {
titleClickCount++
if (titleClickCount >= 3) {
showDevConsole = true
titleClickCount = 0
}
} else {
titleClickCount = 1
}
lastClickTime = currentTime
}
) {
// User story avatar placeholder
Box(

View File

@@ -98,13 +98,11 @@ fun OnboardingScreen(
val elapsed = System.currentTimeMillis() - startTime
transitionProgress = (elapsed / duration).coerceAtMost(1f)
// Update status bar when wave reaches top (around 15% progress)
if (transitionProgress >= 0.15f && !shouldUpdateStatusBar) {
shouldUpdateStatusBar = true
}
delay(16) // ~60fps
}
// Update status bar icons after animation is completely finished
shouldUpdateStatusBar = true
delay(50) // Small delay to ensure UI updates
isTransitioning = false
transitionProgress = 0f
shouldUpdateStatusBar = false
@@ -112,9 +110,34 @@ fun OnboardingScreen(
}
}
// Update status bar and navigation bar icons when wave reaches the top
// Animate navigation bar color starting at 80% of wave animation
val view = LocalView.current
LaunchedEffect(shouldUpdateStatusBar, isDarkTheme) {
LaunchedEffect(isTransitioning, transitionProgress) {
if (isTransitioning && transitionProgress >= 0.8f && !view.isInEditMode) {
val window = (view.context as android.app.Activity).window
// Map 0.8-1.0 to 0-1 for smooth interpolation
val navProgress = ((transitionProgress - 0.8f) / 0.2f).coerceIn(0f, 1f)
val oldColor = if (previousTheme) 0xFF1E1E1E else 0xFFFFFFFF
val newColor = if (targetTheme) 0xFF1E1E1E 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 = WindowCompat.getInsetsController(window, view)
@@ -124,32 +147,11 @@ fun OnboardingScreen(
}
}
// Animate navigation bar color with theme transition
LaunchedEffect(isTransitioning, transitionProgress, isDarkTheme) {
// Set initial navigation bar color only on first launch
LaunchedEffect(Unit) {
if (!view.isInEditMode) {
val window = (view.context as android.app.Activity).window
if (isTransitioning) {
// Interpolate color during transition
val oldColor = if (previousTheme) 0xFF1E1E1E else 0xFFFFFFFF
val newColor = if (targetTheme) 0xFF1E1E1E 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) * transitionProgress).toInt()
val g = (g1 + (g2 - g1) * transitionProgress).toInt()
val b = (b1 + (b2 - b1) * transitionProgress).toInt()
window.navigationBarColor = (0xFF000000 or (r.toLong() shl 16) or (g.toLong() shl 8) or b.toLong()).toInt()
} else {
// Set final color when not transitioning
window.navigationBarColor = if (isDarkTheme) 0xFF1E1E1E.toInt() else 0xFFFFFFFF.toInt()
}
window.navigationBarColor = if (isDarkTheme) 0xFF1E1E1E.toInt() else 0xFFFFFFFF.toInt()
}
}
@@ -387,42 +389,40 @@ fun AnimatedRosettaLogo(
)
Box(
modifier = modifier
.scale(scale)
.graphicsLayer { this.alpha = alpha },
modifier = modifier,
contentAlignment = Alignment.Center
) {
// Pre-render all animations to avoid lag
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
// Rosetta icon (page 0) with pulse animation
// Rosetta icon (page 0) with pulse animation like splash screen
if (currentPage == 0) {
val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat(
initialValue = 1f,
targetValue = 1.08f,
targetValue = 1.1f,
animationSpec = infiniteRepeatable(
animation = tween(1000, easing = FastOutSlowInEasing),
animation = tween(800, easing = FastOutSlowInEasing),
repeatMode = RepeatMode.Reverse
),
label = "pulseScale"
)
// Glow effect behind logo - separate Box without clipping
// Glow effect behind logo - same style as splash screen
Box(
modifier = Modifier
.size(200.dp)
.size(180.dp)
.scale(pulseScale)
.background(
color = Color(0xFF54A9EB).copy(alpha = 0.15f),
color = Color(0xFF54A9EB).copy(alpha = 0.2f),
shape = CircleShape
)
)
// Main logo - circular like splash screen
Image(
painter = painterResource(id = R.drawable.rosetta_icon),
contentDescription = "Rosetta Logo",
modifier = Modifier
.size(180.dp)
.scale(pulseScale)
.size(150.dp)
.clip(CircleShape)
)
}

View File

@@ -68,9 +68,9 @@ fun RosettaAndroidTheme(
val window = (view.context as android.app.Activity).window
// Make status bar transparent for wave animation overlay
window.statusBarColor = AndroidColor.TRANSPARENT
window.navigationBarColor = if (darkTheme) 0xFF1B1B1B.toInt() else 0xFFFFFFFF.toInt()
// Navigation bar color is managed by OnboardingScreen for smooth transition
// Don't change it here to avoid instant color change
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
WindowCompat.getInsetsController(window, view).isAppearanceLightNavigationBars = !darkTheme
}
}