Add new drawable resources for messaging features across multiple resolutions

- Added various icons including msg_palette, msg_photos, msg_pin, msg_retry, msg_secret, msg_send, msg_sendfile, msg_theme, msg_unpin, msg_views, and msg_warning in drawable-hdpi, drawable-mdpi, drawable-xhdpi, and drawable-xxhdpi directories.
- Included fingerprint, ic_ab_done, ic_ab_other, ic_ab_reply, ic_arrow_drop_down, input_attach, input_keyboard, input_smile, and menu_unlock icons in respective drawable directories.
- Enhanced user interface with new icons for better visual representation in messaging functionalities.
This commit is contained in:
2026-02-13 23:24:09 +05:00
parent 93ce53d3d5
commit 815ffa478b
174 changed files with 934 additions and 494 deletions

View File

@@ -9,12 +9,14 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.* import androidx.compose.material3.*
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
@@ -75,7 +77,15 @@ fun SetPasswordScreen(
var visible by remember { mutableStateOf(false) } var visible by remember { mutableStateOf(false) }
// Track keyboard visibility // Track keyboard visibility
val view = androidx.compose.ui.platform.LocalView.current val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
var isKeyboardVisible by remember { mutableStateOf(false) } var isKeyboardVisible by remember { mutableStateOf(false) }
DisposableEffect(view) { DisposableEffect(view) {
@@ -106,7 +116,7 @@ fun SetPasswordScreen(
) { ) {
IconButton(onClick = onBack, enabled = !isCreating) { IconButton(onClick = onBack, enabled = !isCreating) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = textColor.copy(alpha = 0.6f) tint = textColor.copy(alpha = 0.6f)
) )
@@ -280,7 +290,7 @@ fun SetPasswordScreen(
else -> Color(0xFF4CAF50) else -> Color(0xFF4CAF50)
} }
Icon( Icon(
imageVector = TablerIcons.Shield, painter = TelegramIcons.Secret,
contentDescription = null, contentDescription = null,
tint = strengthColor, tint = strengthColor,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -306,7 +316,7 @@ fun SetPasswordScreen(
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
Icon( Icon(
imageVector = TablerIcons.AlertTriangle, painter = TelegramIcons.Warning,
contentDescription = null, contentDescription = null,
tint = Color(0xFFE53935), tint = Color(0xFFE53935),
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -446,7 +456,7 @@ fun SetPasswordScreen(
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
Icon( Icon(
imageVector = TablerIcons.InfoCircle, painter = TelegramIcons.Info,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)

View File

@@ -13,6 +13,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -504,7 +505,7 @@ fun UnlockScreen(
if (isSelected) { if (isSelected) {
Icon( Icon(
TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
@@ -645,7 +646,7 @@ fun UnlockScreen(
) )
} else { } else {
Icon( Icon(
imageVector = TablerIcons.LockOpen, painter = TelegramIcons.Unlock,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
) )
@@ -663,7 +664,7 @@ fun UnlockScreen(
enabled = !isUnlocking enabled = !isUnlocking
) { ) {
Icon( Icon(
imageVector = TablerIcons.Fingerprint, painter = TelegramIcons.Fingerprint,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(22.dp) modifier = Modifier.size(22.dp)
@@ -719,7 +720,7 @@ fun UnlockScreen(
TextButton(onClick = onSwitchAccount) { TextButton(onClick = onSwitchAccount) {
Icon( Icon(
imageVector = TablerIcons.UserPlus, painter = TelegramIcons.AddContact,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)

View File

@@ -27,6 +27,9 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -117,6 +120,15 @@ fun ChatDetailScreen(
val database = RosettaDatabase.getDatabase(context) val database = RosettaDatabase.getDatabase(context)
val hapticFeedback = LocalHapticFeedback.current val hapticFeedback = LocalHapticFeedback.current
// 🔇 Mute state — read from PreferencesManager
val preferencesManager = remember { com.rosetta.messenger.data.PreferencesManager(context) }
val isChatMuted = remember { mutableStateOf(false) }
LaunchedEffect(currentUserPublicKey, user.publicKey) {
if (currentUserPublicKey.isNotBlank()) {
isChatMuted.value = preferencesManager.isChatMuted(currentUserPublicKey, user.publicKey)
}
}
// UI Theme // UI Theme
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7) val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -192,17 +204,24 @@ fun ChatDetailScreen(
var imageViewerSourceBounds by remember { mutableStateOf<ImageSourceBounds?>(null) } var imageViewerSourceBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
var imageViewerImages by remember { mutableStateOf<List<ViewableImage>>(emptyList()) } var imageViewerImages by remember { mutableStateOf<List<ViewableImage>>(emptyList()) }
// 🎨 Управление статус баром // 🎨 Управление статус баром — ВСЕГДА чёрные иконки в светлой теме
DisposableEffect(isDarkTheme, showImageViewer, window, view) { if (!view.isInEditMode) {
if (showImageViewer) { SideEffect {
// 📸 При просмотре фото - чёрные system bars if (showImageViewer) {
SystemBarsStyleUtils.applyFullscreenDark(window, view) SystemBarsStyleUtils.applyFullscreenDark(window, view)
} else { } else {
// Обычный режим чата if (window != null && view != null) {
SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme) val ic = androidx.core.view.WindowCompat.getInsetsController(window, view)
window.statusBarColor = android.graphics.Color.TRANSPARENT
ic.isAppearanceLightStatusBars = !isDarkTheme
}
}
} }
}
DisposableEffect(Unit) {
onDispose { onDispose {
// Восстановить при уходе с экрана
SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme) SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme)
} }
} }
@@ -562,16 +581,10 @@ fun ChatDetailScreen(
} }
) { ) {
Icon( Icon(
Icons.Default painter = TelegramIcons.Close,
.Close,
contentDescription = contentDescription =
"Cancel", "Cancel",
tint = tint = headerIconColor,
if (isDarkTheme
)
Color.White
else
Color.Black,
modifier = modifier =
Modifier.size( Modifier.size(
24.dp 24.dp
@@ -657,12 +670,7 @@ fun ChatDetailScreen(
.ContentCopy, .ContentCopy,
contentDescription = contentDescription =
"Copy", "Copy",
tint = tint = headerIconColor,
if (isDarkTheme
)
Color.White
else
Color.Black,
modifier = modifier =
Modifier.size( Modifier.size(
22.dp 22.dp
@@ -697,12 +705,7 @@ fun ChatDetailScreen(
.Delete, .Delete,
contentDescription = contentDescription =
"Delete", "Delete",
tint = tint = headerIconColor,
if (isDarkTheme
)
Color.White
else
Color.Black,
modifier = modifier =
Modifier.size( Modifier.size(
22.dp 22.dp
@@ -735,8 +738,7 @@ fun ChatDetailScreen(
) )
) { ) {
Icon( Icon(
Icons.Default imageVector = TablerIcons.ChevronLeft,
.KeyboardArrowLeft,
contentDescription = contentDescription =
"Back", "Back",
tint = tint =
@@ -749,7 +751,7 @@ fun ChatDetailScreen(
), ),
modifier = modifier =
Modifier.size( Modifier.size(
32.dp 28.dp
) )
) )
} }
@@ -987,6 +989,16 @@ fun ChatDetailScreen(
isDarkTheme isDarkTheme
) )
} }
// 🔇 Mute icon
if (isChatMuted.value) {
Spacer(modifier = Modifier.width(4.dp))
Icon(
painter = TelegramIcons.Mute,
contentDescription = "Muted",
modifier = Modifier.size(16.dp),
tint = secondaryTextColor
)
}
} }
// Typing indicator или // Typing indicator или
// subtitle // subtitle
@@ -1886,6 +1898,16 @@ fun ChatDetailScreen(
avatarRepository, avatarRepository,
onLongClick = { onLongClick = {
// 📳 Haptic feedback при долгом нажатии // 📳 Haptic feedback при долгом нажатии
// Не разрешаем выделять avatar-сообщения
val hasAvatar =
message.attachments
.any {
it.type ==
AttachmentType
.AVATAR
}
if (hasAvatar) return@MessageBubble
hapticFeedback hapticFeedback
.performHapticFeedback( .performHapticFeedback(
HapticFeedbackType HapticFeedbackType
@@ -1922,7 +1944,14 @@ fun ChatDetailScreen(
} }
}, },
onClick = { onClick = {
if (isSelectionMode val hasAvatar =
message.attachments
.any {
it.type ==
AttachmentType
.AVATAR
}
if (isSelectionMode && !hasAvatar
) { ) {
selectedMessages = selectedMessages =
if (selectedMessages if (selectedMessages

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -23,6 +24,7 @@ import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.lerp import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.positionChange
@@ -57,6 +59,7 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -220,21 +223,22 @@ fun ChatsListScreen(
focusManager.clearFocus() focusManager.clearFocus()
} }
// Update status bar appearance — SideEffect overrides global Theme.kt SideEffect // Update status bar appearance — only set once on entry, not every recomposition
if (!view.isInEditMode) { // This prevents overriding ChatDetailScreen's status bar when ChatsListScreen is in back stack
SideEffect { DisposableEffect(isDarkTheme) {
val window = (view.context as android.app.Activity).window val window = (view.context as android.app.Activity).window
val insetsController = val insetsController =
androidx.core.view.WindowCompat.getInsetsController(window, view) androidx.core.view.WindowCompat.getInsetsController(window, view)
// Status bar — always white icons (header is blue) // Status bar — always white icons (header is blue)
insetsController.isAppearanceLightStatusBars = false insetsController.isAppearanceLightStatusBars = false
window.statusBarColor = android.graphics.Color.TRANSPARENT window.statusBarColor = android.graphics.Color.TRANSPARENT
// Navigation bar: показываем только если есть нативные кнопки // Navigation bar
com.rosetta.messenger.ui.utils.NavigationModeUtils com.rosetta.messenger.ui.utils.NavigationModeUtils
.applyNavigationBarVisibility(insetsController, context, isDarkTheme) .applyNavigationBarVisibility(insetsController, context, isDarkTheme)
}
onDispose { }
} }
// Colors - instant change, no animation - 🔥 КЭШИРУЕМ для производительности // Colors - instant change, no animation - 🔥 КЭШИРУЕМ для производительности
@@ -490,21 +494,35 @@ fun ChatsListScreen(
) )
val headerColor = avatarColors.backgroundColor val headerColor = avatarColors.backgroundColor
// Header с размытым фоном аватарки // Header с blur аватарки (fallback = голубой) или акцентным цветом (light)
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
BlurredAvatarBackground( if (isDarkTheme) {
publicKey = accountPublicKey, if (backgroundBlurColorId == "solid_blue") {
avatarRepository = avatarRepository, // Голубой фон
fallbackColor = headerColor, Box(
blurRadius = 40f, modifier = Modifier
alpha = 0.6f, .matchParentSize()
overlayColors = .background(PrimaryBlueDark)
BackgroundBlurPresets )
.getOverlayColors( } else {
backgroundBlurColorId // Avatar blur (default)
), BlurredAvatarBackground(
isDarkTheme = isDarkTheme publicKey = accountPublicKey,
) avatarRepository = avatarRepository,
fallbackColor = PrimaryBlueDark,
blurRadius = 40f,
alpha = 0.6f,
overlayColors = emptyList(),
isDarkTheme = isDarkTheme
)
}
} else {
Box(
modifier = Modifier
.matchParentSize()
.background(PrimaryBlue)
)
}
// Content поверх фона // Content поверх фона
Column( Column(
@@ -689,7 +707,7 @@ fun ChatsListScreen(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color.White, tint = Color.White,
modifier = Modifier.size(8.dp) modifier = Modifier.size(8.dp)
@@ -734,7 +752,7 @@ fun ChatsListScreen(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.Plus, painter = TelegramIcons.Add,
contentDescription = "Add Account", contentDescription = "Add Account",
tint = if (isDarkTheme) Color(0xFF828282) else Color(0xFF889198), tint = if (isDarkTheme) Color(0xFF828282) else Color(0xFF889198),
modifier = Modifier.size(22.dp) modifier = Modifier.size(22.dp)
@@ -866,7 +884,7 @@ fun ChatsListScreen(
// Logout // Logout
DrawerMenuItemEnhanced( DrawerMenuItemEnhanced(
icon = TablerIcons.Logout, painter = TelegramIcons.Leave,
text = "Log Out", text = "Log Out",
iconColor = Color(0xFFFF4444), iconColor = Color(0xFFFF4444),
textColor = Color(0xFFFF4444), textColor = Color(0xFFFF4444),
@@ -923,7 +941,7 @@ fun ChatsListScreen(
navigationIcon = { navigationIcon = {
IconButton(onClick = { selectedChatKeys = emptySet() }) { IconButton(onClick = { selectedChatKeys = emptySet() }) {
Icon( Icon(
TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Close", contentDescription = "Close",
tint = Color.White tint = Color.White
) )
@@ -950,7 +968,7 @@ fun ChatsListScreen(
} }
}) { }) {
Icon( Icon(
if (allMuted) TablerIcons.Bell else TablerIcons.BellOff, painter = if (allMuted) TelegramIcons.Notifications else TelegramIcons.Mute,
contentDescription = if (allMuted) "Unmute" else "Mute", contentDescription = if (allMuted) "Unmute" else "Mute",
tint = Color.White tint = Color.White
) )
@@ -964,7 +982,7 @@ fun ChatsListScreen(
selectedChatKeys = emptySet() selectedChatKeys = emptySet()
}) { }) {
Icon( Icon(
TablerIcons.Trash, painter = TelegramIcons.Delete,
contentDescription = "Delete", contentDescription = "Delete",
tint = Color.White tint = Color.White
) )
@@ -974,7 +992,7 @@ fun ChatsListScreen(
Box { Box {
IconButton(onClick = { showSelectionMenu = true }) { IconButton(onClick = { showSelectionMenu = true }) {
Icon( Icon(
TablerIcons.DotsVertical, painter = TelegramIcons.More,
contentDescription = "More", contentDescription = "More",
tint = Color.White tint = Color.White
) )
@@ -1000,7 +1018,7 @@ fun ChatsListScreen(
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
if (allPinned) TablerIcons.PinnedOff else TablerIcons.Pin, painter = if (allPinned) TelegramIcons.Unpin else TelegramIcons.Pin,
contentDescription = null, contentDescription = null,
tint = if (isDarkTheme) Color.White else Color.Black tint = if (isDarkTheme) Color.White else Color.Black
) )
@@ -1028,7 +1046,7 @@ fun ChatsListScreen(
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
TablerIcons.Ban, painter = TelegramIcons.Block,
contentDescription = null, contentDescription = null,
tint = Color(0xFFE53935) tint = Color(0xFFE53935)
) )
@@ -1089,10 +1107,10 @@ fun ChatsListScreen(
modifier = modifier =
Modifier Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
.offset(x = 2.dp, y = (-2).dp) .offset(x = 4.dp, y = (-4).dp)
.size(8.dp) .size(10.dp)
.clip(CircleShape) .clip(CircleShape)
.background(if (isDarkTheme) PrimaryBlueDark else PrimaryBlue) .background(if (isDarkTheme) Color.White else PrimaryBlue)
) )
} }
} }
@@ -1208,7 +1226,7 @@ fun ChatsListScreen(
shape = CircleShape shape = CircleShape
) { ) {
Icon( Icon(
TablerIcons.Edit, painter = TelegramIcons.Edit,
contentDescription = "New Chat" contentDescription = "New Chat"
) )
} }
@@ -1444,9 +1462,55 @@ fun ChatsListScreen(
} }
} }
// Track scroll direction to hide/show Requests
val chatListState = rememberLazyListState()
var isRequestsVisible by remember { mutableStateOf(true) }
val hapticFeedback = LocalHapticFeedback.current
// NestedScroll — ловим направление свайпа даже без скролла
// Для появления: накапливаем pull down дельту, нужен сильный жест
val requestsNestedScroll = remember(hapticFeedback) {
var accumulatedPullDown = 0f
object : androidx.compose.ui.input.nestedscroll.NestedScrollConnection {
override fun onPreScroll(
available: androidx.compose.ui.geometry.Offset,
source: androidx.compose.ui.input.nestedscroll.NestedScrollSource
): androidx.compose.ui.geometry.Offset {
if (available.y < -10f) {
// Свайп вверх — прячем легко
accumulatedPullDown = 0f
isRequestsVisible = false
} else if (available.y > 0f && !isRequestsVisible) {
// Свайп вниз — накапливаем для появления
accumulatedPullDown += available.y
if (accumulatedPullDown > 120f) {
isRequestsVisible = true
accumulatedPullDown = 0f
hapticFeedback.performHapticFeedback(
HapticFeedbackType.LongPress
)
}
} else if (available.y <= 0f) {
accumulatedPullDown = 0f
}
return androidx.compose.ui.geometry.Offset.Zero
}
override suspend fun onPostFling(
consumed: androidx.compose.ui.unit.Velocity,
available: androidx.compose.ui.unit.Velocity
): androidx.compose.ui.unit.Velocity {
accumulatedPullDown = 0f
return androidx.compose.ui.unit.Velocity.Zero
}
}
}
LazyColumn( LazyColumn(
state = chatListState,
modifier = modifier =
Modifier.fillMaxSize() Modifier.fillMaxSize()
.nestedScroll(requestsNestedScroll)
.background( .background(
listBackgroundColor listBackgroundColor
) )
@@ -1456,23 +1520,35 @@ fun ChatsListScreen(
key = key =
"requests_section" "requests_section"
) { ) {
RequestsSection( AnimatedVisibility(
count = visible = isRequestsVisible,
requestsCount, enter = expandVertically(
requests = animationSpec = tween(250, easing = FastOutSlowInEasing)
requests, ) + fadeIn(animationSpec = tween(200)),
isDarkTheme = exit = shrinkVertically(
isDarkTheme, animationSpec = tween(250, easing = FastOutSlowInEasing)
onClick = { ) + fadeOut(animationSpec = tween(200))
onRequestsClick() ) {
Column {
RequestsSection(
count =
requestsCount,
requests =
requests,
isDarkTheme =
isDarkTheme,
onClick = {
onRequestsClick()
}
)
Divider(
color =
dividerColor,
thickness =
0.5.dp
)
} }
) }
Divider(
color =
dividerColor,
thickness =
0.5.dp
)
} }
} }
@@ -1980,7 +2056,7 @@ fun ChatItem(
if (isMuted) { if (isMuted) {
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Icon( Icon(
TablerIcons.BellOff, painter = TelegramIcons.Mute,
contentDescription = "Muted", contentDescription = "Muted",
tint = secondaryTextColor, tint = secondaryTextColor,
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1990,7 +2066,7 @@ fun ChatItem(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
// Read status // Read status
Icon( Icon(
TablerIcons.Checks, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -2028,7 +2104,7 @@ fun ChatItem(
// Pin icon // Pin icon
if (chat.isPinned) { if (chat.isPinned) {
Icon( Icon(
TablerIcons.Pin, painter = TelegramIcons.Pin,
contentDescription = "Pinned", contentDescription = "Pinned",
tint = tint =
secondaryTextColor.copy( secondaryTextColor.copy(
@@ -2262,9 +2338,9 @@ fun SwipeableDialogItem(
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
imageVector = painter =
if (isPinned) TablerIcons.PinnedOff if (isPinned) TelegramIcons.Unpin
else TablerIcons.Pin, else TelegramIcons.Pin,
contentDescription = contentDescription =
if (isPinned) "Unpin" else "Pin", if (isPinned) "Unpin" else "Pin",
tint = Color.White, tint = Color.White,
@@ -2303,9 +2379,9 @@ fun SwipeableDialogItem(
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
imageVector = painter =
if (isBlocked) TablerIcons.LockOpen if (isBlocked) TelegramIcons.Unlock
else TablerIcons.Ban, else TelegramIcons.Block,
contentDescription = contentDescription =
if (isBlocked) "Unblock" if (isBlocked) "Unblock"
else "Block", else "Block",
@@ -2343,7 +2419,7 @@ fun SwipeableDialogItem(
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.Trash, painter = TelegramIcons.Delete,
contentDescription = "Delete", contentDescription = "Delete",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(22.dp) modifier = Modifier.size(22.dp)
@@ -2742,7 +2818,7 @@ fun DialogItemContent(
if (isBlocked) { if (isBlocked) {
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Icon( Icon(
imageVector = TablerIcons.Lock, painter = TelegramIcons.Secret,
contentDescription = "Blocked", contentDescription = "Blocked",
tint = Color(0xFFFF3B30), tint = Color(0xFFFF3B30),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -2751,7 +2827,7 @@ fun DialogItemContent(
if (isMuted) { if (isMuted) {
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Icon( Icon(
imageVector = TablerIcons.BellOff, painter = TelegramIcons.Mute,
contentDescription = "Muted", contentDescription = "Muted",
tint = secondaryTextColor, tint = secondaryTextColor,
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -2767,7 +2843,7 @@ fun DialogItemContent(
// галочки (прочитано) // галочки (прочитано)
if (dialog.isSavedMessages) { if (dialog.isSavedMessages) {
Icon( Icon(
imageVector = TablerIcons.Checks, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -2810,8 +2886,8 @@ fun DialogItemContent(
// READ (delivered=3) - две синие // READ (delivered=3) - две синие
// галочки // галочки
Icon( Icon(
imageVector = painter =
TablerIcons.Checks, TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = modifier =
@@ -2825,8 +2901,8 @@ fun DialogItemContent(
1 -> { 1 -> {
// DELIVERED - одна серая галочка // DELIVERED - одна серая галочка
Icon( Icon(
imageVector = painter =
TablerIcons.Check, TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = tint =
secondaryTextColor secondaryTextColor
@@ -2845,8 +2921,8 @@ fun DialogItemContent(
else -> { else -> {
// SENDING (0) - часики // SENDING (0) - часики
Icon( Icon(
imageVector = painter =
TablerIcons.Clock, TelegramIcons.Clock,
contentDescription = contentDescription =
"Sending", "Sending",
tint = tint =
@@ -2988,7 +3064,7 @@ fun DialogItemContent(
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(6.dp))
Icon( Icon(
imageVector = TablerIcons.Pin, painter = TelegramIcons.Pin,
contentDescription = "Pinned", contentDescription = "Pinned",
tint = secondaryTextColor.copy(alpha = 0.5f), tint = secondaryTextColor.copy(alpha = 0.5f),
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -3057,12 +3133,20 @@ fun RequestsSection(
isDarkTheme: Boolean, isDarkTheme: Boolean,
onClick: () -> Unit onClick: () -> Unit
) { ) {
val textColor = remember(isDarkTheme) { if (isDarkTheme) Color.White else Color.Black } // Telegram archived chats uses muted colors:
val secondaryTextColor = // Title: #525252 (light) vs regular #222222
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) } // Message: #919191 (light)
// Badge: always grey #c6c9cc (light)
// Avatar bg: #B8C2CC (light) / #3A3A3C (dark)
val titleColor = remember(isDarkTheme) {
if (isDarkTheme) Color(0xFFAAAAAA) else Color(0xFF525252)
}
val subtitleColor =
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF6E6E6E) else Color(0xFF919191) }
val iconBgColor = val iconBgColor =
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFC7C7CC) } remember(isDarkTheme) { if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFB8C2CC) }
val accentColor = if (isDarkTheme) PrimaryBlueDark else PrimaryBlue val badgeColor =
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF4E4E4E) else Color(0xFFC6C9CC) }
// Последний запрос — показываем имя отправителя как subtitle // Последний запрос — показываем имя отправителя как subtitle
val lastRequest = remember(requests) { requests.firstOrNull() } val lastRequest = remember(requests) { requests.firstOrNull() }
@@ -3096,7 +3180,7 @@ fun RequestsSection(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.MailForward, painter = painterResource(id = R.drawable.archive_filled),
contentDescription = null, contentDescription = null,
tint = Color.White, tint = Color.White,
modifier = Modifier.size(26.dp) modifier = Modifier.size(26.dp)
@@ -3116,7 +3200,7 @@ fun RequestsSection(
text = "Requests", text = "Requests",
fontWeight = FontWeight.SemiBold, fontWeight = FontWeight.SemiBold,
fontSize = 16.sp, fontSize = 16.sp,
color = textColor, color = titleColor,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
@@ -3130,7 +3214,7 @@ fun RequestsSection(
Modifier Modifier
.defaultMinSize(minWidth = 22.dp, minHeight = 22.dp) .defaultMinSize(minWidth = 22.dp, minHeight = 22.dp)
.clip(CircleShape) .clip(CircleShape)
.background(accentColor), .background(badgeColor),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
@@ -3151,20 +3235,13 @@ fun RequestsSection(
Text( Text(
text = subtitle, text = subtitle,
fontSize = 14.sp, fontSize = 14.sp,
color = secondaryTextColor, color = subtitleColor,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
} }
} }
} }
// Разделитель как у обычных чатов
Divider(
color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8),
thickness = 0.5.dp,
modifier = Modifier.padding(start = 84.dp)
)
} }
} }

View File

@@ -417,7 +417,7 @@ private fun ForwardDialogItem(
.size(24.dp) .size(24.dp)
.clip(CircleShape) .clip(CircleShape)
.background( .background(
if (isSelected) Color(0xFF4CD964) if (isSelected) PrimaryBlue
else if (isDarkTheme) Color(0xFF3A3A3A) else if (isDarkTheme) Color(0xFF3A3A3A)
else Color(0xFFE0E0E0) else Color(0xFFE0E0E0)
), ),

View File

@@ -22,8 +22,9 @@ import androidx.compose.ui.unit.sp
import com.rosetta.messenger.network.SearchUser import com.rosetta.messenger.network.SearchUser
import com.rosetta.messenger.repository.AvatarRepository import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.ChevronLeft import compose.icons.tablericons.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)

View File

@@ -11,9 +11,9 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.ArrowLeft import compose.icons.tablericons.*
import compose.icons.tablericons.X
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Verified import androidx.compose.material.icons.filled.Verified
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -61,6 +61,14 @@ fun SearchScreen(
val context = LocalContext.current val context = LocalContext.current
val view = LocalView.current val view = LocalView.current
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
// 🔥 Функция мгновенного закрытия клавиатуры // 🔥 Функция мгновенного закрытия клавиатуры
val hideKeyboardInstantly: () -> Unit = { val hideKeyboardInstantly: () -> Unit = {
@@ -173,7 +181,7 @@ fun SearchScreen(
} }
) { ) {
Icon( Icon(
TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = textColor.copy(alpha = 0.6f) tint = textColor.copy(alpha = 0.6f)
) )
@@ -233,7 +241,7 @@ fun SearchScreen(
) { ) {
IconButton(onClick = { searchViewModel.clearSearchQuery() }) { IconButton(onClick = { searchViewModel.clearSearchQuery() }) {
Icon( Icon(
TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Clear", contentDescription = "Clear",
tint = secondaryTextColor.copy(alpha = 0.6f) tint = secondaryTextColor.copy(alpha = 0.6f)
) )
@@ -424,7 +432,7 @@ private fun RecentUserItem(
// Remove button // Remove button
IconButton(onClick = onRemove, modifier = Modifier.size(40.dp)) { IconButton(onClick = onRemove, modifier = Modifier.size(40.dp)) {
Icon( Icon(
TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Remove", contentDescription = "Remove",
tint = secondaryTextColor.copy(alpha = 0.6f), tint = secondaryTextColor.copy(alpha = 0.6f),
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)

View File

@@ -57,8 +57,7 @@ import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.utils.AttachmentFileManager import com.rosetta.messenger.utils.AttachmentFileManager
import com.rosetta.messenger.utils.AvatarFileManager import com.rosetta.messenger.utils.AvatarFileManager
import com.vanniktech.blurhash.BlurHash import com.vanniktech.blurhash.BlurHash
import compose.icons.TablerIcons import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.tablericons.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@@ -1174,7 +1173,7 @@ fun ImageAttachment(
when (messageStatus) { when (messageStatus) {
MessageStatus.SENDING -> { MessageStatus.SENDING -> {
Icon( Icon(
compose.icons.TablerIcons.Clock, painter = TelegramIcons.Clock,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1182,7 +1181,7 @@ fun ImageAttachment(
} }
MessageStatus.SENT -> { MessageStatus.SENT -> {
Icon( Icon(
compose.icons.TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1190,7 +1189,7 @@ fun ImageAttachment(
} }
MessageStatus.DELIVERED -> { MessageStatus.DELIVERED -> {
Icon( Icon(
compose.icons.TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1198,7 +1197,7 @@ fun ImageAttachment(
} }
MessageStatus.READ -> { MessageStatus.READ -> {
Icon( Icon(
compose.icons.TablerIcons.Checks, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color.White, tint = Color.White,
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1546,7 +1545,7 @@ fun FileAttachment(
when (messageStatus) { when (messageStatus) {
MessageStatus.SENDING -> { MessageStatus.SENDING -> {
Icon( Icon(
compose.icons.TablerIcons.Clock, painter = TelegramIcons.Clock,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1554,7 +1553,7 @@ fun FileAttachment(
} }
MessageStatus.SENT -> { MessageStatus.SENT -> {
Icon( Icon(
compose.icons.TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1562,7 +1561,7 @@ fun FileAttachment(
} }
MessageStatus.DELIVERED, MessageStatus.READ -> { MessageStatus.DELIVERED, MessageStatus.READ -> {
Icon( Icon(
compose.icons.TablerIcons.Checks, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = tint =
if (messageStatus == MessageStatus.READ) { if (messageStatus == MessageStatus.READ) {
@@ -1934,7 +1933,7 @@ fun AvatarAttachment(
when (messageStatus) { when (messageStatus) {
MessageStatus.SENDING -> { MessageStatus.SENDING -> {
Icon( Icon(
compose.icons.TablerIcons.Clock, painter = TelegramIcons.Clock,
contentDescription = "Sending", contentDescription = "Sending",
tint = Color.White.copy(alpha = 0.6f), tint = Color.White.copy(alpha = 0.6f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1942,7 +1941,7 @@ fun AvatarAttachment(
} }
MessageStatus.SENT -> { MessageStatus.SENT -> {
Icon( Icon(
compose.icons.TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = "Sent", contentDescription = "Sent",
tint = Color.White.copy(alpha = 0.6f), tint = Color.White.copy(alpha = 0.6f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1950,7 +1949,7 @@ fun AvatarAttachment(
} }
MessageStatus.DELIVERED -> { MessageStatus.DELIVERED -> {
Icon( Icon(
compose.icons.TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = "Delivered", contentDescription = "Delivered",
tint = Color.White.copy(alpha = 0.6f), tint = Color.White.copy(alpha = 0.6f),
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)
@@ -1958,7 +1957,7 @@ fun AvatarAttachment(
} }
MessageStatus.READ -> { MessageStatus.READ -> {
Icon( Icon(
compose.icons.TablerIcons.Checks, painter = TelegramIcons.Done,
contentDescription = "Read", contentDescription = "Read",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)

View File

@@ -50,6 +50,8 @@ import com.rosetta.messenger.utils.AttachmentFileManager
import com.vanniktech.blurhash.BlurHash import com.vanniktech.blurhash.BlurHash
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.ui.graphics.painter.Painter
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.math.abs import kotlin.math.abs
@@ -392,7 +394,7 @@ fun MessageBubble(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
TablerIcons.CornerUpLeft, painter = TelegramIcons.Reply,
contentDescription = "Reply", contentDescription = "Reply",
tint = tint =
if (swipeProgress >= 1f) Color.White if (swipeProgress >= 1f) Color.White
@@ -449,11 +451,11 @@ fun MessageBubble(
Modifier.padding(start = 12.dp, end = 4.dp) Modifier.padding(start = 12.dp, end = 4.dp)
.size(24.dp) .size(24.dp)
.clip(CircleShape) .clip(CircleShape)
.background(Color(0xFF4CD964)), .background(PrimaryBlue),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = "Selected", contentDescription = "Selected",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -1026,28 +1028,33 @@ fun AnimatedMessageStatus(
) { currentStatus -> ) { currentStatus ->
val iconSize = with(LocalDensity.current) { 14.sp.toDp() } val iconSize = with(LocalDensity.current) { 14.sp.toDp() }
Icon( if (currentStatus == MessageStatus.ERROR) {
imageVector = Icon(
when (currentStatus) { imageVector = TablerIcons.AlertCircle,
MessageStatus.SENDING -> TablerIcons.Clock contentDescription = null,
MessageStatus.SENT -> TablerIcons.Check tint = animatedColor,
MessageStatus.DELIVERED -> TablerIcons.Check modifier =
MessageStatus.READ -> TablerIcons.Checks Modifier.size(iconSize)
MessageStatus.ERROR -> TablerIcons.AlertCircle .scale(scale)
}, .clickable {
contentDescription = null, showErrorMenu = true
tint = animatedColor, }
modifier = )
Modifier.size(iconSize) } else {
.scale(scale) Icon(
.then( painter =
if (currentStatus == MessageStatus.ERROR) { when (currentStatus) {
Modifier.clickable { MessageStatus.SENDING -> TelegramIcons.Clock
showErrorMenu = true MessageStatus.SENT -> TelegramIcons.Done
} MessageStatus.DELIVERED -> TelegramIcons.Done
} else Modifier MessageStatus.READ -> TelegramIcons.Done
) else -> TelegramIcons.Clock
) },
contentDescription = null,
tint = animatedColor,
modifier = Modifier.size(iconSize).scale(scale)
)
}
} }
DropdownMenu( DropdownMenu(
@@ -1062,7 +1069,7 @@ fun AnimatedMessageStatus(
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
TablerIcons.Refresh, painter = TelegramIcons.Retry,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
) )
@@ -1076,7 +1083,7 @@ fun AnimatedMessageStatus(
}, },
leadingIcon = { leadingIcon = {
Icon( Icon(
TablerIcons.Trash, painter = TelegramIcons.Delete,
contentDescription = null, contentDescription = null,
tint = Color(0xFFE53935), tint = Color(0xFFE53935),
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
@@ -1370,7 +1377,7 @@ fun ReplyBubble(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
TablerIcons.Photo, painter = TelegramIcons.Photos,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp)
@@ -1631,7 +1638,7 @@ private fun ForwardedImagePreview(
} else { } else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Icon( Icon(
TablerIcons.Photo, painter = TelegramIcons.Photos,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp)
@@ -1721,85 +1728,86 @@ fun KebabMenu(
onDeleteClick: () -> Unit, onDeleteClick: () -> Unit,
onLogsClick: () -> Unit = {} onLogsClick: () -> Unit = {}
) { ) {
val dividerColor = val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f) val textColor = if (isDarkTheme) Color.White else Color(0xFF222222)
val iconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)
val dividerColor = if (isDarkTheme) Color(0xFF1C1D1F) else Color(0xFFF5F5F5)
DropdownMenu( MaterialTheme(
expanded = expanded, colorScheme = MaterialTheme.colorScheme.copy(
onDismissRequest = onDismiss, surface = menuBgColor,
modifier = Modifier.width(220.dp), onSurface = textColor
properties = )
PopupProperties(
focusable = true,
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) { ) {
if (!isSavedMessages) { DropdownMenu(
KebabMenuItem( expanded = expanded,
icon = if (isBlocked) TablerIcons.CircleCheck else TablerIcons.Ban, onDismissRequest = onDismiss,
text = if (isBlocked) "Unblock User" else "Block User", modifier = Modifier
onClick = { if (isBlocked) onUnblockClick() else onBlockClick() }, .defaultMinSize(minWidth = 196.dp)
tintColor = PrimaryBlue, .background(menuBgColor),
textColor = if (isDarkTheme) Color.White else Color.Black properties =
) PopupProperties(
focusable = true,
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
if (!isSavedMessages) {
KebabMenuItem(
icon = if (isBlocked) TelegramIcons.Done else TelegramIcons.Block,
text = if (isBlocked) "Unblock User" else "Block User",
onClick = { if (isBlocked) onUnblockClick() else onBlockClick() },
tintColor = iconColor,
textColor = textColor
)
}
Box( // Delete chat
modifier = KebabMenuItem(
Modifier.fillMaxWidth() icon = TelegramIcons.Delete,
.padding(horizontal = 16.dp, vertical = 4.dp) text = "Delete Chat",
.height(0.5.dp) onClick = onDeleteClick,
.background(dividerColor) tintColor = Color(0xFFFF3B30),
textColor = Color(0xFFFF3B30)
) )
} }
// Delete chat
KebabMenuItem(
icon = TablerIcons.Trash,
text = "Delete Chat",
onClick = onDeleteClick,
tintColor = Color(0xFFFF3B30),
textColor = Color(0xFFFF3B30)
)
} }
} }
@Composable @Composable
private fun KebabMenuItem( private fun KebabMenuItem(
icon: ImageVector, icon: Painter,
text: String, text: String,
onClick: () -> Unit, onClick: () -> Unit,
tintColor: Color, tintColor: Color,
textColor: Color textColor: Color
) { ) {
val interactionSource = remember { MutableInteractionSource() } // Telegram-style: 48dp height, 18dp horizontal padding
Box(
DropdownMenuItem( modifier = Modifier
text = { .fillMaxWidth()
Row( .defaultMinSize(minWidth = 196.dp, minHeight = 48.dp)
verticalAlignment = Alignment.CenterVertically, .clickable(onClick = onClick)
modifier = Modifier.fillMaxWidth() .padding(horizontal = 18.dp),
) { contentAlignment = Alignment.CenterStart
Icon( ) {
imageVector = icon, Row(
contentDescription = null, verticalAlignment = Alignment.CenterVertically
tint = tintColor, ) {
modifier = Modifier.size(24.dp) Icon(
) painter = icon,
Spacer(modifier = Modifier.width(14.dp)) contentDescription = null,
Text( tint = tintColor,
text = text, modifier = Modifier.size(24.dp)
color = textColor, )
fontSize = 16.sp, Spacer(modifier = Modifier.width(19.dp))
fontWeight = FontWeight.Medium Text(
) text = text,
} color = textColor,
}, fontSize = 16.sp
onClick = onClick, )
modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp), }
interactionSource = interactionSource, }
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp)
)
} }
/** 🖼️ Превью изображения для reply в input bar (как в Telegram) */ /** 🖼️ Превью изображения для reply в input bar (как в Telegram) */
@@ -1949,7 +1957,7 @@ fun ReplyImagePreview(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
TablerIcons.Photo, painter = TelegramIcons.Photos,
contentDescription = null, contentDescription = null,
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
@@ -1958,7 +1966,7 @@ fun ReplyImagePreview(
} }
} }
} }
/** Profile photo menu for avatar */ /** Profile photo menu for avatar — Telegram style */
@Composable @Composable
fun ProfilePhotoMenu( fun ProfilePhotoMenu(
expanded: Boolean, expanded: Boolean,
@@ -1968,45 +1976,48 @@ fun ProfilePhotoMenu(
onDeletePhotoClick: (() -> Unit)? = null, onDeletePhotoClick: (() -> Unit)? = null,
hasAvatar: Boolean = false hasAvatar: Boolean = false
) { ) {
val dividerColor = // Telegram popup colors
if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f) val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
val textColor = if (isDarkTheme) Color.White else Color(0xFF222222)
val iconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)
val dividerColor = if (isDarkTheme) Color(0xFF1C1D1F) else Color(0xFFF5F5F5)
DropdownMenu( MaterialTheme(
expanded = expanded, colorScheme = MaterialTheme.colorScheme.copy(
onDismissRequest = onDismiss, surface = menuBgColor,
modifier = Modifier.width(220.dp), onSurface = textColor
properties =
PopupProperties(
focusable = true,
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
ProfilePhotoMenuItem(
icon = TablerIcons.CameraPlus,
text = "Set Profile Photo",
onClick = onSetPhotoClick,
tintColor = if (isDarkTheme) Color.White else Color.Black,
textColor = if (isDarkTheme) Color.White else Color.Black
) )
) {
// Показываем Delete только если есть аватар DropdownMenu(
if (hasAvatar && onDeletePhotoClick != null) { expanded = expanded,
Box( onDismissRequest = onDismiss,
modifier = modifier = Modifier
Modifier.fillMaxWidth() .defaultMinSize(minWidth = 196.dp)
.padding(horizontal = 16.dp, vertical = 4.dp) .background(menuBgColor),
.height(0.5.dp) properties =
.background(dividerColor) PopupProperties(
) focusable = true,
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
ProfilePhotoMenuItem( ProfilePhotoMenuItem(
icon = TablerIcons.Trash, icon = TelegramIcons.AddPhoto,
text = "Delete Photo", text = if (hasAvatar) "Set Profile Photo" else "Add Photo",
onClick = onDeletePhotoClick, onClick = onSetPhotoClick,
tintColor = Color(0xFFFF3B30), tintColor = iconColor,
textColor = Color(0xFFFF3B30) textColor = textColor
) )
if (hasAvatar && onDeletePhotoClick != null) {
ProfilePhotoMenuItem(
icon = TelegramIcons.Delete,
text = "Delete Photo",
onClick = onDeletePhotoClick,
tintColor = Color(0xFFFF3B30),
textColor = Color(0xFFFF3B30)
)
}
} }
} }
} }
@@ -2021,80 +2032,81 @@ fun OtherProfileMenu(
onBlockClick: () -> Unit, onBlockClick: () -> Unit,
onClearChatClick: () -> Unit onClearChatClick: () -> Unit
) { ) {
val dividerColor = val menuBgColor = if (isDarkTheme) Color(0xFF272829) else Color.White
if (isDarkTheme) Color.White.copy(alpha = 0.1f) else Color.Black.copy(alpha = 0.08f) val textColor = if (isDarkTheme) Color.White else Color(0xFF222222)
val iconColor = if (isDarkTheme) Color.White.copy(alpha = 0.47f) else Color(0xFF676B70)
val dividerColor = if (isDarkTheme) Color(0xFF1C1D1F) else Color(0xFFF5F5F5)
DropdownMenu( MaterialTheme(
expanded = expanded, colorScheme = MaterialTheme.colorScheme.copy(
onDismissRequest = onDismiss, surface = menuBgColor,
modifier = Modifier.width(220.dp), onSurface = textColor
properties = )
PopupProperties(
focusable = true,
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) { ) {
ProfilePhotoMenuItem( DropdownMenu(
icon = if (isBlocked) TablerIcons.CircleCheck else TablerIcons.Ban, expanded = expanded,
text = if (isBlocked) "Unblock User" else "Block User", onDismissRequest = onDismiss,
onClick = onBlockClick, modifier = Modifier
tintColor = PrimaryBlue, .defaultMinSize(minWidth = 196.dp)
textColor = if (isDarkTheme) Color.White else Color.Black .background(menuBgColor),
) properties =
PopupProperties(
focusable = true,
dismissOnBackPress = true,
dismissOnClickOutside = true
)
) {
ProfilePhotoMenuItem(
icon = if (isBlocked) TelegramIcons.Done else TelegramIcons.Block,
text = if (isBlocked) "Unblock User" else "Block User",
onClick = onBlockClick,
tintColor = iconColor,
textColor = textColor
)
Box( ProfilePhotoMenuItem(
modifier = icon = TelegramIcons.Delete,
Modifier.fillMaxWidth() text = "Clear Chat History",
.padding(horizontal = 16.dp, vertical = 4.dp) onClick = onClearChatClick,
.height(0.5.dp) tintColor = Color(0xFFFF3B30),
.background(dividerColor) textColor = Color(0xFFFF3B30)
) )
}
ProfilePhotoMenuItem(
icon = TablerIcons.Trash,
text = "Clear Chat History",
onClick = onClearChatClick,
tintColor = Color(0xFFFF3B30),
textColor = Color(0xFFFF3B30)
)
} }
} }
@Composable @Composable
private fun ProfilePhotoMenuItem( private fun ProfilePhotoMenuItem(
icon: ImageVector, icon: Painter,
text: String, text: String,
onClick: () -> Unit, onClick: () -> Unit,
tintColor: Color, tintColor: Color,
textColor: Color textColor: Color
) { ) {
val interactionSource = remember { MutableInteractionSource() } // Telegram: 48dp height, 18dp horizontal padding, 16sp text, icon→text gap 43dp
Box(
DropdownMenuItem( modifier = Modifier
text = { .fillMaxWidth()
Row( .defaultMinSize(minWidth = 196.dp, minHeight = 48.dp)
verticalAlignment = Alignment.CenterVertically, .clickable(onClick = onClick)
modifier = Modifier.fillMaxWidth() .padding(horizontal = 18.dp),
) { contentAlignment = Alignment.CenterStart
Icon( ) {
imageVector = icon, Row(
contentDescription = null, verticalAlignment = Alignment.CenterVertically
tint = tintColor, ) {
modifier = Modifier.size(24.dp) Icon(
) painter = icon,
Spacer(modifier = Modifier.width(14.dp)) contentDescription = null,
Text( tint = tintColor,
text = text, modifier = Modifier.size(24.dp)
color = textColor, )
fontSize = 16.sp, Spacer(modifier = Modifier.width(19.dp))
fontWeight = FontWeight.Medium Text(
) text = text,
} color = textColor,
}, fontSize = 16.sp
onClick = onClick, )
modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp), }
interactionSource = interactionSource, }
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp)
)
} }

View File

@@ -24,7 +24,7 @@ import androidx.compose.ui.unit.sp
import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.Bug import compose.icons.tablericons.Bug
import compose.icons.tablericons.Trash import com.rosetta.messenger.ui.icons.TelegramIcons
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
@@ -165,7 +165,7 @@ fun DebugLogsBottomSheet(
Row { Row {
IconButton(onClick = onClearLogs) { IconButton(onClick = onClearLogs) {
Icon( Icon(
TablerIcons.Trash, painter = TelegramIcons.Delete,
contentDescription = "Clear logs", contentDescription = "Clear logs",
tint = secondaryTextColor.copy(alpha = 0.6f), tint = secondaryTextColor.copy(alpha = 0.6f),
modifier = Modifier.size(22.dp) modifier = Modifier.size(22.dp)

View File

@@ -66,6 +66,7 @@ import com.rosetta.messenger.ui.utils.SystemBarsStyleUtils
import com.yalantis.ucrop.UCrop import com.yalantis.ucrop.UCrop
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import ja.burhanrashid52.photoeditor.PhotoEditor import ja.burhanrashid52.photoeditor.PhotoEditor
import ja.burhanrashid52.photoeditor.PhotoEditorView import ja.burhanrashid52.photoeditor.PhotoEditorView
import ja.burhanrashid52.photoeditor.SaveSettings import ja.burhanrashid52.photoeditor.SaveSettings
@@ -472,7 +473,7 @@ fun ImageEditorScreen(
modifier = Modifier.align(Alignment.CenterStart) modifier = Modifier.align(Alignment.CenterStart)
) { ) {
Icon( Icon(
TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Close", contentDescription = "Close",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp)
@@ -1102,7 +1103,7 @@ private fun TelegramCaptionBar(
modifier = Modifier.size(32.dp) modifier = Modifier.size(32.dp)
) { ) {
Icon( Icon(
if (showEmojiPicker) TablerIcons.Keyboard else TablerIcons.MoodSmile, painter = if (showEmojiPicker) TelegramIcons.Keyboard else TelegramIcons.Smile,
contentDescription = if (showEmojiPicker) "Keyboard" else "Emoji", contentDescription = if (showEmojiPicker) "Keyboard" else "Emoji",
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(26.dp) modifier = Modifier.size(26.dp)
@@ -1699,7 +1700,7 @@ fun MultiImageEditorScreen(
modifier = Modifier.align(Alignment.CenterStart) modifier = Modifier.align(Alignment.CenterStart)
) { ) {
Icon( Icon(
TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Close", contentDescription = "Close",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp)

View File

@@ -64,6 +64,7 @@ import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.ui.components.AppleEmojiTextField import com.rosetta.messenger.ui.components.AppleEmojiTextField
import com.rosetta.messenger.ui.components.KeyboardHeightProvider import com.rosetta.messenger.ui.components.KeyboardHeightProvider
@@ -597,7 +598,7 @@ fun MediaPickerBottomSheet(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Icon( Icon(
TablerIcons.Photo, painter = TelegramIcons.Photos,
contentDescription = null, contentDescription = null,
tint = secondaryTextColor, tint = secondaryTextColor,
modifier = Modifier.size(64.dp) modifier = Modifier.size(64.dp)
@@ -862,7 +863,7 @@ private fun MediaPickerHeader(
contentPadding = PaddingValues(horizontal = 20.dp, vertical = 8.dp) contentPadding = PaddingValues(horizontal = 20.dp, vertical = 8.dp)
) { ) {
Icon( Icon(
TablerIcons.Send, painter = TelegramIcons.Send,
contentDescription = "Send", contentDescription = "Send",
modifier = Modifier.size(18.dp), modifier = Modifier.size(18.dp),
tint = Color.White tint = Color.White
@@ -897,7 +898,7 @@ private fun QuickActionsRow(
) { ) {
// Camera button // Camera button
QuickActionButton( QuickActionButton(
icon = TablerIcons.Camera, icon = TelegramIcons.Camera,
label = "Camera", label = "Camera",
backgroundColor = PrimaryBlue, backgroundColor = PrimaryBlue,
iconColor = Color.White, iconColor = Color.White,
@@ -907,7 +908,7 @@ private fun QuickActionsRow(
// Avatar button // Avatar button
QuickActionButton( QuickActionButton(
icon = TablerIcons.User, icon = TelegramIcons.Contact,
label = "Avatar", label = "Avatar",
backgroundColor = buttonColor, backgroundColor = buttonColor,
iconColor = iconColor, iconColor = iconColor,
@@ -917,7 +918,7 @@ private fun QuickActionsRow(
// File button // File button
QuickActionButton( QuickActionButton(
icon = TablerIcons.File, icon = TelegramIcons.File,
label = "File", label = "File",
backgroundColor = buttonColor, backgroundColor = buttonColor,
iconColor = iconColor, iconColor = iconColor,
@@ -929,7 +930,7 @@ private fun QuickActionsRow(
@Composable @Composable
private fun QuickActionButton( private fun QuickActionButton(
icon: androidx.compose.ui.graphics.vector.ImageVector, icon: androidx.compose.ui.graphics.painter.Painter,
label: String, label: String,
backgroundColor: Color, backgroundColor: Color,
iconColor: Color, iconColor: Color,
@@ -969,7 +970,7 @@ private fun QuickActionButton(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = icon, painter = icon,
contentDescription = label, contentDescription = label,
tint = iconColor, tint = iconColor,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1125,7 +1126,7 @@ private fun CameraGridItem(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.Camera, painter = TelegramIcons.Camera,
contentDescription = "Camera", contentDescription = "Camera",
tint = Color.White, tint = Color.White,
modifier = Modifier modifier = Modifier
@@ -1143,7 +1144,7 @@ private fun CameraGridItem(
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.Camera, painter = TelegramIcons.Camera,
contentDescription = "Camera", contentDescription = "Camera",
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier modifier = Modifier
@@ -1262,7 +1263,7 @@ private fun MediaGridItem(
) { ) {
if (isSelected) { if (isSelected) {
Icon( Icon(
imageVector = TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color.White, tint = Color.White,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -1586,7 +1587,7 @@ fun PhotoPreviewWithCaptionScreen(
modifier = Modifier.align(Alignment.CenterStart) modifier = Modifier.align(Alignment.CenterStart)
) { ) {
Icon( Icon(
TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Close", contentDescription = "Close",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp)
@@ -1634,7 +1635,7 @@ fun PhotoPreviewWithCaptionScreen(
label = "emojiIcon" label = "emojiIcon"
) { isEmoji -> ) { isEmoji ->
Icon( Icon(
if (isEmoji) TablerIcons.Keyboard else TablerIcons.MoodSmile, painter = if (isEmoji) TelegramIcons.Keyboard else TelegramIcons.Smile,
contentDescription = if (isEmoji) "Keyboard" else "Emoji", contentDescription = if (isEmoji) "Keyboard" else "Emoji",
tint = Color.White.copy(alpha = 0.7f), tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(26.dp) modifier = Modifier.size(26.dp)
@@ -1676,7 +1677,7 @@ fun PhotoPreviewWithCaptionScreen(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
TablerIcons.Send, painter = TelegramIcons.Send,
contentDescription = "Send", contentDescription = "Send",
tint = Color.White, tint = Color.White,
modifier = Modifier modifier = Modifier

View File

@@ -10,8 +10,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
import compose.icons.TablerIcons import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.tablericons.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -272,7 +271,7 @@ fun MessageInputBar(
horizontalArrangement = Arrangement.Center horizontalArrangement = Arrangement.Center
) { ) {
Icon( Icon(
TablerIcons.Ban, painter = TelegramIcons.Block,
contentDescription = null, contentDescription = null,
tint = Color(0xFFFF6B6B), tint = Color(0xFFFF6B6B),
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
@@ -419,7 +418,7 @@ fun MessageInputBar(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Cancel", contentDescription = "Cancel",
tint = if (isDarkTheme) Color.White.copy(alpha = 0.5f) tint = if (isDarkTheme) Color.White.copy(alpha = 0.5f)
else Color.Black.copy(alpha = 0.4f), else Color.Black.copy(alpha = 0.4f),
@@ -442,7 +441,7 @@ fun MessageInputBar(
modifier = Modifier.size(40.dp) modifier = Modifier.size(40.dp)
) { ) {
Icon( Icon(
TablerIcons.Paperclip, painter = TelegramIcons.Attach,
contentDescription = "Attach", contentDescription = "Attach",
tint = if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.6f) tint = if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.6f)
else Color(0xFF8E8E93).copy(alpha = 0.6f), else Color(0xFF8E8E93).copy(alpha = 0.6f),
@@ -485,8 +484,8 @@ fun MessageInputBar(
modifier = Modifier.size(40.dp) modifier = Modifier.size(40.dp)
) { ) {
Icon( Icon(
if (showEmojiPicker) TablerIcons.Keyboard painter = if (showEmojiPicker) TelegramIcons.Keyboard
else TablerIcons.MoodSmile, else TelegramIcons.Smile,
contentDescription = "Emoji", contentDescription = "Emoji",
tint = if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.6f) tint = if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.6f)
else Color(0xFF8E8E93).copy(alpha = 0.6f), else Color(0xFF8E8E93).copy(alpha = 0.6f),

View File

@@ -0,0 +1,93 @@
package com.rosetta.messenger.ui.icons
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import com.rosetta.messenger.R
/**
* Centralized Telegram icon provider.
* Maps to Telegram's native PNG drawable resources.
* Use with Icon(painter = TelegramIcons.ArrowBack, ...)
*/
object TelegramIcons {
// ═══════════════════════════════════════════════════════════
// 🧭 NAVIGATION
// ═══════════════════════════════════════════════════════════
val ArrowBack: Painter @Composable get() = painterResource(R.drawable.msg_arrow_back)
val Close: Painter @Composable get() = painterResource(R.drawable.msg_close)
val ChevronDown: Painter @Composable get() = painterResource(R.drawable.ic_arrow_drop_down)
val ChevronRight: Painter @Composable get() = painterResource(R.drawable.msg_arrowright)
val More: Painter @Composable get() = painterResource(R.drawable.ic_ab_other)
// ═══════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════
val Done: Painter @Composable get() = painterResource(R.drawable.ic_ab_done)
val Add: Painter @Composable get() = painterResource(R.drawable.msg_add)
val Edit: Painter @Composable get() = painterResource(R.drawable.msg_edit)
val Delete: Painter @Composable get() = painterResource(R.drawable.msg_delete)
val Reply: Painter @Composable get() = painterResource(R.drawable.ic_ab_reply)
val Retry: Painter @Composable get() = painterResource(R.drawable.msg_retry)
val Send: Painter @Composable get() = painterResource(R.drawable.msg_send)
// ═══════════════════════════════════════════════════════════
// 🔔 NOTIFICATIONS
// ═══════════════════════════════════════════════════════════
val Notifications: Painter @Composable get() = painterResource(R.drawable.msg_notifications)
val Mute: Painter @Composable get() = painterResource(R.drawable.msg_mute)
// ═══════════════════════════════════════════════════════════
// 📌 CHAT ACTIONS
// ═══════════════════════════════════════════════════════════
val Pin: Painter @Composable get() = painterResource(R.drawable.msg_pin)
val Unpin: Painter @Composable get() = painterResource(R.drawable.msg_unpin)
val Block: Painter @Composable get() = painterResource(R.drawable.msg_block)
val Message: Painter @Composable get() = painterResource(R.drawable.msg_message)
// ═══════════════════════════════════════════════════════════
// 🔒 SECURITY
// ═══════════════════════════════════════════════════════════
val Secret: Painter @Composable get() = painterResource(R.drawable.msg_secret)
val Unlock: Painter @Composable get() = painterResource(R.drawable.menu_unlock)
val Fingerprint: Painter @Composable get() = painterResource(R.drawable.fingerprint)
// ═══════════════════════════════════════════════════════════
// 📷 MEDIA
// ═══════════════════════════════════════════════════════════
val Camera: Painter @Composable get() = painterResource(R.drawable.msg_camera)
val AddPhoto: Painter @Composable get() = painterResource(R.drawable.msg_addphoto)
val Photos: Painter @Composable get() = painterResource(R.drawable.msg_photos)
val File: Painter @Composable get() = painterResource(R.drawable.msg_sendfile)
val Attach: Painter @Composable get() = painterResource(R.drawable.input_attach)
// ═══════════════════════════════════════════════════════════
// 😊 INPUT
// ═══════════════════════════════════════════════════════════
val Smile: Painter @Composable get() = painterResource(R.drawable.input_smile)
val Keyboard: Painter @Composable get() = painterResource(R.drawable.input_keyboard)
// ═══════════════════════════════════════════════════════════
// ⚙️ SETTINGS
// ═══════════════════════════════════════════════════════════
val Palette: Painter @Composable get() = painterResource(R.drawable.msg_palette)
val Theme: Painter @Composable get() = painterResource(R.drawable.msg_theme)
val Customize: Painter @Composable get() = painterResource(R.drawable.msg_customize)
val Leave: Painter @Composable get() = painterResource(R.drawable.msg_leave)
// ═══════════════════════════════════════════════════════════
// 👤 CONTACTS
// ═══════════════════════════════════════════════════════════
val Contact: Painter @Composable get() = painterResource(R.drawable.msg_contact)
val AddContact: Painter @Composable get() = painterResource(R.drawable.msg_addcontact)
// ═══════════════════════════════════════════════════════════
// STATUS & INFO
// ═══════════════════════════════════════════════════════════
val Clock: Painter @Composable get() = painterResource(R.drawable.msg_autodelete)
val Warning: Painter @Composable get() = painterResource(R.drawable.msg_warning)
val Info: Painter @Composable get() = painterResource(R.drawable.msg_info)
val Views: Painter @Composable get() = painterResource(R.drawable.msg_views)
}

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -28,6 +29,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -55,6 +57,16 @@ fun AppearanceScreen(
accountName: String = "", accountName: String = "",
avatarRepository: AvatarRepository? = null avatarRepository: AvatarRepository? = null
) { ) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
@@ -101,7 +113,7 @@ fun AppearanceScreen(
) { ) {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = Color.White tint = Color.White
) )
@@ -441,7 +453,7 @@ private fun ColorCircleItem(
val borderColor by animateColorAsState( val borderColor by animateColorAsState(
targetValue = if (isSelected) { targetValue = if (isSelected) {
Color.White if (isDarkTheme) Color.White else Color(0xFF222222)
} else { } else {
Color.Transparent Color.Transparent
}, },
@@ -456,7 +468,7 @@ private fun ColorCircleItem(
.clip(CircleShape) .clip(CircleShape)
.border( .border(
width = if (isSelected) 2.5.dp else 0.5.dp, width = if (isSelected) 2.5.dp else 0.5.dp,
color = if (isSelected) borderColor else Color.White.copy(alpha = 0.12f), color = if (isSelected) borderColor else if (isDarkTheme) Color.White.copy(alpha = 0.12f) else Color.Black.copy(alpha = 0.12f),
shape = CircleShape shape = CircleShape
) )
.clickable(onClick = onClick), .clickable(onClick = onClick),
@@ -509,7 +521,7 @@ private fun ColorCircleItem(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = "Selected", contentDescription = "Selected",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)

View File

@@ -30,6 +30,7 @@ import com.airbnb.lottie.compose.*
import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
// Auth colors // Auth colors
@@ -82,6 +83,14 @@ fun BiometricEnableScreen(
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val context = LocalContext.current val context = LocalContext.current
val view = LocalView.current val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
// Function to hide keyboard // Function to hide keyboard
fun hideKeyboard() { fun hideKeyboard() {
@@ -141,7 +150,7 @@ fun BiometricEnableScreen(
) { ) {
IconButton(onClick = onBack, enabled = !isLoading) { IconButton(onClick = onBack, enabled = !isLoading) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = textColor.copy(alpha = 0.6f) tint = textColor.copy(alpha = 0.6f)
) )
@@ -312,7 +321,7 @@ fun BiometricEnableScreen(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
imageVector = TablerIcons.AlertCircle, painter = TelegramIcons.Warning,
contentDescription = null, contentDescription = null,
tint = errorRed, tint = errorRed,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
@@ -377,7 +386,7 @@ fun BiometricEnableScreen(
) )
} else { } else {
Icon( Icon(
imageVector = TablerIcons.Fingerprint, painter = TelegramIcons.Fingerprint,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(22.dp) modifier = Modifier.size(22.dp)
) )
@@ -408,7 +417,7 @@ fun BiometricEnableScreen(
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
Icon( Icon(
imageVector = TablerIcons.ShieldLock, painter = TelegramIcons.Secret,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)

View File

@@ -32,8 +32,6 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Block import androidx.compose.material.icons.outlined.Block
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
@@ -43,6 +41,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
@@ -62,6 +61,7 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -93,6 +93,7 @@ import com.rosetta.messenger.ui.components.metaball.ProfileMetaballEffect
import com.rosetta.messenger.ui.onboarding.PrimaryBlue import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.utils.AttachmentFileManager import com.rosetta.messenger.utils.AttachmentFileManager
import com.rosetta.messenger.utils.AvatarFileManager import com.rosetta.messenger.utils.AvatarFileManager
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -220,6 +221,10 @@ fun OtherProfileScreen(
val chatsListViewModel: ChatsListViewModel = viewModel() val chatsListViewModel: ChatsListViewModel = viewModel()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
// 🔕 Mute state
val preferencesManager = remember { com.rosetta.messenger.data.PreferencesManager(context) }
var notificationsEnabled by remember { mutableStateOf(true) }
// 🔥 Загружаем статус блокировки при открытии экрана // 🔥 Загружаем статус блокировки при открытии экрана
LaunchedEffect(user.publicKey) { isBlocked = chatsListViewModel.isUserBlocked(user.publicKey) } LaunchedEffect(user.publicKey) { isBlocked = chatsListViewModel.isUserBlocked(user.publicKey) }
@@ -244,6 +249,14 @@ fun OtherProfileScreen(
remember(currentUserPublicKey) { currentUserPublicKey.trim() } remember(currentUserPublicKey) { currentUserPublicKey.trim() }
val activeAccountPrivateKey = val activeAccountPrivateKey =
remember(currentUserPrivateKey) { currentUserPrivateKey.trim() } remember(currentUserPrivateKey) { currentUserPrivateKey.trim() }
// 🔕 Load mute state from preferences
LaunchedEffect(activeAccountPublicKey, user.publicKey) {
if (activeAccountPublicKey.isNotBlank()) {
notificationsEnabled = !preferencesManager.isChatMuted(activeAccountPublicKey, user.publicKey)
}
}
val dialogKey = val dialogKey =
remember(activeAccountPublicKey, user.publicKey) { remember(activeAccountPublicKey, user.publicKey) {
if (activeAccountPublicKey.isBlank()) { if (activeAccountPublicKey.isBlank()) {
@@ -522,14 +535,14 @@ fun OtherProfileScreen(
) )
) { ) {
item { item {
Spacer(modifier = Modifier.height(16.dp))
Spacer(modifier = Modifier.height(6.dp))
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// 📋 ACCOUNT SECTION // 📋 INFORMATION SECTION — первый элемент
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
TelegramSectionTitle(title = "Account", isDarkTheme = isDarkTheme) TelegramSectionTitle(
title = "Information",
isDarkTheme = isDarkTheme,
color = PrimaryBlue
)
if (user.username.isNotBlank()) { if (user.username.isNotBlank()) {
TelegramCopyField( TelegramCopyField(
@@ -538,6 +551,12 @@ fun OtherProfileScreen(
label = "Username", label = "Username",
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
Divider(
color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0),
thickness = 0.5.dp,
modifier = Modifier.padding(start = 16.dp)
)
} }
TelegramCopyField( TelegramCopyField(
@@ -547,11 +566,21 @@ fun OtherProfileScreen(
isDarkTheme = isDarkTheme isDarkTheme = isDarkTheme
) )
Spacer(modifier = Modifier.height(12.dp)) // ═══════════════════════════════════════════════════════════
// Разделитель секций
// ═══════════════════════════════════════════════════════════
Box(
modifier = Modifier
.fillMaxWidth()
.height(8.dp)
.background(if (isDarkTheme) Color(0xFF0F0F0F) else Color(0xFFF0F0F0))
)
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// ✉️ WRITE MESSAGE BUTTON // ✉️ WRITE MESSAGE BUTTON
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Spacer(modifier = Modifier.height(12.dp))
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -573,7 +602,7 @@ fun OtherProfileScreen(
) )
) { ) {
Icon( Icon(
TablerIcons.MessageCircle2, painter = TelegramIcons.Message,
contentDescription = null, contentDescription = null,
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = Color.White tint = Color.White
@@ -588,13 +617,32 @@ fun OtherProfileScreen(
} }
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(12.dp))
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// 📚 SHARED CONTENT // 🔔 NOTIFICATIONS SECTION
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
TelegramSectionTitle(title = "Shared", isDarkTheme = isDarkTheme) TelegramToggleItem(
icon = if (notificationsEnabled) TelegramIcons.Notifications else TelegramIcons.Mute,
title = "Notifications",
subtitle = if (notificationsEnabled) "On" else "Off",
isEnabled = notificationsEnabled,
onToggle = {
notificationsEnabled = !notificationsEnabled
coroutineScope.launch {
preferencesManager.setChatMuted(
activeAccountPublicKey,
user.publicKey,
!notificationsEnabled
)
}
},
isDarkTheme = isDarkTheme
)
// ═══════════════════════════════════════════════════════════
// 📚 SHARED CONTENT (без разделителя — сразу табы)
// ═══════════════════════════════════════════════════════════
OtherProfileSharedTabs( OtherProfileSharedTabs(
selectedTab = selectedTab, selectedTab = selectedTab,
onTabSelected = { tab -> onTabSelected = { tab ->
@@ -1202,7 +1250,7 @@ private fun OtherProfileActionButtons(
horizontalArrangement = Arrangement.spacedBy(12.dp) horizontalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
OtherProfileActionButton( OtherProfileActionButton(
icon = TablerIcons.MessageCircle2, icon = TelegramIcons.Message,
title = "Message", title = "Message",
subtitle = "Open chat", subtitle = "Open chat",
accent = PrimaryBlue, accent = PrimaryBlue,
@@ -1211,7 +1259,7 @@ private fun OtherProfileActionButtons(
onClick = onMessageClick onClick = onMessageClick
) )
OtherProfileActionButton( OtherProfileActionButton(
icon = if (isMuted) TablerIcons.BellOff else TablerIcons.BellRinging2, icon = if (isMuted) TelegramIcons.Mute else TelegramIcons.Notifications,
title = if (isMuted) "Muted" else "Mute", title = if (isMuted) "Muted" else "Mute",
subtitle = if (isMuted) "Tap to unmute" else "Silence notifications", subtitle = if (isMuted) "Tap to unmute" else "Silence notifications",
accent = accent =
@@ -1229,7 +1277,7 @@ private fun OtherProfileActionButtons(
@Composable @Composable
private fun OtherProfileActionButton( private fun OtherProfileActionButton(
icon: ImageVector, icon: Painter,
title: String, title: String,
subtitle: String, subtitle: String,
accent: Color, accent: Color,
@@ -1260,7 +1308,7 @@ private fun OtherProfileActionButton(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = icon, painter = icon,
contentDescription = title, contentDescription = title,
tint = accent, tint = accent,
modifier = Modifier.size(19.dp) modifier = Modifier.size(19.dp)
@@ -1504,7 +1552,7 @@ private fun OtherProfileFileList(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.File, painter = TelegramIcons.File,
contentDescription = null, contentDescription = null,
tint = PrimaryBlue, tint = PrimaryBlue,
modifier = Modifier.size(18.dp) modifier = Modifier.size(18.dp)
@@ -1661,8 +1709,11 @@ private fun CollapsingOtherProfileHeader(
val avatarFontSize = androidx.compose.ui.unit.lerp(40.sp, 12.sp, collapseProgress) val avatarFontSize = androidx.compose.ui.unit.lerp(40.sp, 12.sp, collapseProgress)
// Text animation - always centered // Text animation - always centered
val textDefaultY = expandedHeight - 48.dp val textDefaultY = expandedHeight - 70.dp
val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT_OTHER / 2 // Collapsed: center text block vertically in collapsed header
// Text block is ~42dp tall (name 18sp ~22dp + 2dp spacer + online 13sp ~18dp)
val textBlockHeight = 42.dp
val textCollapsedY = statusBarHeight + (COLLAPSED_HEADER_HEIGHT_OTHER - textBlockHeight) / 2
val textY = androidx.compose.ui.unit.lerp(textDefaultY, textCollapsedY, collapseProgress) val textY = androidx.compose.ui.unit.lerp(textDefaultY, textCollapsedY, collapseProgress)
val nameFontSize = androidx.compose.ui.unit.lerp(24.sp, 18.sp, collapseProgress) val nameFontSize = androidx.compose.ui.unit.lerp(24.sp, 18.sp, collapseProgress)
@@ -1673,7 +1724,19 @@ private fun CollapsingOtherProfileHeader(
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
val textColor = Color.White val textColor = Color.White
// ═══════════════════════════════════════════════════════════
// 🔙 BUTTONS Y - At top when expanded, centered when collapsed
// ═══════════════════════════════════════════════════════════
val buttonsExpandedY = statusBarHeight + 8.dp
val buttonsCollapsedY = statusBarHeight + (COLLAPSED_HEADER_HEIGHT_OTHER - 48.dp) / 2
val buttonsY = androidx.compose.ui.unit.lerp(buttonsExpandedY, buttonsCollapsedY, collapseProgress)
Box(modifier = Modifier.fillMaxWidth().height(headerHeight).clipToBounds()) { Box(modifier = Modifier.fillMaxWidth().height(headerHeight).clipToBounds()) {
// Solid opaque background floor — prevents content from bleeding through
Box(modifier = Modifier.matchParentSize().background(
if (isDarkTheme) Color(0xFF1C1C1E) else Color(0xFFEFEFF4)
))
// Expansion fraction — computed early so blur can fade during expansion // Expansion fraction — computed early so blur can fade during expansion
val expandFractionEarly = expansionProgress.coerceIn(0f, 1f) val expandFractionEarly = expansionProgress.coerceIn(0f, 1f)
val blurAlpha = (1f - expandFractionEarly * 2.5f).coerceIn(0f, 1f) val blurAlpha = (1f - expandFractionEarly * 2.5f).coerceIn(0f, 1f)
@@ -1695,6 +1758,28 @@ private fun CollapsingOtherProfileHeader(
} }
} }
// ═══════════════════════════════════════════════════════════
// 🌅 BOTTOM GRADIENT — плавно исчезает когда аватарка раскрывается
// ═══════════════════════════════════════════════════════════
val gradientAlpha = (1f - expandFractionEarly * 1.2f).coerceIn(0f, 1f)
if (gradientAlpha > 0.01f) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(80.dp)
.align(Alignment.BottomCenter)
.graphicsLayer { alpha = gradientAlpha }
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.35f)
)
)
)
)
}
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// 👤 AVATAR — Telegram-style expansion on pull-down // 👤 AVATAR — Telegram-style expansion on pull-down
// При скролле вверх: metaball merge с Dynamic Island // При скролле вверх: metaball merge с Dynamic Island
@@ -1795,19 +1880,56 @@ private fun CollapsingOtherProfileHeader(
} }
} }
// Gradient overlays when avatar is expanded
if (expansionAvatarAlpha > 0.01f) {
// Top gradient
Box(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.align(Alignment.TopCenter)
.graphicsLayer { alpha = expandFraction.coerceIn(0f, 1f) }
.background(
Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = 0.6f),
Color.Transparent
)
)
)
)
// Bottom gradient
Box(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.align(Alignment.BottomCenter)
.graphicsLayer { alpha = expandFraction.coerceIn(0f, 1f) }
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.6f)
)
)
)
)
}
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// 🔙 BACK BUTTON // 🔙 BACK BUTTON - At top when expanded, centered when collapsed
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Box( Box(
modifier = modifier =
Modifier.padding(top = statusBarHeight) Modifier
.padding(start = 4.dp, top = 4.dp) .align(Alignment.TopStart)
.offset(x = 4.dp, y = buttonsY)
.size(48.dp), .size(48.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) { IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
Icon( Icon(
imageVector = Icons.Filled.ArrowBack, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1816,19 +1938,17 @@ private fun CollapsingOtherProfileHeader(
} }
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// ⋮ MENU BUTTON (top right corner) // ⋮ MENU BUTTON - At top when expanded, centered when collapsed
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Box( Box(
modifier = modifier =
Modifier.align(Alignment.TopEnd) Modifier.align(Alignment.TopEnd)
.padding(top = statusBarHeight) .offset(x = -4.dp, y = buttonsY),
.padding(end = 4.dp, top = 4.dp)
.size(48.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
IconButton(onClick = { onAvatarMenuChange(true) }, modifier = Modifier.size(48.dp)) { IconButton(onClick = { onAvatarMenuChange(true) }, modifier = Modifier.size(48.dp)) {
Icon( Icon(
imageVector = Icons.Default.MoreVert, painter = TelegramIcons.More,
contentDescription = "Profile menu", contentDescription = "Profile menu",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1857,17 +1977,7 @@ private fun CollapsingOtherProfileHeader(
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Column( Column(
modifier = modifier =
Modifier.align(Alignment.TopCenter).offset(y = textY).graphicsLayer { Modifier.align(Alignment.TopCenter).offset(y = textY),
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
},
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
// Name + Verified Badge // Name + Verified Badge

View File

@@ -7,12 +7,14 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.* import androidx.compose.material3.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -25,6 +27,16 @@ fun ProfileLogsScreen(
onBack: () -> Unit, onBack: () -> Unit,
onClearLogs: () -> Unit onClearLogs: () -> Unit
) { ) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7) val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -63,7 +75,7 @@ fun ProfileLogsScreen(
) { ) {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = textColor tint = textColor
) )
@@ -79,7 +91,7 @@ fun ProfileLogsScreen(
) )
IconButton(onClick = onClearLogs) { IconButton(onClick = onClearLogs) {
Icon( Icon(
imageVector = TablerIcons.Trash, painter = TelegramIcons.Delete,
contentDescription = "Clear logs", contentDescription = "Clear logs",
tint = secondaryTextColor tint = secondaryTextColor
) )

View File

@@ -43,8 +43,7 @@ import androidx.compose.ui.window.PopupProperties
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import compose.icons.TablerIcons import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.tablericons.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -292,7 +291,7 @@ fun ProfilePhotoPicker(
modifier = Modifier.align(Alignment.CenterStart) modifier = Modifier.align(Alignment.CenterStart)
) { ) {
Icon( Icon(
imageVector = TablerIcons.X, painter = TelegramIcons.Close,
contentDescription = "Close", contentDescription = "Close",
tint = textColor, tint = textColor,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -350,7 +349,7 @@ fun ProfilePhotoPicker(
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Icon( Icon(
imageVector = TablerIcons.Photo, painter = TelegramIcons.Photos,
contentDescription = null, contentDescription = null,
tint = textColor.copy(alpha = 0.4f), tint = textColor.copy(alpha = 0.4f),
modifier = Modifier.size(64.dp) modifier = Modifier.size(64.dp)
@@ -501,7 +500,7 @@ private fun PermissionRequest(
modifier = Modifier.padding(32.dp) modifier = Modifier.padding(32.dp)
) { ) {
Icon( Icon(
imageVector = TablerIcons.Photo, painter = TelegramIcons.Photos,
contentDescription = null, contentDescription = null,
tint = textColor.copy(alpha = 0.5f), tint = textColor.copy(alpha = 0.5f),
modifier = Modifier.size(64.dp) modifier = Modifier.size(64.dp)

View File

@@ -44,6 +44,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -74,6 +75,11 @@ import java.util.Locale
import com.rosetta.messenger.utils.ImageCropHelper import com.rosetta.messenger.utils.ImageCropHelper
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import com.rosetta.messenger.R
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@@ -278,6 +284,16 @@ fun ProfileScreen(
dialogDao: com.rosetta.messenger.database.DialogDao? = null, dialogDao: com.rosetta.messenger.database.DialogDao? = null,
backgroundBlurColorId: String = "avatar" backgroundBlurColorId: String = "avatar"
) { ) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = false
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
val context = LocalContext.current val context = LocalContext.current
val activity = context as? FragmentActivity val activity = context as? FragmentActivity
val biometricManager = remember { BiometricAuthManager(context) } val biometricManager = remember { BiometricAuthManager(context) }
@@ -817,7 +833,7 @@ fun ProfileScreen(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
TelegramToggleItem( TelegramToggleItem(
icon = TablerIcons.Bell, icon = TelegramIcons.Notifications,
title = "Push Notifications", title = "Push Notifications",
isEnabled = notificationsEnabled, isEnabled = notificationsEnabled,
onToggle = { onToggle = {
@@ -837,7 +853,7 @@ fun ProfileScreen(
TelegramSectionTitle(title = "Settings", isDarkTheme = isDarkTheme) TelegramSectionTitle(title = "Settings", isDarkTheme = isDarkTheme)
TelegramSettingsItem( TelegramSettingsItem(
icon = TablerIcons.Palette, icon = TelegramIcons.Theme,
title = "Theme", title = "Theme",
onClick = onNavigateToTheme, onClick = onNavigateToTheme,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
@@ -845,7 +861,7 @@ fun ProfileScreen(
) )
TelegramSettingsItem( TelegramSettingsItem(
icon = TablerIcons.Brush, icon = TelegramIcons.Customize,
title = "Appearance", title = "Appearance",
onClick = onNavigateToAppearance, onClick = onNavigateToAppearance,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
@@ -853,7 +869,7 @@ fun ProfileScreen(
) )
TelegramSettingsItem( TelegramSettingsItem(
icon = TablerIcons.Lock, icon = TelegramIcons.Secret,
title = "Safety", title = "Safety",
onClick = onNavigateToSafety, onClick = onNavigateToSafety,
isDarkTheme = isDarkTheme, isDarkTheme = isDarkTheme,
@@ -863,7 +879,7 @@ fun ProfileScreen(
// Biometric settings (only show if available) // Biometric settings (only show if available)
if (biometricAvailable is BiometricAvailability.Available && activity != null) { if (biometricAvailable is BiometricAvailability.Available && activity != null) {
TelegramSettingsItem( TelegramSettingsItem(
icon = TablerIcons.Fingerprint, icon = TelegramIcons.Fingerprint,
title = "Biometric Authentication", title = "Biometric Authentication",
onClick = { onClick = {
onNavigateToBiometric() onNavigateToBiometric()
@@ -972,10 +988,9 @@ fun ProfileScreen(
// Positioned at bottom-right of header, half overlapping content area // Positioned at bottom-right of header, half overlapping content area
// Fades out when collapsed or when avatar is expanded // Fades out when collapsed or when avatar is expanded
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
val cameraButtonAlpha = (1f - collapseProgress * 2.5f).coerceIn(0f, 1f) * val cameraButtonSize = 60.dp
(1f - expansionProgress * 4f).coerceIn(0f, 1f) val cameraButtonAlpha = (1f - collapseProgress * 2f).coerceIn(0f, 1f)
if (cameraButtonAlpha > 0.01f) { if (cameraButtonAlpha > 0.01f) {
val cameraButtonSize = 52.dp
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.TopEnd) .align(Alignment.TopEnd)
@@ -991,15 +1006,15 @@ fun ProfileScreen(
clip = false clip = false
) )
.clip(CircleShape) .clip(CircleShape)
.background(Color.White) .background(if (isDarkTheme) Color.White else PrimaryBlue)
.clickable { showPhotoPicker = true }, .clickable { showPhotoPicker = true },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = TablerIcons.CameraPlus, painter = TelegramIcons.AddPhoto,
contentDescription = "Change avatar", contentDescription = "Change avatar",
tint = Color(0xFF8E8E93), tint = if (isDarkTheme) Color(0xFF8E8E93) else Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(26.dp).offset(x = 2.dp)
) )
} }
} }
@@ -1089,7 +1104,10 @@ private fun CollapsingProfileHeader(
// 📝 TEXT - внизу header зоны, внутри блока // 📝 TEXT - внизу header зоны, внутри блока
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
val textDefaultY = expandedHeight - 70.dp // Ближе к аватарке val textDefaultY = expandedHeight - 70.dp // Ближе к аватарке
val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT / 2 // Collapsed: center text block vertically in collapsed header
// Text block is ~35dp tall (name 18sp + 2dp spacer + online 13sp)
val textBlockHeight = 35.dp
val textCollapsedY = statusBarHeight + (COLLAPSED_HEADER_HEIGHT - textBlockHeight) / 2
// Текст меняет позицию только при collapse, НЕ при overscroll // Текст меняет позицию только при collapse, НЕ при overscroll
val textY = androidx.compose.ui.unit.lerp(textDefaultY, textCollapsedY, collapseProgress) val textY = androidx.compose.ui.unit.lerp(textDefaultY, textCollapsedY, collapseProgress)
@@ -1210,31 +1228,60 @@ private fun CollapsingProfileHeader(
} }
} }
// Gradient overlays when avatar is expanded
if (expandFraction > 0.01f) {
// Top gradient
Box(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.align(Alignment.TopCenter)
.graphicsLayer { alpha = expandFraction }
.background(
Brush.verticalGradient(
colors = listOf(
Color.Black.copy(alpha = 0.6f),
Color.Transparent
)
)
)
)
// Bottom gradient
Box(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.align(Alignment.BottomCenter)
.graphicsLayer { alpha = expandFraction }
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.6f)
)
)
)
)
}
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// 🔙 BACK BUTTON - Aligned with text vertical center // 🔙 BACK BUTTON - At top when expanded, centered when collapsed
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
val buttonsExpandedY = statusBarHeight + 8.dp
val buttonsCollapsedY = statusBarHeight + (COLLAPSED_HEADER_HEIGHT - 48.dp) / 2
val buttonsY = androidx.compose.ui.unit.lerp(buttonsExpandedY, buttonsCollapsedY, collapseProgress)
Box( Box(
modifier = modifier =
Modifier Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.offset(x = 4.dp, y = textY) .offset(x = 4.dp, y = buttonsY)
.graphicsLayer {
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
}
.size(48.dp), .size(48.dp),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) { IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1243,23 +1290,12 @@ private fun CollapsingProfileHeader(
} }
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
// ⋮ MENU BUTTON / 💾 SAVE BUTTON - Aligned with text vertical center // ⋮ MENU BUTTON / 💾 SAVE BUTTON - At top when expanded, centered when collapsed
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Box( Box(
modifier = modifier =
Modifier.align(Alignment.TopEnd) Modifier.align(Alignment.TopEnd)
.offset(x = -4.dp, y = textY) .offset(x = -4.dp, y = buttonsY),
.graphicsLayer {
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
},
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
AnimatedVisibility(visible = hasChanges, enter = fadeIn(), exit = fadeOut()) { AnimatedVisibility(visible = hasChanges, enter = fadeIn(), exit = fadeOut()) {
@@ -1283,7 +1319,7 @@ private fun CollapsingProfileHeader(
modifier = Modifier.size(48.dp) modifier = Modifier.size(48.dp)
) { ) {
Icon( Icon(
imageVector = TablerIcons.DotsVertical, painter = TelegramIcons.More,
contentDescription = "Profile menu", contentDescription = "Profile menu",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1312,17 +1348,7 @@ private fun CollapsingProfileHeader(
// ═══════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════
Column( Column(
modifier = modifier =
Modifier.align(Alignment.TopCenter).offset(y = textY).graphicsLayer { Modifier.align(Alignment.TopCenter).offset(y = textY),
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
},
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text( Text(
@@ -1445,7 +1471,7 @@ fun ProfileCard(
modifier = Modifier.align(Alignment.TopStart).statusBarsPadding().padding(4.dp) modifier = Modifier.align(Alignment.TopStart).statusBarsPadding().padding(4.dp)
) { ) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = if (isDarkTheme) Color.White else Color.Black tint = if (isDarkTheme) Color.White else Color.Black
) )
@@ -1690,7 +1716,7 @@ fun TelegramCopyField(value: String, fullValue: String, label: String, isDarkThe
@Composable @Composable
private fun TelegramSettingsItem( private fun TelegramSettingsItem(
icon: ImageVector, icon: Painter,
title: String, title: String,
onClick: () -> Unit, onClick: () -> Unit,
isDarkTheme: Boolean, isDarkTheme: Boolean,
@@ -1712,7 +1738,7 @@ private fun TelegramSettingsItem(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
imageVector = icon, painter = icon,
contentDescription = null, contentDescription = null,
tint = iconColor, tint = iconColor,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1740,8 +1766,8 @@ private fun TelegramSettingsItem(
} }
@Composable @Composable
private fun TelegramToggleItem( fun TelegramToggleItem(
icon: ImageVector, icon: Painter,
title: String, title: String,
subtitle: String? = null, subtitle: String? = null,
isEnabled: Boolean, isEnabled: Boolean,
@@ -1764,7 +1790,7 @@ private fun TelegramToggleItem(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
imageVector = icon, painter = icon,
contentDescription = null, contentDescription = null,
tint = iconColor, tint = iconColor,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1852,7 +1878,7 @@ private fun TelegramToggleItem(
@Composable @Composable
private fun TelegramBiometricItem(isEnabled: Boolean, onToggle: () -> Unit, isDarkTheme: Boolean) { private fun TelegramBiometricItem(isEnabled: Boolean, onToggle: () -> Unit, isDarkTheme: Boolean) {
TelegramToggleItem( TelegramToggleItem(
icon = TablerIcons.Fingerprint, icon = TelegramIcons.Fingerprint,
title = "Biometric Authentication", title = "Biometric Authentication",
isEnabled = isEnabled, isEnabled = isEnabled,
onToggle = onToggle, onToggle = onToggle,
@@ -1872,7 +1898,7 @@ private fun TelegramLogoutItem(onClick: () -> Unit, isDarkTheme: Boolean) {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
imageVector = TablerIcons.Logout, painter = TelegramIcons.Leave,
contentDescription = null, contentDescription = null,
tint = redColor, tint = redColor,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -1903,7 +1929,7 @@ private fun ProfileSectionTitle(title: String, isDarkTheme: Boolean) {
@Composable @Composable
fun ProfileNavigationItem( fun ProfileNavigationItem(
icon: ImageVector, icon: Painter,
iconBackground: Color, iconBackground: Color,
title: String, title: String,
subtitle: String, subtitle: String,
@@ -1934,7 +1960,7 @@ fun ProfileNavigationItem(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
imageVector = icon, painter = icon,
contentDescription = null, contentDescription = null,
tint = Color.White, tint = Color.White,
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
@@ -2120,6 +2146,22 @@ fun FullScreenAvatarViewer(
) )
) )
// Bottom gradient shadow
Box(
modifier = Modifier
.fillMaxWidth()
.height(120.dp)
.align(Alignment.BottomCenter)
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.6f)
)
)
)
)
// Header: back button + name + date // Header: back button + name + date
Row( Row(
modifier = Modifier modifier = Modifier
@@ -2130,7 +2172,7 @@ fun FullScreenAvatarViewer(
) { ) {
IconButton(onClick = { showContent = false }) { IconButton(onClick = { showContent = false }) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Close", contentDescription = "Close",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)
@@ -2158,7 +2200,7 @@ fun FullScreenAvatarViewer(
IconButton(onClick = { /* menu */ }) { IconButton(onClick = { /* menu */ }) {
Icon( Icon(
imageVector = TablerIcons.DotsVertical, painter = TelegramIcons.More,
contentDescription = "Menu", contentDescription = "Menu",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(24.dp) modifier = Modifier.size(24.dp)

View File

@@ -14,6 +14,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -21,6 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -33,6 +35,16 @@ fun ThemeScreen(
onBack: () -> Unit, onBack: () -> Unit,
onThemeModeChange: (String) -> Unit onThemeModeChange: (String) -> Unit
) { ) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7) val surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -63,7 +75,7 @@ fun ThemeScreen(
) { ) {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = textColor tint = textColor
) )
@@ -209,7 +221,7 @@ private fun TelegramThemeOption(
// Radio button // Radio button
if (isSelected) { if (isSelected) {
Icon( Icon(
imageVector = TablerIcons.Check, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color(0xFF007AFF), tint = Color(0xFF007AFF),
modifier = Modifier.size(20.dp) modifier = Modifier.size(20.dp)
@@ -358,7 +370,7 @@ private fun MessageBubble(
if (isMe) { if (isMe) {
// Read checkmarks (DoneAll icon like in real chat) // Read checkmarks (DoneAll icon like in real chat)
Icon( Icon(
imageVector = TablerIcons.Checks, painter = TelegramIcons.Done,
contentDescription = null, contentDescription = null,
tint = Color(0xFF4FC3F7), // Blue checkmarks for read messages tint = Color(0xFF4FC3F7), // Blue checkmarks for read messages
modifier = Modifier.size(14.dp) modifier = Modifier.size(14.dp)

View File

@@ -7,13 +7,16 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons import compose.icons.TablerIcons
import compose.icons.tablericons.* import compose.icons.tablericons.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -24,6 +27,16 @@ fun UpdatesScreen(
isDarkTheme: Boolean, isDarkTheme: Boolean,
onBack: () -> Unit onBack: () -> Unit
) { ) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController = androidx.core.view.WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = !isDarkTheme
window.statusBarColor = android.graphics.Color.TRANSPARENT
}
}
val versionName = remember { BuildConfig.VERSION_NAME } val versionName = remember { BuildConfig.VERSION_NAME }
val buildNumber = remember { BuildConfig.VERSION_CODE.toString() } val buildNumber = remember { BuildConfig.VERSION_CODE.toString() }
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF) val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
@@ -52,7 +65,7 @@ fun UpdatesScreen(
) { ) {
IconButton(onClick = onBack) { IconButton(onClick = onBack) {
Icon( Icon(
imageVector = TablerIcons.ArrowLeft, imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back", contentDescription = "Back",
tint = if (isDarkTheme) Color.White else Color.Black tint = if (isDarkTheme) Color.White else Color.Black
) )

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Some files were not shown because too many files have changed in this diff Show More