feat: simplify color handling in ChatsListScreen and improve gesture callbacks in SwipeableDialogItem

This commit is contained in:
2026-02-12 20:09:53 +05:00
parent ea537ccce1
commit e208ce050a
16 changed files with 419 additions and 365 deletions

View File

@@ -29,7 +29,7 @@ object BackgroundBlurPresets {
/** Сплошные цвета */
private val solidColors = listOf(
BackgroundBlurOption("solid_blue", listOf(Color(0xFF2979FF)), "Blue"),
BackgroundBlurOption("solid_blue", listOf(Color(0xFF0D8CF4)), "Blue"),
BackgroundBlurOption("solid_green", listOf(Color(0xFF4CAF50)), "Green"),
BackgroundBlurOption("solid_orange", listOf(Color(0xFFFF9800)), "Orange"),
BackgroundBlurOption("solid_red", listOf(Color(0xFFE53935)), "Red"),

View File

@@ -63,11 +63,12 @@ fun AppearanceScreen(
BackHandler { onBack() }
Column(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)
) {
// ═══════════════════════════════════════════════════════════
// CONTENT
// ═══════════════════════════════════════════════════════════
@@ -105,7 +106,7 @@ fun AppearanceScreen(
tint = Color.White
)
}
IconButton(onClick = onToggleTheme) {
IconButton(onClick = { onToggleTheme() }) {
Icon(
imageVector = if (isDarkTheme) TablerIcons.Sun else TablerIcons.Moon,
contentDescription = "Toggle theme",
@@ -154,6 +155,7 @@ fun AppearanceScreen(
Spacer(modifier = Modifier.height(32.dp))
}
}
}
}
@@ -439,7 +441,7 @@ private fun ColorCircleItem(
val borderColor by animateColorAsState(
targetValue = if (isSelected) {
if (isDarkTheme) Color.White else Color(0xFF222222)
Color.White
} else {
Color.Transparent
},

View File

@@ -226,6 +226,14 @@ fun OtherProfileScreen(
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
// Закрываем клавиатуру при открытии экрана
LaunchedEffect(Unit) {
val imm = context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE)
as android.view.inputmethod.InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
focusManager.clearFocus()
}
// 🗑️ Для удаления диалога
val database = remember { RosettaDatabase.getDatabase(context) }
val messageDao = remember { database.messageDao() }
@@ -1664,7 +1672,7 @@ private fun CollapsingOtherProfileHeader(
// ═══════════════════════════════════════════════════════════
// 🎨 TEXT COLOR - просто по теме: белый в тёмной, чёрный в светлой
// ═══════════════════════════════════════════════════════════
val textColor = if (isDarkTheme) Color.White else Color.Black
val textColor = Color.White
Box(modifier = Modifier.fillMaxWidth().height(headerHeight)) {
// ═══════════════════════════════════════════════════════════
@@ -1674,9 +1682,10 @@ private fun CollapsingOtherProfileHeader(
publicKey = publicKey,
avatarRepository = avatarRepository,
fallbackColor = avatarColors.backgroundColor,
blurRadius = 25f,
alpha = 0.3f,
overlayColors = com.rosetta.messenger.ui.settings.BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId)
blurRadius = 20f,
alpha = 0.9f,
overlayColors = com.rosetta.messenger.ui.settings.BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId),
isDarkTheme = isDarkTheme
)
// ═══════════════════════════════════════════════════════════
@@ -1730,7 +1739,7 @@ private fun CollapsingOtherProfileHeader(
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = if (isDarkTheme) Color.White else Color.Black,
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
@@ -1751,7 +1760,7 @@ private fun CollapsingOtherProfileHeader(
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Profile menu",
tint = if (isDarkTheme) Color.White else Color.Black,
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
@@ -1820,12 +1829,7 @@ private fun CollapsingOtherProfileHeader(
Text(
text = statusText,
fontSize = onlineFontSize,
color =
if (isOnline) {
Color(0xFF4CAF50)
} else {
textColor.copy(alpha = 0.7f)
}
color = Color.White
)
}
}

View File

@@ -55,6 +55,8 @@ import com.rosetta.messenger.biometric.BiometricAvailability
import com.rosetta.messenger.biometric.BiometricPreferences
import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.ui.components.BlurredAvatarBackground
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.ui.onboarding.PrimaryBlueDark
import com.rosetta.messenger.ui.components.metaball.ProfileMetaballEffect
import com.rosetta.messenger.utils.AvatarFileManager
@@ -259,7 +261,6 @@ fun ProfileScreen(
onNavigateToAppearance: () -> Unit = {},
onNavigateToSafety: () -> Unit = {},
onNavigateToLogs: () -> Unit = {},
onNavigateToCrashLogs: () -> Unit = {},
onNavigateToBiometric: () -> Unit = {},
viewModel: ProfileViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
avatarRepository: AvatarRepository? = null,
@@ -759,6 +760,31 @@ fun ProfileScreen(
Spacer(modifier = Modifier.height(24.dp))
// ═════════════════════════════════════════════════════════════
// 🔔 NOTIFICATIONS SECTION
// ═════════════════════════════════════════════════════════════
TelegramSectionTitle(title = "Notifications", isDarkTheme = isDarkTheme)
run {
val preferencesManager = remember { com.rosetta.messenger.data.PreferencesManager(context) }
val notificationsEnabled by preferencesManager.notificationsEnabled.collectAsState(initial = true)
val scope = rememberCoroutineScope()
TelegramToggleItem(
icon = TablerIcons.Bell,
title = "Push Notifications",
isEnabled = notificationsEnabled,
onToggle = {
scope.launch {
preferencesManager.setNotificationsEnabled(!notificationsEnabled)
}
},
isDarkTheme = isDarkTheme
)
}
Spacer(modifier = Modifier.height(24.dp))
// ═════════════════════════════════════════════════════════════
// ⚙️ SETTINGS SECTION - Telegram style
// ═════════════════════════════════════════════════════════════
@@ -785,14 +811,6 @@ fun ProfileScreen(
title = "Safety",
onClick = onNavigateToSafety,
isDarkTheme = isDarkTheme,
showDivider = true
)
TelegramSettingsItem(
icon = TablerIcons.Bug,
title = "Crash Logs",
onClick = onNavigateToCrashLogs,
isDarkTheme = isDarkTheme,
showDivider = biometricAvailable is BiometricAvailability.Available
)
@@ -938,7 +956,7 @@ private fun CollapsingProfileHeader(
// ═══════════════════════════════════════════════════════════
// 🎨 TEXT COLOR - просто по теме: белый в тёмной, чёрный в светлой
// ═══════════════════════════════════════════════════════════
val textColor = if (isDarkTheme) Color.White else Color.Black
val textColor = Color.White
// ═══════════════════════════════════════════════════════════
// 📐 HEADER HEIGHT - ФИКСИРОВАННАЯ! Не меняется при overscroll
@@ -959,7 +977,7 @@ private fun CollapsingProfileHeader(
// ═══════════════════════════════════════════════════════════
// 📝 TEXT - внизу header зоны, внутри блока
// ═══════════════════════════════════════════════════════════
val textDefaultY = expandedHeight - 48.dp // Внизу header блока (ближе к низу)
val textDefaultY = expandedHeight - 70.dp // Ближе к аватарке
val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT / 2
// Текст меняет позицию только при collapse, НЕ при overscroll
@@ -977,9 +995,10 @@ private fun CollapsingProfileHeader(
publicKey = publicKey,
avatarRepository = avatarRepository,
fallbackColor = avatarColors.backgroundColor,
blurRadius = 25f,
alpha = 0.3f,
overlayColors = BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId)
blurRadius = 20f,
alpha = 0.9f,
overlayColors = BackgroundBlurPresets.getOverlayColors(backgroundBlurColorId),
isDarkTheme = isDarkTheme
)
// ═══════════════════════════════════════════════════════════
@@ -1033,7 +1052,7 @@ private fun CollapsingProfileHeader(
Icon(
imageVector = TablerIcons.ArrowLeft,
contentDescription = "Back",
tint = if (isDarkTheme) Color.White else Color.Black,
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
@@ -1055,11 +1074,9 @@ private fun CollapsingProfileHeader(
text = "Save",
color =
if (isSaveEnabled) {
if (isDarkTheme) Color.White else Color.Black
Color.White
} else {
(if (isDarkTheme) Color.White else Color.Black).copy(
alpha = 0.45f
)
Color.White.copy(alpha = 0.45f)
},
fontWeight = FontWeight.SemiBold
)
@@ -1074,7 +1091,7 @@ private fun CollapsingProfileHeader(
Icon(
imageVector = TablerIcons.DotsVertical,
contentDescription = "Profile menu",
tint = if (isDarkTheme) Color.White else Color.Black,
tint = Color.White,
modifier = Modifier.size(24.dp)
)
}
@@ -1174,7 +1191,8 @@ private fun FullSizeAvatar(
}
initialLoadComplete = true
} else {
// Нет аватарки - помечаем загрузку завершенной
// Нет аватарки - сбрасываем bitmap и помечаем загрузку завершенной
bitmap = null
initialLoadComplete = true
}
}
@@ -1323,8 +1341,8 @@ fun ProfileCard(
// ═════════════════════════════════════════════════════════════
@Composable
fun TelegramSectionTitle(title: String, isDarkTheme: Boolean) {
val textColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
fun TelegramSectionTitle(title: String, isDarkTheme: Boolean, color: Color? = null) {
val textColor = color ?: if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
Text(
text = title,
@@ -1483,11 +1501,12 @@ private fun TelegramSettingsItem(
onClick: () -> Unit,
isDarkTheme: Boolean,
showDivider: Boolean = false,
subtitle: String? = null
subtitle: String? = null,
iconTint: Color? = null
) {
val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val iconColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val iconColor = iconTint ?: if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0)
Column {
@@ -1527,85 +1546,109 @@ private fun TelegramSettingsItem(
}
@Composable
private fun TelegramBiometricItem(isEnabled: Boolean, onToggle: () -> Unit, isDarkTheme: Boolean) {
private fun TelegramToggleItem(
icon: ImageVector,
title: String,
subtitle: String? = null,
isEnabled: Boolean,
onToggle: () -> Unit,
isDarkTheme: Boolean,
showDivider: Boolean = false
) {
val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val iconColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
val primaryBlue = Color(0xFF007AFF)
val accentColor = if (isDarkTheme) PrimaryBlueDark else PrimaryBlue
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0)
Row(
modifier =
Modifier.fillMaxWidth()
.clickable(onClick = onToggle)
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = TablerIcons.Fingerprint,
contentDescription = null,
tint = iconColor,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = "Biometric Authentication",
fontSize = 16.sp,
color = textColor,
modifier = Modifier.weight(1f)
)
// iOS-style animated switch
val animatedThumbOffset by
animateFloatAsState(
targetValue = if (isEnabled) 1f else 0f,
animationSpec =
spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "switchThumb"
)
val trackColor by
animateColorAsState(
targetValue =
if (isEnabled) primaryBlue
else if (isDarkTheme) Color(0xFF39393D) else Color(0xFFE9E9EA),
animationSpec = tween(300),
label = "trackColor"
)
Box(
Column {
Row(
modifier =
Modifier.width(51.dp)
.height(31.dp)
.clip(RoundedCornerShape(15.5.dp))
.background(trackColor)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onClick = { onToggle() }
)
.padding(2.dp)
Modifier.fillMaxWidth()
.clickable(onClick = onToggle)
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = iconColor,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(20.dp))
Column(modifier = Modifier.weight(1f)) {
Text(text = title, fontSize = 16.sp, color = textColor)
if (subtitle != null) {
Spacer(modifier = Modifier.height(2.dp))
Text(text = subtitle, fontSize = 13.sp, color = secondaryTextColor)
}
}
// Material 2 / old Telegram style switch
val thumbOffset by animateFloatAsState(
targetValue = if (isEnabled) 1f else 0f,
animationSpec = tween(durationMillis = 150),
label = "thumb"
)
val trackColor by animateColorAsState(
targetValue = if (isEnabled) accentColor.copy(alpha = 0.5f)
else if (isDarkTheme) Color(0xFF39393D) else Color(0xFFBDBDBD),
animationSpec = tween(durationMillis = 150),
label = "track"
)
val thumbColor by animateColorAsState(
targetValue = if (isEnabled) accentColor
else if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFFF1F1F1),
animationSpec = tween(durationMillis = 150),
label = "thumbColor"
)
Box(
modifier =
Modifier.size(27.dp)
.align(Alignment.CenterStart)
.offset(x = (20.dp * animatedThumbOffset))
.shadow(
elevation = if (isEnabled) 3.dp else 2.dp,
shape = CircleShape,
spotColor = Color.Black.copy(alpha = 0.15f)
)
.clip(CircleShape)
.background(Color.White)
modifier = Modifier
.width(37.dp)
.height(20.dp)
.clip(RoundedCornerShape(10.dp))
.background(trackColor)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
onClick = onToggle
),
contentAlignment = Alignment.CenterStart
) {
Box(
modifier = Modifier
.offset(x = (17.dp * thumbOffset))
.size(20.dp)
.shadow(2.dp, CircleShape)
.clip(CircleShape)
.background(thumbColor)
)
}
}
if (showDivider) {
Divider(
color = dividerColor,
thickness = 0.5.dp,
modifier = Modifier.padding(start = 60.dp)
)
}
}
}
@Composable
private fun TelegramBiometricItem(isEnabled: Boolean, onToggle: () -> Unit, isDarkTheme: Boolean) {
TelegramToggleItem(
icon = TablerIcons.Fingerprint,
title = "Biometric Authentication",
isEnabled = isEnabled,
onToggle = onToggle,
isDarkTheme = isDarkTheme
)
}
@Composable
private fun TelegramLogoutItem(onClick: () -> Unit, isDarkTheme: Boolean) {
val redColor = if (isDarkTheme) Color(0xFFFF5555) else Color(0xFFFF3B30)

View File

@@ -24,6 +24,8 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.ui.onboarding.PrimaryBlueDark
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -46,10 +48,11 @@ fun SafetyScreen(
val clipboardManager = LocalClipboardManager.current
val context = LocalContext.current
val scope = rememberCoroutineScope()
// Copy states
var copiedPublicKey by remember { mutableStateOf(false) }
var copiedPrivateKey by remember { mutableStateOf(false) }
var showDeleteConfirmation by remember { mutableStateOf(false) }
// Handle back gesture
BackHandler { onBack() }
@@ -105,7 +108,7 @@ fun SafetyScreen(
// ═══════════════════════════════════════════════════════════════
// Keys Section - Telegram style
// ═══════════════════════════════════════════════════════════════
TelegramSectionHeader("Keys", secondaryTextColor)
TelegramSectionHeader("Keys", Color(0xFF8E8E93))
TelegramCopyRow(
label = "Public Key",
@@ -173,7 +176,7 @@ fun SafetyScreen(
TelegramActionRow(
label = "Delete Account",
onClick = onDeleteAccount,
onClick = { showDeleteConfirmation = true },
textColor = redColor,
secondaryTextColor = secondaryTextColor,
showDivider = false
@@ -187,6 +190,40 @@ fun SafetyScreen(
Spacer(modifier = Modifier.height(32.dp))
}
}
// Delete Account Confirmation Dialog
if (showDeleteConfirmation) {
AlertDialog(
onDismissRequest = { showDeleteConfirmation = false },
containerColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color.White,
title = {
Text(
"Delete Account",
fontWeight = FontWeight.Bold,
color = textColor
)
},
text = {
Text(
"You are attempting to delete your account. Are you sure? This action cannot be undone.",
color = secondaryTextColor
)
},
confirmButton = {
TextButton(
onClick = {
showDeleteConfirmation = false
onDeleteAccount()
}
) { Text("Delete", color = Color(0xFFFF3B30)) }
},
dismissButton = {
TextButton(onClick = { showDeleteConfirmation = false }) {
Text("Cancel", color = if (isDarkTheme) PrimaryBlueDark else PrimaryBlue)
}
}
)
}
}
@Composable