Add new drawable resources for messaging features across multiple resolutions

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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