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.
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
BIN
app/src/main/res/drawable-hdpi/fingerprint.png
Normal file
|
After Width: | Height: | Size: 827 B |
BIN
app/src/main/res/drawable-hdpi/ic_ab_done.png
Normal file
|
After Width: | Height: | Size: 244 B |
BIN
app/src/main/res/drawable-hdpi/ic_ab_other.png
Normal file
|
After Width: | Height: | Size: 149 B |
BIN
app/src/main/res/drawable-hdpi/ic_ab_reply.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
app/src/main/res/drawable-hdpi/ic_arrow_drop_down.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
app/src/main/res/drawable-hdpi/input_attach.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/drawable-hdpi/input_keyboard.png
Normal file
|
After Width: | Height: | Size: 385 B |
BIN
app/src/main/res/drawable-hdpi/input_smile.png
Normal file
|
After Width: | Height: | Size: 558 B |
BIN
app/src/main/res/drawable-hdpi/menu_unlock.png
Normal file
|
After Width: | Height: | Size: 753 B |
BIN
app/src/main/res/drawable-hdpi/msg_add.png
Normal file
|
After Width: | Height: | Size: 189 B |
BIN
app/src/main/res/drawable-hdpi/msg_addcontact.png
Normal file
|
After Width: | Height: | Size: 433 B |
BIN
app/src/main/res/drawable-hdpi/msg_addphoto.png
Normal file
|
After Width: | Height: | Size: 468 B |
BIN
app/src/main/res/drawable-hdpi/msg_arrow_back.png
Normal file
|
After Width: | Height: | Size: 257 B |
BIN
app/src/main/res/drawable-hdpi/msg_arrowright.png
Normal file
|
After Width: | Height: | Size: 193 B |
BIN
app/src/main/res/drawable-hdpi/msg_autodelete.png
Normal file
|
After Width: | Height: | Size: 461 B |
BIN
app/src/main/res/drawable-hdpi/msg_block.png
Normal file
|
After Width: | Height: | Size: 401 B |
BIN
app/src/main/res/drawable-hdpi/msg_camera.png
Normal file
|
After Width: | Height: | Size: 457 B |
BIN
app/src/main/res/drawable-hdpi/msg_close.png
Normal file
|
After Width: | Height: | Size: 268 B |
BIN
app/src/main/res/drawable-hdpi/msg_contact.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
app/src/main/res/drawable-hdpi/msg_customize.png
Normal file
|
After Width: | Height: | Size: 395 B |
BIN
app/src/main/res/drawable-hdpi/msg_delete.png
Normal file
|
After Width: | Height: | Size: 411 B |
BIN
app/src/main/res/drawable-hdpi/msg_edit.png
Normal file
|
After Width: | Height: | Size: 334 B |
BIN
app/src/main/res/drawable-hdpi/msg_info.png
Normal file
|
After Width: | Height: | Size: 447 B |
BIN
app/src/main/res/drawable-hdpi/msg_leave.png
Normal file
|
After Width: | Height: | Size: 340 B |
BIN
app/src/main/res/drawable-hdpi/msg_message.png
Normal file
|
After Width: | Height: | Size: 380 B |
BIN
app/src/main/res/drawable-hdpi/msg_mute.png
Normal file
|
After Width: | Height: | Size: 490 B |
BIN
app/src/main/res/drawable-hdpi/msg_notifications.png
Normal file
|
After Width: | Height: | Size: 348 B |
BIN
app/src/main/res/drawable-hdpi/msg_palette.png
Normal file
|
After Width: | Height: | Size: 460 B |
BIN
app/src/main/res/drawable-hdpi/msg_photos.png
Normal file
|
After Width: | Height: | Size: 370 B |
BIN
app/src/main/res/drawable-hdpi/msg_pin.png
Normal file
|
After Width: | Height: | Size: 391 B |
BIN
app/src/main/res/drawable-hdpi/msg_retry.png
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
app/src/main/res/drawable-hdpi/msg_secret.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
app/src/main/res/drawable-hdpi/msg_send.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
app/src/main/res/drawable-hdpi/msg_sendfile.png
Normal file
|
After Width: | Height: | Size: 339 B |
BIN
app/src/main/res/drawable-hdpi/msg_theme.png
Normal file
|
After Width: | Height: | Size: 513 B |
BIN
app/src/main/res/drawable-hdpi/msg_unpin.png
Normal file
|
After Width: | Height: | Size: 446 B |
BIN
app/src/main/res/drawable-hdpi/msg_views.png
Normal file
|
After Width: | Height: | Size: 267 B |
BIN
app/src/main/res/drawable-hdpi/msg_warning.png
Normal file
|
After Width: | Height: | Size: 104 B |
BIN
app/src/main/res/drawable-mdpi/fingerprint.png
Normal file
|
After Width: | Height: | Size: 532 B |
BIN
app/src/main/res/drawable-mdpi/ic_ab_done.png
Normal file
|
After Width: | Height: | Size: 182 B |
BIN
app/src/main/res/drawable-mdpi/ic_ab_other.png
Normal file
|
After Width: | Height: | Size: 120 B |
BIN
app/src/main/res/drawable-mdpi/ic_ab_reply.png
Normal file
|
After Width: | Height: | Size: 244 B |
BIN
app/src/main/res/drawable-mdpi/ic_arrow_drop_down.png
Normal file
|
After Width: | Height: | Size: 124 B |
BIN
app/src/main/res/drawable-mdpi/input_attach.png
Normal file
|
After Width: | Height: | Size: 320 B |
BIN
app/src/main/res/drawable-mdpi/input_keyboard.png
Normal file
|
After Width: | Height: | Size: 284 B |
BIN
app/src/main/res/drawable-mdpi/input_smile.png
Normal file
|
After Width: | Height: | Size: 403 B |
BIN
app/src/main/res/drawable-mdpi/menu_unlock.png
Normal file
|
After Width: | Height: | Size: 553 B |
BIN
app/src/main/res/drawable-mdpi/msg_add.png
Normal file
|
After Width: | Height: | Size: 122 B |
BIN
app/src/main/res/drawable-mdpi/msg_addcontact.png
Normal file
|
After Width: | Height: | Size: 330 B |
BIN
app/src/main/res/drawable-mdpi/msg_addphoto.png
Normal file
|
After Width: | Height: | Size: 331 B |
BIN
app/src/main/res/drawable-mdpi/msg_arrow_back.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
app/src/main/res/drawable-mdpi/msg_arrowright.png
Normal file
|
After Width: | Height: | Size: 142 B |
BIN
app/src/main/res/drawable-mdpi/msg_autodelete.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
app/src/main/res/drawable-mdpi/msg_block.png
Normal file
|
After Width: | Height: | Size: 275 B |
BIN
app/src/main/res/drawable-mdpi/msg_camera.png
Normal file
|
After Width: | Height: | Size: 335 B |
BIN
app/src/main/res/drawable-mdpi/msg_close.png
Normal file
|
After Width: | Height: | Size: 155 B |
BIN
app/src/main/res/drawable-mdpi/msg_contact.png
Normal file
|
After Width: | Height: | Size: 236 B |
BIN
app/src/main/res/drawable-mdpi/msg_customize.png
Normal file
|
After Width: | Height: | Size: 280 B |
BIN
app/src/main/res/drawable-mdpi/msg_delete.png
Normal file
|
After Width: | Height: | Size: 231 B |
BIN
app/src/main/res/drawable-mdpi/msg_edit.png
Normal file
|
After Width: | Height: | Size: 239 B |
BIN
app/src/main/res/drawable-mdpi/msg_info.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
app/src/main/res/drawable-mdpi/msg_leave.png
Normal file
|
After Width: | Height: | Size: 259 B |
BIN
app/src/main/res/drawable-mdpi/msg_message.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
app/src/main/res/drawable-mdpi/msg_mute.png
Normal file
|
After Width: | Height: | Size: 357 B |
BIN
app/src/main/res/drawable-mdpi/msg_notifications.png
Normal file
|
After Width: | Height: | Size: 266 B |
BIN
app/src/main/res/drawable-mdpi/msg_palette.png
Normal file
|
After Width: | Height: | Size: 348 B |
BIN
app/src/main/res/drawable-mdpi/msg_photos.png
Normal file
|
After Width: | Height: | Size: 236 B |
BIN
app/src/main/res/drawable-mdpi/msg_pin.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
app/src/main/res/drawable-mdpi/msg_retry.png
Normal file
|
After Width: | Height: | Size: 325 B |
BIN
app/src/main/res/drawable-mdpi/msg_secret.png
Normal file
|
After Width: | Height: | Size: 265 B |
BIN
app/src/main/res/drawable-mdpi/msg_send.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
app/src/main/res/drawable-mdpi/msg_sendfile.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
app/src/main/res/drawable-mdpi/msg_theme.png
Normal file
|
After Width: | Height: | Size: 364 B |
BIN
app/src/main/res/drawable-mdpi/msg_unpin.png
Normal file
|
After Width: | Height: | Size: 312 B |
BIN
app/src/main/res/drawable-mdpi/msg_views.png
Normal file
|
After Width: | Height: | Size: 175 B |
BIN
app/src/main/res/drawable-mdpi/msg_warning.png
Normal file
|
After Width: | Height: | Size: 73 B |
BIN
app/src/main/res/drawable-xhdpi/fingerprint.png
Normal file
|
After Width: | Height: | Size: 991 B |
BIN
app/src/main/res/drawable-xhdpi/ic_ab_done.png
Normal file
|
After Width: | Height: | Size: 216 B |