feat: Update ChatDetailScreen and ChatsListScreen for improved UI responsiveness and consistency; add custom verified badge icon

This commit is contained in:
2026-02-24 20:35:33 +05:00
parent 73ec5d77f4
commit 6643e0e069
17 changed files with 931 additions and 153 deletions

View File

@@ -58,6 +58,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -142,7 +143,7 @@ fun ChatDetailScreen(
val backgroundColor = if (isDarkTheme) Color(0xFF1A1A1A) else Color(0xFFF2F2F7)
val textColor = if (isDarkTheme) Color.White else Color.Black
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF8E8E93)
val headerIconColor = if (isDarkTheme) Color(0xFF0A84FF) else Color(0xFF007AFF)
val headerIconColor = Color.White
// 🔥 Keyboard & Emoji Coordinator
val coordinator = remember { KeyboardTransitionCoordinator() }
@@ -232,7 +233,7 @@ fun ChatDetailScreen(
if (window != null && view != null) {
val ic = androidx.core.view.WindowCompat.getInsetsController(window, view)
window.statusBarColor = android.graphics.Color.TRANSPARENT
ic.isAppearanceLightStatusBars = !isDarkTheme
ic.isAppearanceLightStatusBars = false
}
}
}
@@ -659,7 +660,7 @@ fun ChatDetailScreen(
// 🚀 Весь контент (swipe-back обрабатывается в SwipeBackContainer)
Box(modifier = Modifier.fillMaxSize()) {
// Telegram-style solid header background (без blur)
val headerBackground = if (isDarkTheme) Color(0xFF212121) else Color(0xFFFFFFFF)
val headerBackground = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFF228BE6)
Scaffold(
contentWindowInsets = WindowInsets(0.dp),
@@ -669,13 +670,7 @@ fun ChatDetailScreen(
Box(
modifier =
Modifier.fillMaxWidth()
.background(
if (isSelectionMode) {
if (isDarkTheme)
Color(0xFF212121)
else Color.White
} else headerBackground
)
.background(headerBackground)
// 🎨 statusBarsPadding ПОСЛЕ background =
// хедер начинается под статус баром
.statusBarsPadding()
@@ -737,12 +732,7 @@ fun ChatDetailScreen(
fontWeight =
FontWeight
.Bold,
color =
if (isDarkTheme
)
Color.White
else
Color.Black
color = Color.White
)
}
@@ -880,14 +870,7 @@ fun ChatDetailScreen(
imageVector = TablerIcons.ChevronLeft,
contentDescription =
"Back",
tint =
if (isDarkTheme
)
Color.White
else
Color(
0xFF007AFF
),
tint = Color.White,
modifier =
Modifier.size(
28.dp
@@ -1007,15 +990,14 @@ fun ChatDetailScreen(
CircleShape
)
.background(
PrimaryBlue
Color(0xFF228BE6)
),
contentAlignment =
Alignment
.Center
) {
Icon(
Icons.Default
.Bookmark,
painter = painterResource(R.drawable.bookmark_outlined),
contentDescription =
null,
tint =
@@ -1102,7 +1084,7 @@ fun ChatDetailScreen(
FontWeight
.SemiBold,
color =
textColor,
Color.White,
maxLines =
1,
overflow =
@@ -1125,7 +1107,8 @@ fun ChatDetailScreen(
size =
16,
isDarkTheme =
isDarkTheme
isDarkTheme,
badgeTint = if (isDarkTheme) null else Color(0xFFACD2F9)
)
}
// 🔇 Mute icon
@@ -1135,7 +1118,7 @@ fun ChatDetailScreen(
painter = TelegramIcons.Mute,
contentDescription = "Muted",
modifier = Modifier.size(16.dp),
tint = secondaryTextColor
tint = Color.White.copy(alpha = 0.7f)
)
}
}
@@ -1155,13 +1138,13 @@ fun ChatDetailScreen(
color =
when {
isSavedMessages ->
secondaryTextColor
Color.White.copy(alpha = 0.7f)
isOnline ->
Color(
0xFF38B24D
) // Зелёный когда онлайн
0xFF7FE084
) // Зелёный когда онлайн (светлый на синем фоне)
else ->
secondaryTextColor // Серый
Color.White.copy(alpha = 0.7f) // Белый полупрозрачный
// для
// offline
},
@@ -1182,14 +1165,7 @@ fun ChatDetailScreen(
.Call,
contentDescription =
"Call",
tint =
if (isDarkTheme
)
Color.White
else
Color(
0xFF007AFF
)
tint = Color.White
)
}
}
@@ -1221,14 +1197,7 @@ fun ChatDetailScreen(
.MoreVert,
contentDescription =
"More",
tint =
if (isDarkTheme
)
Color.White
else
Color(
0xFF007AFF
),
tint = Color.White,
modifier =
Modifier.size(
26.dp
@@ -1287,15 +1256,7 @@ fun ChatDetailScreen(
.fillMaxWidth()
.height(0.5.dp)
.background(
if (isDarkTheme)
Color.White.copy(
alpha =
0.15f
)
else
Color.Black.copy(
alpha = 0.1f
)
Color.White.copy(alpha = 0.15f)
)
)
} // Закрытие Box unified header

View File

@@ -73,6 +73,8 @@ import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.components.VerifiedBadge
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
import com.rosetta.messenger.ui.onboarding.PrimaryBlueDark
import com.rosetta.messenger.update.UpdateManager
import com.rosetta.messenger.update.UpdateState
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.graphics.painter.Painter
@@ -245,6 +247,10 @@ fun ChatsListScreen(
val focusManager = androidx.compose.ui.platform.LocalFocusManager.current
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
val sduUpdateState by UpdateManager.updateState.collectAsState()
val sduDownloadProgress by UpdateManager.downloadProgress.collectAsState()
val sduDebugLogs by UpdateManager.debugLogs.collectAsState()
var showSduLogs by remember { mutableStateOf(false) }
val themeRevealRadius = remember { Animatable(0f) }
var rootSize by remember { mutableStateOf(IntSize.Zero) }
var themeToggleCenterInRoot by remember { mutableStateOf<Offset?>(null) }
@@ -253,6 +259,73 @@ fun ChatsListScreen(
var themeRevealCenter by remember { mutableStateOf(Offset.Zero) }
var themeRevealSnapshot by remember { mutableStateOf<ImageBitmap?>(null) }
// ═══════════════ SDU Debug Log Dialog ═══════════════
if (showSduLogs) {
AlertDialog(
onDismissRequest = { showSduLogs = false },
title = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("SDU Logs", fontWeight = FontWeight.Bold, fontSize = 16.sp)
Spacer(modifier = Modifier.weight(1f))
Text(
"state: ${sduUpdateState::class.simpleName}",
fontSize = 11.sp,
color = Color.Gray
)
}
},
text = {
val scrollState = rememberScrollState()
LaunchedEffect(sduDebugLogs.size) {
scrollState.animateScrollTo(scrollState.maxValue)
}
Column(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 400.dp)
.verticalScroll(scrollState)
) {
if (sduDebugLogs.isEmpty()) {
Text(
"Нет логов. SDU ещё не инициализирован\nили пакет 0x0A не пришёл.",
fontSize = 13.sp,
color = Color.Gray
)
} else {
sduDebugLogs.forEach { line ->
Text(
text = line,
fontSize = 11.sp,
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
color = when {
"ERROR" in line || "EXCEPTION" in line -> Color(0xFFFF5555)
"WARNING" in line -> Color(0xFFFFAA33)
"State ->" in line -> Color(0xFF55BB55)
else -> if (isDarkTheme) Color(0xFFCCCCCC) else Color(0xFF333333)
},
modifier = Modifier.padding(vertical = 1.dp)
)
}
}
}
},
confirmButton = {
Row {
TextButton(onClick = {
// Retry: force re-request SDU
UpdateManager.requestSduServer()
}) {
Text("Retry SDU")
}
Spacer(modifier = Modifier.width(8.dp))
TextButton(onClick = { showSduLogs = false }) {
Text("Close")
}
}
}
)
}
fun startThemeReveal() {
if (themeRevealActive) {
return
@@ -801,7 +874,7 @@ fun ChatsListScreen(
VerifiedBadge(
verified = 1,
size = 15,
badgeTint = Color.White
badgeTint = Color(0xFFACD2F9)
)
}
}
@@ -1078,9 +1151,15 @@ fun ChatsListScreen(
}
// ═══════════════════════════════════════════════════════════
// FOOTER - Version
// FOOTER - Version + Update Banner
// ═══════════════════════════════════════════════════════════
Column(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.fillMaxWidth().navigationBarsPadding()) {
// Telegram-style update banner
val curUpdate = sduUpdateState
val showUpdateBanner = curUpdate is UpdateState.UpdateAvailable ||
curUpdate is UpdateState.Downloading ||
curUpdate is UpdateState.ReadyToInstall
Divider(
color =
if (isDarkTheme)
@@ -1089,29 +1168,137 @@ fun ChatsListScreen(
thickness = 0.5.dp
)
// Version info
Box(
modifier =
Modifier.fillMaxWidth()
.padding(
horizontal = 20.dp,
vertical = 12.dp
),
contentAlignment =
Alignment.CenterStart
) {
Text(
text = "Rosetta v${BuildConfig.VERSION_NAME}",
fontSize = 12.sp,
color =
if (isDarkTheme)
Color(0xFF666666)
else
Color(0xFF999999)
)
// Version info — прячем когда есть баннер обновления
if (!showUpdateBanner) {
Row(
modifier =
Modifier.fillMaxWidth()
.padding(
horizontal = 20.dp,
vertical = 12.dp
),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Rosetta v${BuildConfig.VERSION_NAME}",
fontSize = 12.sp,
color =
if (isDarkTheme)
Color(0xFF666666)
else
Color(0xFF999999)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "(статус: ${sduUpdateState::class.simpleName})",
fontSize = 10.sp,
color =
if (isDarkTheme)
Color(0xFF555555)
else
Color(0xFFAAAAAA)
)
Spacer(modifier = Modifier.weight(1f))
// Debug log button
Box(
modifier = Modifier
.size(28.dp)
.clip(CircleShape)
.background(
if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFEEEEEE)
)
.clickable { showSduLogs = true },
contentAlignment = Alignment.Center
) {
Icon(
imageVector = TablerIcons.Bug,
contentDescription = "SDU Logs",
modifier = Modifier.size(16.dp),
tint = if (isDarkTheme) Color(0xFF888888) else Color(0xFF999999)
)
}
}
Spacer(modifier = Modifier.height(16.dp))
}
Spacer(modifier = Modifier.height(16.dp))
if (showUpdateBanner) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.background(
Brush.horizontalGradient(
colors = listOf(
Color(0xFF69BF72),
Color(0xFF53B3AD)
)
)
)
.clickable {
when (curUpdate) {
is UpdateState.UpdateAvailable ->
UpdateManager.downloadAndInstall(context)
is UpdateState.Downloading ->
UpdateManager.cancelDownload()
is UpdateState.ReadyToInstall ->
UpdateManager.downloadAndInstall(context)
else -> {}
}
},
contentAlignment = Alignment.CenterStart
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = when (curUpdate) {
is UpdateState.Downloading -> TablerIcons.X
else -> TablerIcons.Download
},
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(22.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = when (curUpdate) {
is UpdateState.Downloading ->
"Downloading... ${curUpdate.progress}%"
is UpdateState.ReadyToInstall ->
"Install Update"
is UpdateState.UpdateAvailable ->
"Update Rosetta"
else -> ""
},
color = Color.White,
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f)
)
if (curUpdate is UpdateState.UpdateAvailable) {
Text(
text = curUpdate.version,
color = Color.White.copy(alpha = 0.8f),
fontSize = 13.sp,
fontWeight = FontWeight.Medium
)
}
if (curUpdate is UpdateState.Downloading) {
CircularProgressIndicator(
progress = curUpdate.progress / 100f,
modifier = Modifier.size(20.dp),
color = Color.White,
trackColor = Color.White.copy(alpha = 0.3f),
strokeWidth = 2.dp
)
}
}
}
Spacer(modifier = Modifier.height(12.dp))
}
}
}
}
@@ -1757,7 +1944,10 @@ fun ChatsListScreen(
state = chatListState,
modifier =
Modifier.fillMaxSize()
.nestedScroll(requestsNestedScroll)
.then(
if (requestsCount > 0) Modifier.nestedScroll(requestsNestedScroll)
else Modifier
)
.background(
listBackgroundColor
)
@@ -3384,7 +3574,7 @@ fun DialogItemContent(
modifier =
Modifier.fillMaxSize()
.clip(CircleShape)
.background(PrimaryBlue),
.background(Color(0xFF228BE6)),
contentAlignment = Alignment.Center
) {
Icon(

View File

@@ -347,7 +347,7 @@ private fun ForwardDialogItem(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(PrimaryBlue),
.background(Color(0xFF228BE6)),
contentAlignment = Alignment.Center
) {
Icon(

View File

@@ -18,11 +18,13 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.airbnb.lottie.compose.*
import com.airbnb.lottie.LottieComposition
import com.rosetta.messenger.network.SearchUser
import com.rosetta.messenger.R
import com.rosetta.messenger.ui.components.VerifiedBadge
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
@@ -182,12 +184,12 @@ private fun SearchResultItem(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(if (isOwnAccount) PrimaryBlue else avatarColors.backgroundColor),
.background(if (isOwnAccount) Color(0xFF228BE6) else avatarColors.backgroundColor),
contentAlignment = Alignment.Center
) {
if (isOwnAccount) {
Icon(
Icons.Default.Bookmark,
painter = painterResource(R.drawable.bookmark_outlined),
contentDescription = "Saved Messages",
tint = Color.White,
modifier = Modifier.size(20.dp)

View File

@@ -30,6 +30,7 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalView
import com.rosetta.messenger.repository.AvatarRepository
import com.rosetta.messenger.ui.components.AvatarImage
import com.rosetta.messenger.ui.components.VerifiedBadge
import com.rosetta.messenger.database.RosettaDatabase
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
@@ -438,11 +439,9 @@ private fun RecentUserItem(
)
if (user.verified != 0) {
Spacer(modifier = Modifier.width(4.dp))
Icon(
Icons.Default.Verified,
contentDescription = "Verified",
tint = PrimaryBlue,
modifier = Modifier.size(16.dp)
VerifiedBadge(
verified = user.verified,
size = 16
)
}
}