diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fb2f6ba..ab0943d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,6 +17,9 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } + + // Optimize Lottie animations + manifestPlaceholders["enableLottieOptimizations"] = "true" } signingConfigs { diff --git a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt index 395aa67..76b5c4e 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/chats/ChatsListScreen.kt @@ -573,74 +573,38 @@ fun ChatsListScreen( dialogToDelete?.let { dialog -> AlertDialog( onDismissRequest = { dialogToDelete = null }, - icon = { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = null, - tint = PrimaryBlue, - modifier = Modifier.size(32.dp) - ) - }, + containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, title = { Text( - text = "Delete conversation?", - fontWeight = FontWeight.SemiBold, - fontSize = 18.sp, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() + "Delete Chat", + fontWeight = FontWeight.Bold, + color = textColor ) }, text = { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) } - Text( - text = "All messages with $displayName will be permanently deleted. This action cannot be undone.", - fontSize = 15.sp, - color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - TextButton( - onClick = { dialogToDelete = null }, - modifier = Modifier.weight(1f) - ) { - Text("Cancel", fontWeight = FontWeight.Medium) - } - - Spacer(modifier = Modifier.width(8.dp)) - - TextButton( - onClick = { - scope.launch { - chatsViewModel.deleteDialog(dialog.opponentKey) - dialogToDelete = null - } - }, - colors = ButtonDefaults.textButtonColors( - contentColor = Color(0xFFFF3B30) - ), - modifier = Modifier.weight(1f) - ) { - Text("Delete", fontWeight = FontWeight.SemiBold) + Text( + "Are you sure you want to delete this chat? This action cannot be undone.", + color = secondaryTextColor + ) + }, + confirmButton = { + TextButton( + onClick = { + val opponentKey = dialog.opponentKey + dialogToDelete = null + scope.launch { + chatsViewModel.deleteDialog(opponentKey) } } + ) { + Text("Delete", color = Color(0xFFFF3B30)) } }, - confirmButton = {}, - dismissButton = {}, - containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, - shape = RoundedCornerShape(16.dp) + dismissButton = { + TextButton(onClick = { dialogToDelete = null }) { + Text("Cancel", color = PrimaryBlue) + } + } ) } @@ -648,75 +612,39 @@ fun ChatsListScreen( dialogToBlock?.let { dialog -> AlertDialog( onDismissRequest = { dialogToBlock = null }, - icon = { - Icon( - imageVector = Icons.Default.Block, - contentDescription = null, - tint = Color(0xFFFF6B6B), - modifier = Modifier.size(32.dp) - ) - }, + containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, title = { Text( - text = "Block user?", - fontWeight = FontWeight.SemiBold, - fontSize = 18.sp, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() + "Block ${dialog.opponentTitle.ifEmpty { "User" }}", + fontWeight = FontWeight.Bold, + color = textColor ) }, text = { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) } - Text( - text = "$displayName will no longer be able to send you messages. You can unblock them later.", - fontSize = 15.sp, - color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - TextButton( - onClick = { dialogToBlock = null }, - modifier = Modifier.weight(1f) - ) { - Text("Cancel", fontWeight = FontWeight.Medium) - } - - Spacer(modifier = Modifier.width(8.dp)) - - TextButton( - onClick = { - scope.launch { - chatsViewModel.blockUser(dialog.opponentKey) - dialogToBlock = null - blocklistUpdateTrigger++ - } - }, - colors = ButtonDefaults.textButtonColors( - contentColor = Color(0xFFFF3B30) - ), - modifier = Modifier.weight(1f) - ) { - Text("Block", fontWeight = FontWeight.SemiBold) + Text( + "Are you sure you want to block this user? They won't be able to send you messages.", + color = secondaryTextColor + ) + }, + confirmButton = { + TextButton( + onClick = { + val opponentKey = dialog.opponentKey + dialogToBlock = null + scope.launch { + chatsViewModel.blockUser(opponentKey) + blocklistUpdateTrigger++ } } + ) { + Text("Block", color = Color(0xFFFF3B30)) } }, - confirmButton = {}, - dismissButton = {}, - containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, - shape = RoundedCornerShape(16.dp) + dismissButton = { + TextButton(onClick = { dialogToBlock = null }) { + Text("Cancel", color = PrimaryBlue) + } + } ) } @@ -724,75 +652,39 @@ fun ChatsListScreen( dialogToUnblock?.let { dialog -> AlertDialog( onDismissRequest = { dialogToUnblock = null }, - icon = { - Icon( - imageVector = Icons.Default.LockOpen, - contentDescription = null, - tint = Color(0xFF4CAF50), - modifier = Modifier.size(32.dp) - ) - }, + containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, title = { Text( - text = "Unblock user?", - fontWeight = FontWeight.SemiBold, - fontSize = 18.sp, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() + "Unblock ${dialog.opponentTitle.ifEmpty { "User" }}", + fontWeight = FontWeight.Bold, + color = textColor ) }, text = { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val displayName = dialog.opponentTitle.ifEmpty { dialog.opponentKey.take(8) } - Text( - text = "$displayName will be able to send you messages again.", - fontSize = 15.sp, - color = if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF666666), - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - - Spacer(modifier = Modifier.height(16.dp)) - - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically - ) { - TextButton( - onClick = { dialogToUnblock = null }, - modifier = Modifier.weight(1f) - ) { - Text("Cancel", fontWeight = FontWeight.Medium) - } - - Spacer(modifier = Modifier.width(8.dp)) - - TextButton( - onClick = { - scope.launch { - chatsViewModel.unblockUser(dialog.opponentKey) - dialogToUnblock = null - blocklistUpdateTrigger++ - } - }, - colors = ButtonDefaults.textButtonColors( - contentColor = Color(0xFF4CAF50) - ), - modifier = Modifier.weight(1f) - ) { - Text("Unblock", fontWeight = FontWeight.SemiBold) + Text( + "Are you sure you want to unblock this user? They will be able to send you messages again.", + color = secondaryTextColor + ) + }, + confirmButton = { + TextButton( + onClick = { + val opponentKey = dialog.opponentKey + dialogToUnblock = null + scope.launch { + chatsViewModel.unblockUser(opponentKey) + blocklistUpdateTrigger++ } } + ) { + Text("Unblock", color = PrimaryBlue) } }, - confirmButton = {}, - dismissButton = {}, - containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White, - shape = RoundedCornerShape(16.dp) + dismissButton = { + TextButton(onClick = { dialogToUnblock = null }) { + Text("Cancel", color = Color(0xFF8E8E93)) + } + } ) } } // Close Box diff --git a/app/src/main/java/com/rosetta/messenger/ui/onboarding/OnboardingScreen.kt b/app/src/main/java/com/rosetta/messenger/ui/onboarding/OnboardingScreen.kt index 2eae784..dad45dc 100644 --- a/app/src/main/java/com/rosetta/messenger/ui/onboarding/OnboardingScreen.kt +++ b/app/src/main/java/com/rosetta/messenger/ui/onboarding/OnboardingScreen.kt @@ -389,11 +389,23 @@ fun AnimatedRosettaLogo( ) Box( - modifier = modifier, + modifier = modifier + .graphicsLayer { + // Enable hardware acceleration + compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen + }, contentAlignment = Alignment.Center ) { // Pre-render all animations to avoid lag - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Box( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + // Hardware layer for better performance + renderEffect = null + }, + contentAlignment = Alignment.Center + ) { // Rosetta icon (page 0) with pulse animation like splash screen if (currentPage == 0) { val pulseScale by rememberInfiniteTransition(label = "pulse").animateFloat( @@ -428,70 +440,108 @@ fun AnimatedRosettaLogo( } // Fast page - idea animation (page 1) - ideaComposition?.let { comp -> - val lottieComp = comp as? com.airbnb.lottie.LottieComposition - val progress by animateLottieCompositionAsState( - composition = lottieComp, - iterations = 1, - isPlaying = currentPage == 1 - ) - if (currentPage == 1) { - LottieAnimation( + // Load adjacent pages for smooth swiping + val shouldLoadIdea = currentPage in 0..2 + if (shouldLoadIdea) { + ideaComposition?.let { comp -> + val lottieComp = comp as? com.airbnb.lottie.LottieComposition + val progress by animateLottieCompositionAsState( composition = lottieComp, - progress = { progress }, - modifier = Modifier.fillMaxSize() + iterations = 1, + isPlaying = currentPage == 1, + speed = 1.2f // Faster for smoother perception ) + if (currentPage == 1) { + LottieAnimation( + composition = lottieComp, + progress = { progress }, + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + // Hardware acceleration for smooth rendering + compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen + }, + maintainOriginalImageBounds = true + ) + } } } // Free page - money animation (page 2) - moneyComposition?.let { comp -> - val lottieComp = comp as? com.airbnb.lottie.LottieComposition - val progress by animateLottieCompositionAsState( - composition = lottieComp, - iterations = 1, - isPlaying = currentPage == 2 - ) - if (currentPage == 2) { - LottieAnimation( + val shouldLoadMoney = currentPage in 1..3 + if (shouldLoadMoney) { + moneyComposition?.let { comp -> + val lottieComp = comp as? com.airbnb.lottie.LottieComposition + val progress by animateLottieCompositionAsState( composition = lottieComp, - progress = { progress }, - modifier = Modifier.fillMaxSize() + iterations = 1, + isPlaying = currentPage == 2, + speed = 1.2f ) + if (currentPage == 2) { + LottieAnimation( + composition = lottieComp, + progress = { progress }, + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen + }, + maintainOriginalImageBounds = true + ) + } } } // Secure page - lock animation (page 3) - lockComposition?.let { comp -> - val lottieComp = comp as? com.airbnb.lottie.LottieComposition - val progress by animateLottieCompositionAsState( - composition = lottieComp, - iterations = 1, - isPlaying = currentPage == 3 - ) - if (currentPage == 3) { - LottieAnimation( + val shouldLoadLock = currentPage in 2..4 + if (shouldLoadLock) { + lockComposition?.let { comp -> + val lottieComp = comp as? com.airbnb.lottie.LottieComposition + val progress by animateLottieCompositionAsState( composition = lottieComp, - progress = { progress }, - modifier = Modifier.fillMaxSize() + iterations = 1, + isPlaying = currentPage == 3, + speed = 1.2f ) + if (currentPage == 3) { + LottieAnimation( + composition = lottieComp, + progress = { progress }, + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen + }, + maintainOriginalImageBounds = true + ) + } } } // Private page - book animation (page 4) - bookComposition?.let { comp -> - val lottieComp = comp as? com.airbnb.lottie.LottieComposition - val progress by animateLottieCompositionAsState( - composition = lottieComp, - iterations = 1, - isPlaying = currentPage == 4 - ) - if (currentPage == 4) { - LottieAnimation( + val shouldLoadBook = currentPage in 3..4 + if (shouldLoadBook) { + bookComposition?.let { comp -> + val lottieComp = comp as? com.airbnb.lottie.LottieComposition + val progress by animateLottieCompositionAsState( composition = lottieComp, - progress = { progress }, - modifier = Modifier.fillMaxSize() + iterations = 1, + isPlaying = currentPage == 4, + speed = 1.2f ) + if (currentPage == 4) { + LottieAnimation( + composition = lottieComp, + progress = { progress }, + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen + }, + maintainOriginalImageBounds = true + ) + } } } }