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 compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
@@ -75,7 +77,15 @@ fun SetPasswordScreen(
var visible by remember { mutableStateOf(false) }
// 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) }
DisposableEffect(view) {
@@ -106,7 +116,7 @@ fun SetPasswordScreen(
) {
IconButton(onClick = onBack, enabled = !isCreating) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = textColor.copy(alpha = 0.6f)
)
@@ -280,7 +290,7 @@ fun SetPasswordScreen(
else -> Color(0xFF4CAF50)
}
Icon(
imageVector = TablerIcons.Shield,
painter = TelegramIcons.Secret,
contentDescription = null,
tint = strengthColor,
modifier = Modifier.size(16.dp)
@@ -306,7 +316,7 @@ fun SetPasswordScreen(
verticalAlignment = Alignment.Top
) {
Icon(
imageVector = TablerIcons.AlertTriangle,
painter = TelegramIcons.Warning,
contentDescription = null,
tint = Color(0xFFE53935),
modifier = Modifier.size(16.dp)
@@ -446,7 +456,7 @@ fun SetPasswordScreen(
verticalAlignment = Alignment.Top
) {
Icon(
imageVector = TablerIcons.InfoCircle,
painter = TelegramIcons.Info,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(20.dp)

View File

@@ -13,6 +13,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -504,7 +505,7 @@ fun UnlockScreen(
if (isSelected) {
Icon(
TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(20.dp)
@@ -645,7 +646,7 @@ fun UnlockScreen(
)
} else {
Icon(
imageVector = TablerIcons.LockOpen,
painter = TelegramIcons.Unlock,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
@@ -663,7 +664,7 @@ fun UnlockScreen(
enabled = !isUnlocking
) {
Icon(
imageVector = TablerIcons.Fingerprint,
painter = TelegramIcons.Fingerprint,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(22.dp)
@@ -719,7 +720,7 @@ fun UnlockScreen(
TextButton(onClick = onSwitchAccount) {
Icon(
imageVector = TablerIcons.UserPlus,
painter = TelegramIcons.AddContact,
contentDescription = null,
tint = PrimaryBlue,
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.material.icons.Icons
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.runtime.*
import androidx.compose.runtime.collectAsState
@@ -117,6 +120,15 @@ fun ChatDetailScreen(
val database = RosettaDatabase.getDatabase(context)
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
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -192,17 +204,24 @@ fun ChatDetailScreen(
var imageViewerSourceBounds by remember { mutableStateOf<ImageSourceBounds?>(null) }
var imageViewerImages by remember { mutableStateOf<List<ViewableImage>>(emptyList()) }
// 🎨 Управление статус баром
DisposableEffect(isDarkTheme, showImageViewer, window, view) {
if (showImageViewer) {
// 📸 При просмотре фото - чёрные system bars
SystemBarsStyleUtils.applyFullscreenDark(window, view)
} else {
// Обычный режим чата
SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme)
// 🎨 Управление статус баром — ВСЕГДА чёрные иконки в светлой теме
if (!view.isInEditMode) {
SideEffect {
if (showImageViewer) {
SystemBarsStyleUtils.applyFullscreenDark(window, view)
} else {
if (window != null && view != null) {
val ic = androidx.core.view.WindowCompat.getInsetsController(window, view)
window.statusBarColor = android.graphics.Color.TRANSPARENT
ic.isAppearanceLightStatusBars = !isDarkTheme
}
}
}
}
DisposableEffect(Unit) {
onDispose {
// Восстановить при уходе с экрана
SystemBarsStyleUtils.applyChatStatusBar(window, view, isDarkTheme)
}
}
@@ -562,16 +581,10 @@ fun ChatDetailScreen(
}
) {
Icon(
Icons.Default
.Close,
painter = TelegramIcons.Close,
contentDescription =
"Cancel",
tint =
if (isDarkTheme
)
Color.White
else
Color.Black,
tint = headerIconColor,
modifier =
Modifier.size(
24.dp
@@ -657,12 +670,7 @@ fun ChatDetailScreen(
.ContentCopy,
contentDescription =
"Copy",
tint =
if (isDarkTheme
)
Color.White
else
Color.Black,
tint = headerIconColor,
modifier =
Modifier.size(
22.dp
@@ -697,12 +705,7 @@ fun ChatDetailScreen(
.Delete,
contentDescription =
"Delete",
tint =
if (isDarkTheme
)
Color.White
else
Color.Black,
tint = headerIconColor,
modifier =
Modifier.size(
22.dp
@@ -735,8 +738,7 @@ fun ChatDetailScreen(
)
) {
Icon(
Icons.Default
.KeyboardArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription =
"Back",
tint =
@@ -749,7 +751,7 @@ fun ChatDetailScreen(
),
modifier =
Modifier.size(
32.dp
28.dp
)
)
}
@@ -987,6 +989,16 @@ fun ChatDetailScreen(
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 или
// subtitle
@@ -1886,6 +1898,16 @@ fun ChatDetailScreen(
avatarRepository,
onLongClick = {
// 📳 Haptic feedback при долгом нажатии
// Не разрешаем выделять avatar-сообщения
val hasAvatar =
message.attachments
.any {
it.type ==
AttachmentType
.AVATAR
}
if (hasAvatar) return@MessageBubble
hapticFeedback
.performHapticFeedback(
HapticFeedbackType
@@ -1922,7 +1944,14 @@ fun ChatDetailScreen(
}
},
onClick = {
if (isSelectionMode
val hasAvatar =
message.attachments
.any {
it.type ==
AttachmentType
.AVATAR
}
if (isSelectionMode && !hasAvatar
) {
selectedMessages =
if (selectedMessages

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
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.graphicsLayer
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.pointerInput
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 compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import java.text.SimpleDateFormat
import java.util.*
import kotlinx.coroutines.launch
@@ -220,21 +223,22 @@ fun ChatsListScreen(
focusManager.clearFocus()
}
// Update status bar appearance — SideEffect overrides global Theme.kt SideEffect
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as android.app.Activity).window
val insetsController =
androidx.core.view.WindowCompat.getInsetsController(window, view)
// Update status bar appearance — only set once on entry, not every recomposition
// This prevents overriding ChatDetailScreen's status bar when ChatsListScreen is in back stack
DisposableEffect(isDarkTheme) {
val window = (view.context as android.app.Activity).window
val insetsController =
androidx.core.view.WindowCompat.getInsetsController(window, view)
// Status bar — always white icons (header is blue)
insetsController.isAppearanceLightStatusBars = false
window.statusBarColor = android.graphics.Color.TRANSPARENT
// Status bar — always white icons (header is blue)
insetsController.isAppearanceLightStatusBars = false
window.statusBarColor = android.graphics.Color.TRANSPARENT
// Navigation bar: показываем только если есть нативные кнопки
com.rosetta.messenger.ui.utils.NavigationModeUtils
.applyNavigationBarVisibility(insetsController, context, isDarkTheme)
}
// Navigation bar
com.rosetta.messenger.ui.utils.NavigationModeUtils
.applyNavigationBarVisibility(insetsController, context, isDarkTheme)
onDispose { }
}
// Colors - instant change, no animation - 🔥 КЭШИРУЕМ для производительности
@@ -490,21 +494,35 @@ fun ChatsListScreen(
)
val headerColor = avatarColors.backgroundColor
// Header с размытым фоном аватарки
// Header с blur аватарки (fallback = голубой) или акцентным цветом (light)
Box(modifier = Modifier.fillMaxWidth()) {
BlurredAvatarBackground(
publicKey = accountPublicKey,
avatarRepository = avatarRepository,
fallbackColor = headerColor,
blurRadius = 40f,
alpha = 0.6f,
overlayColors =
BackgroundBlurPresets
.getOverlayColors(
backgroundBlurColorId
),
isDarkTheme = isDarkTheme
)
if (isDarkTheme) {
if (backgroundBlurColorId == "solid_blue") {
// Голубой фон
Box(
modifier = Modifier
.matchParentSize()
.background(PrimaryBlueDark)
)
} else {
// Avatar blur (default)
BlurredAvatarBackground(
publicKey = accountPublicKey,
avatarRepository = avatarRepository,
fallbackColor = PrimaryBlueDark,
blurRadius = 40f,
alpha = 0.6f,
overlayColors = emptyList(),
isDarkTheme = isDarkTheme
)
}
} else {
Box(
modifier = Modifier
.matchParentSize()
.background(PrimaryBlue)
)
}
// Content поверх фона
Column(
@@ -689,7 +707,7 @@ fun ChatsListScreen(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(8.dp)
@@ -734,7 +752,7 @@ fun ChatsListScreen(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.Plus,
painter = TelegramIcons.Add,
contentDescription = "Add Account",
tint = if (isDarkTheme) Color(0xFF828282) else Color(0xFF889198),
modifier = Modifier.size(22.dp)
@@ -866,7 +884,7 @@ fun ChatsListScreen(
// Logout
DrawerMenuItemEnhanced(
icon = TablerIcons.Logout,
painter = TelegramIcons.Leave,
text = "Log Out",
iconColor = Color(0xFFFF4444),
textColor = Color(0xFFFF4444),
@@ -923,7 +941,7 @@ fun ChatsListScreen(
navigationIcon = {
IconButton(onClick = { selectedChatKeys = emptySet() }) {
Icon(
TablerIcons.X,
painter = TelegramIcons.Close,
contentDescription = "Close",
tint = Color.White
)
@@ -950,7 +968,7 @@ fun ChatsListScreen(
}
}) {
Icon(
if (allMuted) TablerIcons.Bell else TablerIcons.BellOff,
painter = if (allMuted) TelegramIcons.Notifications else TelegramIcons.Mute,
contentDescription = if (allMuted) "Unmute" else "Mute",
tint = Color.White
)
@@ -964,7 +982,7 @@ fun ChatsListScreen(
selectedChatKeys = emptySet()
}) {
Icon(
TablerIcons.Trash,
painter = TelegramIcons.Delete,
contentDescription = "Delete",
tint = Color.White
)
@@ -974,7 +992,7 @@ fun ChatsListScreen(
Box {
IconButton(onClick = { showSelectionMenu = true }) {
Icon(
TablerIcons.DotsVertical,
painter = TelegramIcons.More,
contentDescription = "More",
tint = Color.White
)
@@ -1000,7 +1018,7 @@ fun ChatsListScreen(
},
leadingIcon = {
Icon(
if (allPinned) TablerIcons.PinnedOff else TablerIcons.Pin,
painter = if (allPinned) TelegramIcons.Unpin else TelegramIcons.Pin,
contentDescription = null,
tint = if (isDarkTheme) Color.White else Color.Black
)
@@ -1028,7 +1046,7 @@ fun ChatsListScreen(
},
leadingIcon = {
Icon(
TablerIcons.Ban,
painter = TelegramIcons.Block,
contentDescription = null,
tint = Color(0xFFE53935)
)
@@ -1089,10 +1107,10 @@ fun ChatsListScreen(
modifier =
Modifier
.align(Alignment.TopEnd)
.offset(x = 2.dp, y = (-2).dp)
.size(8.dp)
.offset(x = 4.dp, y = (-4).dp)
.size(10.dp)
.clip(CircleShape)
.background(if (isDarkTheme) PrimaryBlueDark else PrimaryBlue)
.background(if (isDarkTheme) Color.White else PrimaryBlue)
)
}
}
@@ -1208,7 +1226,7 @@ fun ChatsListScreen(
shape = CircleShape
) {
Icon(
TablerIcons.Edit,
painter = TelegramIcons.Edit,
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(
state = chatListState,
modifier =
Modifier.fillMaxSize()
.nestedScroll(requestsNestedScroll)
.background(
listBackgroundColor
)
@@ -1456,23 +1520,35 @@ fun ChatsListScreen(
key =
"requests_section"
) {
RequestsSection(
count =
requestsCount,
requests =
requests,
isDarkTheme =
isDarkTheme,
onClick = {
onRequestsClick()
AnimatedVisibility(
visible = isRequestsVisible,
enter = expandVertically(
animationSpec = tween(250, easing = FastOutSlowInEasing)
) + fadeIn(animationSpec = tween(200)),
exit = shrinkVertically(
animationSpec = tween(250, easing = FastOutSlowInEasing)
) + fadeOut(animationSpec = tween(200))
) {
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) {
Spacer(modifier = Modifier.width(4.dp))
Icon(
TablerIcons.BellOff,
painter = TelegramIcons.Mute,
contentDescription = "Muted",
tint = secondaryTextColor,
modifier = Modifier.size(14.dp)
@@ -1990,7 +2066,7 @@ fun ChatItem(
Row(verticalAlignment = Alignment.CenterVertically) {
// Read status
Icon(
TablerIcons.Checks,
painter = TelegramIcons.Done,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(16.dp)
@@ -2028,7 +2104,7 @@ fun ChatItem(
// Pin icon
if (chat.isPinned) {
Icon(
TablerIcons.Pin,
painter = TelegramIcons.Pin,
contentDescription = "Pinned",
tint =
secondaryTextColor.copy(
@@ -2262,9 +2338,9 @@ fun SwipeableDialogItem(
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector =
if (isPinned) TablerIcons.PinnedOff
else TablerIcons.Pin,
painter =
if (isPinned) TelegramIcons.Unpin
else TelegramIcons.Pin,
contentDescription =
if (isPinned) "Unpin" else "Pin",
tint = Color.White,
@@ -2303,9 +2379,9 @@ fun SwipeableDialogItem(
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector =
if (isBlocked) TablerIcons.LockOpen
else TablerIcons.Ban,
painter =
if (isBlocked) TelegramIcons.Unlock
else TelegramIcons.Block,
contentDescription =
if (isBlocked) "Unblock"
else "Block",
@@ -2343,7 +2419,7 @@ fun SwipeableDialogItem(
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = TablerIcons.Trash,
painter = TelegramIcons.Delete,
contentDescription = "Delete",
tint = Color.White,
modifier = Modifier.size(22.dp)
@@ -2742,7 +2818,7 @@ fun DialogItemContent(
if (isBlocked) {
Spacer(modifier = Modifier.width(4.dp))
Icon(
imageVector = TablerIcons.Lock,
painter = TelegramIcons.Secret,
contentDescription = "Blocked",
tint = Color(0xFFFF3B30),
modifier = Modifier.size(14.dp)
@@ -2751,7 +2827,7 @@ fun DialogItemContent(
if (isMuted) {
Spacer(modifier = Modifier.width(4.dp))
Icon(
imageVector = TablerIcons.BellOff,
painter = TelegramIcons.Mute,
contentDescription = "Muted",
tint = secondaryTextColor,
modifier = Modifier.size(14.dp)
@@ -2767,7 +2843,7 @@ fun DialogItemContent(
// галочки (прочитано)
if (dialog.isSavedMessages) {
Icon(
imageVector = TablerIcons.Checks,
painter = TelegramIcons.Done,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(16.dp)
@@ -2810,8 +2886,8 @@ fun DialogItemContent(
// READ (delivered=3) - две синие
// галочки
Icon(
imageVector =
TablerIcons.Checks,
painter =
TelegramIcons.Done,
contentDescription = null,
tint = PrimaryBlue,
modifier =
@@ -2825,8 +2901,8 @@ fun DialogItemContent(
1 -> {
// DELIVERED - одна серая галочка
Icon(
imageVector =
TablerIcons.Check,
painter =
TelegramIcons.Done,
contentDescription = null,
tint =
secondaryTextColor
@@ -2845,8 +2921,8 @@ fun DialogItemContent(
else -> {
// SENDING (0) - часики
Icon(
imageVector =
TablerIcons.Clock,
painter =
TelegramIcons.Clock,
contentDescription =
"Sending",
tint =
@@ -2988,7 +3064,7 @@ fun DialogItemContent(
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.width(6.dp))
Icon(
imageVector = TablerIcons.Pin,
painter = TelegramIcons.Pin,
contentDescription = "Pinned",
tint = secondaryTextColor.copy(alpha = 0.5f),
modifier = Modifier.size(16.dp)
@@ -3057,12 +3133,20 @@ fun RequestsSection(
isDarkTheme: Boolean,
onClick: () -> Unit
) {
val textColor = remember(isDarkTheme) { if (isDarkTheme) Color.White else Color.Black }
val secondaryTextColor =
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666) }
// Telegram archived chats uses muted colors:
// Title: #525252 (light) vs regular #222222
// 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 =
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFC7C7CC) }
val accentColor = if (isDarkTheme) PrimaryBlueDark else PrimaryBlue
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF3A3A3C) else Color(0xFFB8C2CC) }
val badgeColor =
remember(isDarkTheme) { if (isDarkTheme) Color(0xFF4E4E4E) else Color(0xFFC6C9CC) }
// Последний запрос — показываем имя отправителя как subtitle
val lastRequest = remember(requests) { requests.firstOrNull() }
@@ -3096,7 +3180,7 @@ fun RequestsSection(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.MailForward,
painter = painterResource(id = R.drawable.archive_filled),
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(26.dp)
@@ -3116,7 +3200,7 @@ fun RequestsSection(
text = "Requests",
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp,
color = textColor,
color = titleColor,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
@@ -3130,7 +3214,7 @@ fun RequestsSection(
Modifier
.defaultMinSize(minWidth = 22.dp, minHeight = 22.dp)
.clip(CircleShape)
.background(accentColor),
.background(badgeColor),
contentAlignment = Alignment.Center
) {
Text(
@@ -3151,20 +3235,13 @@ fun RequestsSection(
Text(
text = subtitle,
fontSize = 14.sp,
color = secondaryTextColor,
color = subtitleColor,
maxLines = 1,
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)
.clip(CircleShape)
.background(
if (isSelected) Color(0xFF4CD964)
if (isSelected) PrimaryBlue
else if (isDarkTheme) Color(0xFF3A3A3A)
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.repository.AvatarRepository
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons
import compose.icons.tablericons.ChevronLeft
import compose.icons.tablericons.*
import kotlinx.coroutines.launch
@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.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons
import compose.icons.tablericons.ArrowLeft
import compose.icons.tablericons.X
import compose.icons.tablericons.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Verified
import androidx.compose.material3.*
@@ -61,6 +61,14 @@ fun SearchScreen(
val context = LocalContext.current
val view = LocalView.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 = {
@@ -173,7 +181,7 @@ fun SearchScreen(
}
) {
Icon(
TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = textColor.copy(alpha = 0.6f)
)
@@ -233,7 +241,7 @@ fun SearchScreen(
) {
IconButton(onClick = { searchViewModel.clearSearchQuery() }) {
Icon(
TablerIcons.X,
painter = TelegramIcons.Close,
contentDescription = "Clear",
tint = secondaryTextColor.copy(alpha = 0.6f)
)
@@ -424,7 +432,7 @@ private fun RecentUserItem(
// Remove button
IconButton(onClick = onRemove, modifier = Modifier.size(40.dp)) {
Icon(
TablerIcons.X,
painter = TelegramIcons.Close,
contentDescription = "Remove",
tint = secondaryTextColor.copy(alpha = 0.6f),
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.AvatarFileManager
import com.vanniktech.blurhash.BlurHash
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
@@ -1174,7 +1173,7 @@ fun ImageAttachment(
when (messageStatus) {
MessageStatus.SENDING -> {
Icon(
compose.icons.TablerIcons.Clock,
painter = TelegramIcons.Clock,
contentDescription = null,
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp)
@@ -1182,7 +1181,7 @@ fun ImageAttachment(
}
MessageStatus.SENT -> {
Icon(
compose.icons.TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = null,
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp)
@@ -1190,7 +1189,7 @@ fun ImageAttachment(
}
MessageStatus.DELIVERED -> {
Icon(
compose.icons.TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = null,
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp)
@@ -1198,7 +1197,7 @@ fun ImageAttachment(
}
MessageStatus.READ -> {
Icon(
compose.icons.TablerIcons.Checks,
painter = TelegramIcons.Done,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(14.dp)
@@ -1546,7 +1545,7 @@ fun FileAttachment(
when (messageStatus) {
MessageStatus.SENDING -> {
Icon(
compose.icons.TablerIcons.Clock,
painter = TelegramIcons.Clock,
contentDescription = null,
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp)
@@ -1554,7 +1553,7 @@ fun FileAttachment(
}
MessageStatus.SENT -> {
Icon(
compose.icons.TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = null,
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier.size(14.dp)
@@ -1562,7 +1561,7 @@ fun FileAttachment(
}
MessageStatus.DELIVERED, MessageStatus.READ -> {
Icon(
compose.icons.TablerIcons.Checks,
painter = TelegramIcons.Done,
contentDescription = null,
tint =
if (messageStatus == MessageStatus.READ) {
@@ -1934,7 +1933,7 @@ fun AvatarAttachment(
when (messageStatus) {
MessageStatus.SENDING -> {
Icon(
compose.icons.TablerIcons.Clock,
painter = TelegramIcons.Clock,
contentDescription = "Sending",
tint = Color.White.copy(alpha = 0.6f),
modifier = Modifier.size(14.dp)
@@ -1942,7 +1941,7 @@ fun AvatarAttachment(
}
MessageStatus.SENT -> {
Icon(
compose.icons.TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = "Sent",
tint = Color.White.copy(alpha = 0.6f),
modifier = Modifier.size(14.dp)
@@ -1950,7 +1949,7 @@ fun AvatarAttachment(
}
MessageStatus.DELIVERED -> {
Icon(
compose.icons.TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = "Delivered",
tint = Color.White.copy(alpha = 0.6f),
modifier = Modifier.size(14.dp)
@@ -1958,7 +1957,7 @@ fun AvatarAttachment(
}
MessageStatus.READ -> {
Icon(
compose.icons.TablerIcons.Checks,
painter = TelegramIcons.Done,
contentDescription = "Read",
tint = Color.White,
modifier = Modifier.size(14.dp)

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -272,7 +271,7 @@ fun MessageInputBar(
horizontalArrangement = Arrangement.Center
) {
Icon(
TablerIcons.Ban,
painter = TelegramIcons.Block,
contentDescription = null,
tint = Color(0xFFFF6B6B),
modifier = Modifier.size(20.dp)
@@ -419,7 +418,7 @@ fun MessageInputBar(
contentAlignment = Alignment.Center
) {
Icon(
TablerIcons.X,
painter = TelegramIcons.Close,
contentDescription = "Cancel",
tint = if (isDarkTheme) Color.White.copy(alpha = 0.5f)
else Color.Black.copy(alpha = 0.4f),
@@ -442,7 +441,7 @@ fun MessageInputBar(
modifier = Modifier.size(40.dp)
) {
Icon(
TablerIcons.Paperclip,
painter = TelegramIcons.Attach,
contentDescription = "Attach",
tint = if (isDarkTheme) Color(0xFF8E8E93).copy(alpha = 0.6f)
else Color(0xFF8E8E93).copy(alpha = 0.6f),
@@ -485,8 +484,8 @@ fun MessageInputBar(
modifier = Modifier.size(40.dp)
) {
Icon(
if (showEmojiPicker) TablerIcons.Keyboard
else TablerIcons.MoodSmile,
painter = if (showEmojiPicker) TelegramIcons.Keyboard
else TelegramIcons.Smile,
contentDescription = "Emoji",
tint = if (isDarkTheme) 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 compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
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.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -55,6 +57,16 @@ fun AppearanceScreen(
accountName: String = "",
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 textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
@@ -101,7 +113,7 @@ fun AppearanceScreen(
) {
IconButton(onClick = onBack) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = Color.White
)
@@ -441,7 +453,7 @@ private fun ColorCircleItem(
val borderColor by animateColorAsState(
targetValue = if (isSelected) {
Color.White
if (isDarkTheme) Color.White else Color(0xFF222222)
} else {
Color.Transparent
},
@@ -456,7 +468,7 @@ private fun ColorCircleItem(
.clip(CircleShape)
.border(
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
)
.clickable(onClick = onClick),
@@ -509,7 +521,7 @@ private fun ColorCircleItem(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = "Selected",
tint = Color.White,
modifier = Modifier.size(18.dp)

View File

@@ -30,6 +30,7 @@ import com.airbnb.lottie.compose.*
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import kotlinx.coroutines.delay
// Auth colors
@@ -82,6 +83,14 @@ fun BiometricEnableScreen(
val focusManager = LocalFocusManager.current
val context = LocalContext.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
fun hideKeyboard() {
@@ -141,7 +150,7 @@ fun BiometricEnableScreen(
) {
IconButton(onClick = onBack, enabled = !isLoading) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = textColor.copy(alpha = 0.6f)
)
@@ -312,7 +321,7 @@ fun BiometricEnableScreen(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = TablerIcons.AlertCircle,
painter = TelegramIcons.Warning,
contentDescription = null,
tint = errorRed,
modifier = Modifier.size(16.dp)
@@ -377,7 +386,7 @@ fun BiometricEnableScreen(
)
} else {
Icon(
imageVector = TablerIcons.Fingerprint,
painter = TelegramIcons.Fingerprint,
contentDescription = null,
modifier = Modifier.size(22.dp)
)
@@ -408,7 +417,7 @@ fun BiometricEnableScreen(
verticalAlignment = Alignment.Top
) {
Icon(
imageVector = TablerIcons.ShieldLock,
painter = TelegramIcons.Secret,
contentDescription = null,
tint = PrimaryBlue,
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.RoundedCornerShape
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.material3.*
import androidx.compose.runtime.*
@@ -43,6 +41,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
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.style.TextAlign
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.unit.Velocity
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.utils.AttachmentFileManager
import com.rosetta.messenger.utils.AvatarFileManager
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import kotlinx.coroutines.CoroutineScope
@@ -220,6 +221,10 @@ fun OtherProfileScreen(
val chatsListViewModel: ChatsListViewModel = viewModel()
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) }
@@ -244,6 +249,14 @@ fun OtherProfileScreen(
remember(currentUserPublicKey) { currentUserPublicKey.trim() }
val activeAccountPrivateKey =
remember(currentUserPrivateKey) { currentUserPrivateKey.trim() }
// 🔕 Load mute state from preferences
LaunchedEffect(activeAccountPublicKey, user.publicKey) {
if (activeAccountPublicKey.isNotBlank()) {
notificationsEnabled = !preferencesManager.isChatMuted(activeAccountPublicKey, user.publicKey)
}
}
val dialogKey =
remember(activeAccountPublicKey, user.publicKey) {
if (activeAccountPublicKey.isBlank()) {
@@ -522,14 +535,14 @@ fun OtherProfileScreen(
)
) {
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()) {
TelegramCopyField(
@@ -538,6 +551,12 @@ fun OtherProfileScreen(
label = "Username",
isDarkTheme = isDarkTheme
)
Divider(
color = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE0E0E0),
thickness = 0.5.dp,
modifier = Modifier.padding(start = 16.dp)
)
}
TelegramCopyField(
@@ -547,11 +566,21 @@ fun OtherProfileScreen(
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
// ═══════════════════════════════════════════════════════════
Spacer(modifier = Modifier.height(12.dp))
Box(
modifier = Modifier
.fillMaxWidth()
@@ -573,7 +602,7 @@ fun OtherProfileScreen(
)
) {
Icon(
TablerIcons.MessageCircle2,
painter = TelegramIcons.Message,
contentDescription = null,
modifier = Modifier.size(20.dp),
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(
selectedTab = selectedTab,
onTabSelected = { tab ->
@@ -1202,7 +1250,7 @@ private fun OtherProfileActionButtons(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OtherProfileActionButton(
icon = TablerIcons.MessageCircle2,
icon = TelegramIcons.Message,
title = "Message",
subtitle = "Open chat",
accent = PrimaryBlue,
@@ -1211,7 +1259,7 @@ private fun OtherProfileActionButtons(
onClick = onMessageClick
)
OtherProfileActionButton(
icon = if (isMuted) TablerIcons.BellOff else TablerIcons.BellRinging2,
icon = if (isMuted) TelegramIcons.Mute else TelegramIcons.Notifications,
title = if (isMuted) "Muted" else "Mute",
subtitle = if (isMuted) "Tap to unmute" else "Silence notifications",
accent =
@@ -1229,7 +1277,7 @@ private fun OtherProfileActionButtons(
@Composable
private fun OtherProfileActionButton(
icon: ImageVector,
icon: Painter,
title: String,
subtitle: String,
accent: Color,
@@ -1260,7 +1308,7 @@ private fun OtherProfileActionButton(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = icon,
painter = icon,
contentDescription = title,
tint = accent,
modifier = Modifier.size(19.dp)
@@ -1504,7 +1552,7 @@ private fun OtherProfileFileList(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.File,
painter = TelegramIcons.File,
contentDescription = null,
tint = PrimaryBlue,
modifier = Modifier.size(18.dp)
@@ -1661,8 +1709,11 @@ private fun CollapsingOtherProfileHeader(
val avatarFontSize = androidx.compose.ui.unit.lerp(40.sp, 12.sp, collapseProgress)
// Text animation - always centered
val textDefaultY = expandedHeight - 48.dp
val textCollapsedY = statusBarHeight + COLLAPSED_HEADER_HEIGHT_OTHER / 2
val textDefaultY = expandedHeight - 70.dp
// 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 nameFontSize = androidx.compose.ui.unit.lerp(24.sp, 18.sp, collapseProgress)
@@ -1673,7 +1724,19 @@ private fun CollapsingOtherProfileHeader(
// ═══════════════════════════════════════════════════════════
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()) {
// 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
val expandFractionEarly = expansionProgress.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
// При скролле вверх: 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(
modifier =
Modifier.padding(top = statusBarHeight)
.padding(start = 4.dp, top = 4.dp)
Modifier
.align(Alignment.TopStart)
.offset(x = 4.dp, y = buttonsY)
.size(48.dp),
contentAlignment = Alignment.Center
) {
IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
Icon(
imageVector = Icons.Filled.ArrowBack,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = Color.White,
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(
modifier =
Modifier.align(Alignment.TopEnd)
.padding(top = statusBarHeight)
.padding(end = 4.dp, top = 4.dp)
.size(48.dp),
.offset(x = -4.dp, y = buttonsY),
contentAlignment = Alignment.Center
) {
IconButton(onClick = { onAvatarMenuChange(true) }, modifier = Modifier.size(48.dp)) {
Icon(
imageVector = Icons.Default.MoreVert,
painter = TelegramIcons.More,
contentDescription = "Profile menu",
tint = Color.White,
modifier = Modifier.size(24.dp)
@@ -1857,17 +1977,7 @@ private fun CollapsingOtherProfileHeader(
// ═══════════════════════════════════════════════════════════
Column(
modifier =
Modifier.align(Alignment.TopCenter).offset(y = textY).graphicsLayer {
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
},
Modifier.align(Alignment.TopCenter).offset(y = textY),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 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.rememberLazyListState
import androidx.compose.material3.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.FontWeight
import androidx.compose.ui.unit.dp
@@ -25,6 +27,16 @@ fun ProfileLogsScreen(
onBack: () -> 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 surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -63,7 +75,7 @@ fun ProfileLogsScreen(
) {
IconButton(onClick = onBack) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = textColor
)
@@ -79,7 +91,7 @@ fun ProfileLogsScreen(
)
IconButton(onClick = onClearLogs) {
Icon(
imageVector = TablerIcons.Trash,
painter = TelegramIcons.Delete,
contentDescription = "Clear logs",
tint = secondaryTextColor
)

View File

@@ -43,8 +43,7 @@ import androidx.compose.ui.window.PopupProperties
import androidx.core.content.ContextCompat
import coil.compose.AsyncImage
import coil.request.ImageRequest
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -292,7 +291,7 @@ fun ProfilePhotoPicker(
modifier = Modifier.align(Alignment.CenterStart)
) {
Icon(
imageVector = TablerIcons.X,
painter = TelegramIcons.Close,
contentDescription = "Close",
tint = textColor,
modifier = Modifier.size(24.dp)
@@ -350,7 +349,7 @@ fun ProfilePhotoPicker(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = TablerIcons.Photo,
painter = TelegramIcons.Photos,
contentDescription = null,
tint = textColor.copy(alpha = 0.4f),
modifier = Modifier.size(64.dp)
@@ -501,7 +500,7 @@ private fun PermissionRequest(
modifier = Modifier.padding(32.dp)
) {
Icon(
imageVector = TablerIcons.Photo,
painter = TelegramIcons.Photos,
contentDescription = null,
tint = textColor.copy(alpha = 0.5f),
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.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -74,6 +75,11 @@ import java.util.Locale
import com.rosetta.messenger.utils.ImageCropHelper
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.delay
import kotlinx.coroutines.flow.first
@@ -278,6 +284,16 @@ fun ProfileScreen(
dialogDao: com.rosetta.messenger.database.DialogDao? = null,
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 activity = context as? FragmentActivity
val biometricManager = remember { BiometricAuthManager(context) }
@@ -817,7 +833,7 @@ fun ProfileScreen(
val scope = rememberCoroutineScope()
TelegramToggleItem(
icon = TablerIcons.Bell,
icon = TelegramIcons.Notifications,
title = "Push Notifications",
isEnabled = notificationsEnabled,
onToggle = {
@@ -837,7 +853,7 @@ fun ProfileScreen(
TelegramSectionTitle(title = "Settings", isDarkTheme = isDarkTheme)
TelegramSettingsItem(
icon = TablerIcons.Palette,
icon = TelegramIcons.Theme,
title = "Theme",
onClick = onNavigateToTheme,
isDarkTheme = isDarkTheme,
@@ -845,7 +861,7 @@ fun ProfileScreen(
)
TelegramSettingsItem(
icon = TablerIcons.Brush,
icon = TelegramIcons.Customize,
title = "Appearance",
onClick = onNavigateToAppearance,
isDarkTheme = isDarkTheme,
@@ -853,7 +869,7 @@ fun ProfileScreen(
)
TelegramSettingsItem(
icon = TablerIcons.Lock,
icon = TelegramIcons.Secret,
title = "Safety",
onClick = onNavigateToSafety,
isDarkTheme = isDarkTheme,
@@ -863,7 +879,7 @@ fun ProfileScreen(
// Biometric settings (only show if available)
if (biometricAvailable is BiometricAvailability.Available && activity != null) {
TelegramSettingsItem(
icon = TablerIcons.Fingerprint,
icon = TelegramIcons.Fingerprint,
title = "Biometric Authentication",
onClick = {
onNavigateToBiometric()
@@ -972,10 +988,9 @@ fun ProfileScreen(
// Positioned at bottom-right of header, half overlapping content area
// Fades out when collapsed or when avatar is expanded
// ═══════════════════════════════════════════════════════════
val cameraButtonAlpha = (1f - collapseProgress * 2.5f).coerceIn(0f, 1f) *
(1f - expansionProgress * 4f).coerceIn(0f, 1f)
val cameraButtonSize = 60.dp
val cameraButtonAlpha = (1f - collapseProgress * 2f).coerceIn(0f, 1f)
if (cameraButtonAlpha > 0.01f) {
val cameraButtonSize = 52.dp
Box(
modifier = Modifier
.align(Alignment.TopEnd)
@@ -991,15 +1006,15 @@ fun ProfileScreen(
clip = false
)
.clip(CircleShape)
.background(Color.White)
.background(if (isDarkTheme) Color.White else PrimaryBlue)
.clickable { showPhotoPicker = true },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.CameraPlus,
painter = TelegramIcons.AddPhoto,
contentDescription = "Change avatar",
tint = Color(0xFF8E8E93),
modifier = Modifier.size(24.dp)
tint = if (isDarkTheme) Color(0xFF8E8E93) else Color.White,
modifier = Modifier.size(26.dp).offset(x = 2.dp)
)
}
}
@@ -1089,7 +1104,10 @@ private fun CollapsingProfileHeader(
// 📝 TEXT - внизу header зоны, внутри блока
// ═══════════════════════════════════════════════════════════
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
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(
modifier =
Modifier
.align(Alignment.TopStart)
.offset(x = 4.dp, y = textY)
.graphicsLayer {
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
}
.offset(x = 4.dp, y = buttonsY)
.size(48.dp),
contentAlignment = Alignment.Center
) {
IconButton(onClick = onBack, modifier = Modifier.size(48.dp)) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = Color.White,
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(
modifier =
Modifier.align(Alignment.TopEnd)
.offset(x = -4.dp, y = textY)
.graphicsLayer {
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
},
.offset(x = -4.dp, y = buttonsY),
contentAlignment = Alignment.Center
) {
AnimatedVisibility(visible = hasChanges, enter = fadeIn(), exit = fadeOut()) {
@@ -1283,7 +1319,7 @@ private fun CollapsingProfileHeader(
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = TablerIcons.DotsVertical,
painter = TelegramIcons.More,
contentDescription = "Profile menu",
tint = Color.White,
modifier = Modifier.size(24.dp)
@@ -1312,17 +1348,7 @@ private fun CollapsingProfileHeader(
// ═══════════════════════════════════════════════════════════
Column(
modifier =
Modifier.align(Alignment.TopCenter).offset(y = textY).graphicsLayer {
val centerOffsetY =
with(density) {
androidx.compose
.ui
.unit
.lerp(24.dp, 18.dp, collapseProgress)
.toPx()
}
translationY = -centerOffsetY
},
Modifier.align(Alignment.TopCenter).offset(y = textY),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
@@ -1445,7 +1471,7 @@ fun ProfileCard(
modifier = Modifier.align(Alignment.TopStart).statusBarsPadding().padding(4.dp)
) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = if (isDarkTheme) Color.White else Color.Black
)
@@ -1690,7 +1716,7 @@ fun TelegramCopyField(value: String, fullValue: String, label: String, isDarkThe
@Composable
private fun TelegramSettingsItem(
icon: ImageVector,
icon: Painter,
title: String,
onClick: () -> Unit,
isDarkTheme: Boolean,
@@ -1712,7 +1738,7 @@ private fun TelegramSettingsItem(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
painter = icon,
contentDescription = null,
tint = iconColor,
modifier = Modifier.size(24.dp)
@@ -1740,8 +1766,8 @@ private fun TelegramSettingsItem(
}
@Composable
private fun TelegramToggleItem(
icon: ImageVector,
fun TelegramToggleItem(
icon: Painter,
title: String,
subtitle: String? = null,
isEnabled: Boolean,
@@ -1764,7 +1790,7 @@ private fun TelegramToggleItem(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
painter = icon,
contentDescription = null,
tint = iconColor,
modifier = Modifier.size(24.dp)
@@ -1852,7 +1878,7 @@ private fun TelegramToggleItem(
@Composable
private fun TelegramBiometricItem(isEnabled: Boolean, onToggle: () -> Unit, isDarkTheme: Boolean) {
TelegramToggleItem(
icon = TablerIcons.Fingerprint,
icon = TelegramIcons.Fingerprint,
title = "Biometric Authentication",
isEnabled = isEnabled,
onToggle = onToggle,
@@ -1872,7 +1898,7 @@ private fun TelegramLogoutItem(onClick: () -> Unit, isDarkTheme: Boolean) {
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = TablerIcons.Logout,
painter = TelegramIcons.Leave,
contentDescription = null,
tint = redColor,
modifier = Modifier.size(24.dp)
@@ -1903,7 +1929,7 @@ private fun ProfileSectionTitle(title: String, isDarkTheme: Boolean) {
@Composable
fun ProfileNavigationItem(
icon: ImageVector,
icon: Painter,
iconBackground: Color,
title: String,
subtitle: String,
@@ -1934,7 +1960,7 @@ fun ProfileNavigationItem(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = icon,
painter = icon,
contentDescription = null,
tint = Color.White,
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
Row(
modifier = Modifier
@@ -2130,7 +2172,7 @@ fun FullScreenAvatarViewer(
) {
IconButton(onClick = { showContent = false }) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Close",
tint = Color.White,
modifier = Modifier.size(24.dp)
@@ -2158,7 +2200,7 @@ fun FullScreenAvatarViewer(
IconButton(onClick = { /* menu */ }) {
Icon(
imageVector = TablerIcons.DotsVertical,
painter = TelegramIcons.More,
contentDescription = "Menu",
tint = Color.White,
modifier = Modifier.size(24.dp)

View File

@@ -14,6 +14,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@@ -21,6 +22,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -33,6 +35,16 @@ fun ThemeScreen(
onBack: () -> 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 surfaceColor = if (isDarkTheme) Color(0xFF2C2C2E) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black
@@ -63,7 +75,7 @@ fun ThemeScreen(
) {
IconButton(onClick = onBack) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
tint = textColor
)
@@ -209,7 +221,7 @@ private fun TelegramThemeOption(
// Radio button
if (isSelected) {
Icon(
imageVector = TablerIcons.Check,
painter = TelegramIcons.Done,
contentDescription = null,
tint = Color(0xFF007AFF),
modifier = Modifier.size(20.dp)
@@ -358,7 +370,7 @@ private fun MessageBubble(
if (isMe) {
// Read checkmarks (DoneAll icon like in real chat)
Icon(
imageVector = TablerIcons.Checks,
painter = TelegramIcons.Done,
contentDescription = null,
tint = Color(0xFF4FC3F7), // Blue checkmarks for read messages
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.verticalScroll
import androidx.compose.material3.*
import com.rosetta.messenger.ui.icons.TelegramIcons
import compose.icons.TablerIcons
import compose.icons.tablericons.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -24,6 +27,16 @@ fun UpdatesScreen(
isDarkTheme: Boolean,
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 buildNumber = remember { BuildConfig.VERSION_CODE.toString() }
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFFFFFFF)
@@ -52,7 +65,7 @@ fun UpdatesScreen(
) {
IconButton(onClick = onBack) {
Icon(
imageVector = TablerIcons.ArrowLeft,
imageVector = TablerIcons.ChevronLeft,
contentDescription = "Back",
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