files: рескин панели свойств/доступа под макет (табы-пилюли, шапка, звезда)
Правая панель (FilesDetailsSheet) по макету «Правая панель свойства/доступ»: - табы из текст+подчёркивание → две иконки-пилюли (Обсуждение 💬 / Доступ 👥), активная — зелёная заливка PrimaryLight; - шапка: крестик в правом верхнем углу, чип владельца (аватар-инициал+логин), дата/размер справа, звезда-избранное (Icons Star/StarBorder, wired на vm.toggleFavorite) вместо строки «Владелец … • …»; - кнопка отправки комментария → зелёный круг-градиент с белой стрелкой (было «→»). Иконки — Material (extended уже в модуле). Компилируется.
This commit is contained in:
@@ -21,9 +21,18 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||||
|
import androidx.compose.material.icons.filled.ChatBubbleOutline
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material.icons.filled.Group
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
import androidx.compose.material.icons.filled.StarBorder
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.CheckboxDefaults
|
import androidx.compose.material3.CheckboxDefaults
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
@@ -49,6 +58,7 @@ import androidx.compose.ui.graphics.SolidColor
|
|||||||
import androidx.compose.ui.text.PlatformTextStyle
|
import androidx.compose.ui.text.PlatformTextStyle
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.geometry.Rect
|
import androidx.compose.ui.geometry.Rect
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.boundsInWindow
|
import androidx.compose.ui.layout.boundsInWindow
|
||||||
@@ -998,10 +1008,10 @@ fun FilesDetailsSheet(
|
|||||||
loading: Boolean,
|
loading: Boolean,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onTabChange: (FileDetailsTab) -> Unit,
|
onTabChange: (FileDetailsTab) -> Unit,
|
||||||
|
onToggleFavorite: () -> Unit,
|
||||||
onWebOnlyAction: (String) -> Unit,
|
onWebOnlyAction: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (item == null) return
|
if (item == null) return
|
||||||
val base = serverUrl.trimEnd('/')
|
|
||||||
var commentDraft by remember(item.relativePath) { mutableStateOf("") }
|
var commentDraft by remember(item.relativePath) { mutableStateOf("") }
|
||||||
androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss) {
|
androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss) {
|
||||||
Surface(
|
Surface(
|
||||||
@@ -1016,9 +1026,26 @@ fun FilesDetailsSheet(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(20.dp),
|
.padding(20.dp),
|
||||||
) {
|
) {
|
||||||
|
// Крестик закрытия — правый верхний угол (по макету)
|
||||||
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(28.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onDismiss),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Close,
|
||||||
|
contentDescription = "Закрыть",
|
||||||
|
tint = F7Colors.TextSecondary,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
verticalAlignment = Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
@@ -1029,52 +1056,86 @@ fun FilesDetailsSheet(
|
|||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(6.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
val metaParts = buildList {
|
// Чип владельца (аватар-инициал + логин) — по макету
|
||||||
item.size?.let { add(formatFileSize(it)) }
|
Row(
|
||||||
item.lastModified?.let {
|
modifier = Modifier
|
||||||
add(
|
.clip(RoundedCornerShape(100.dp))
|
||||||
SimpleDateFormat("d MMMM", Locale("ru")).format(Date(it)),
|
.background(F7Colors.SurfaceMuted)
|
||||||
|
.padding(start = 4.dp, end = 10.dp, top = 4.dp, bottom = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(F7Colors.PrimaryLight),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
ownerLabel.firstOrNull()?.uppercaseChar()?.toString() ?: "?",
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = F7Colors.Primary,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
add("Владелец $ownerLabel")
|
|
||||||
}
|
|
||||||
Text(
|
Text(
|
||||||
metaParts.joinToString(" • "),
|
ownerLabel,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = F7Colors.TextSecondary,
|
color = F7Colors.TextSecondary,
|
||||||
maxLines = 2,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TextButton(onClick = onDismiss) {
|
}
|
||||||
Text("✕", style = MaterialTheme.typography.titleMedium)
|
Column(horizontalAlignment = Alignment.End) {
|
||||||
|
val dateSize = buildList {
|
||||||
|
item.lastModified?.let {
|
||||||
|
add(SimpleDateFormat("d MMMM", Locale("ru")).format(Date(it)))
|
||||||
|
}
|
||||||
|
item.size?.let { add(formatFileSize(it)) }
|
||||||
|
}
|
||||||
|
if (dateSize.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
dateSize.joinToString(" / "),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = F7Colors.TextSecondary,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
if (item.favorite) Icons.Filled.Star else Icons.Filled.StarBorder,
|
||||||
|
contentDescription = "Избранное",
|
||||||
|
tint = if (item.favorite) F7Colors.Primary else F7Colors.TextSecondary,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(onClick = onToggleFavorite),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(16.dp))
|
Spacer(Modifier.height(16.dp))
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
) {
|
) {
|
||||||
FilesDetailsTabButton(
|
FilesDetailsTabPill(
|
||||||
serverUrl = serverUrl,
|
icon = Icons.Filled.ChatBubbleOutline,
|
||||||
label = "Общий доступ",
|
contentDescription = "Обсуждение",
|
||||||
iconPath = "files/many-person-gray.svg",
|
|
||||||
selected = tab == FileDetailsTab.Sharing,
|
|
||||||
onClick = { onTabChange(FileDetailsTab.Sharing) },
|
|
||||||
)
|
|
||||||
FilesDetailsTabButton(
|
|
||||||
serverUrl = serverUrl,
|
|
||||||
label = "События",
|
|
||||||
iconPath = "files/grid-files-gray.svg",
|
|
||||||
selected = tab == FileDetailsTab.Events,
|
selected = tab == FileDetailsTab.Events,
|
||||||
onClick = { onTabChange(FileDetailsTab.Events) },
|
onClick = { onTabChange(FileDetailsTab.Events) },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
FilesDetailsTabPill(
|
||||||
|
icon = Icons.Filled.Group,
|
||||||
|
contentDescription = "Общий доступ",
|
||||||
|
selected = tab == FileDetailsTab.Sharing,
|
||||||
|
onClick = { onTabChange(FileDetailsTab.Sharing) },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
HorizontalDivider(
|
Spacer(Modifier.height(16.dp))
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
|
||||||
color = F7Colors.Border.copy(alpha = 0.5f),
|
|
||||||
)
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
@@ -1117,42 +1178,28 @@ fun FilesDetailsSheet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FilesDetailsTabButton(
|
private fun FilesDetailsTabPill(
|
||||||
serverUrl: String,
|
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||||
label: String,
|
contentDescription: String,
|
||||||
iconPath: String,
|
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val base = serverUrl.trimEnd('/')
|
val shape = RoundedCornerShape(14.dp)
|
||||||
Column(
|
|
||||||
modifier = Modifier.clickable(onClick = onClick),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
|
||||||
) {
|
|
||||||
AsyncImage(
|
|
||||||
model = "$base/themes/forbion/images/$iconPath",
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
contentScale = ContentScale.Fit,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Medium,
|
|
||||||
color = if (selected) F7Colors.TextPrimary else F7Colors.TextSecondary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.height(2.dp)
|
.height(40.dp)
|
||||||
.width(72.dp)
|
.clip(shape)
|
||||||
.clip(RoundedCornerShape(1.dp))
|
.background(if (selected) F7Colors.PrimaryLight else F7Colors.Surface)
|
||||||
.background(if (selected) F7Colors.Primary else Color.Transparent),
|
.border(1.dp, if (selected) Color.Transparent else F7Colors.Border, shape)
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
tint = if (selected) F7Colors.Primary else F7Colors.TextSecondary,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1348,8 +1395,24 @@ private fun FilesEventsTabContent(
|
|||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
onEnter = onSubmitComment,
|
onEnter = onSubmitComment,
|
||||||
)
|
)
|
||||||
TextButton(onClick = onSubmitComment) {
|
Box(
|
||||||
Text("→", fontSize = 20.sp)
|
modifier = Modifier
|
||||||
|
.size(40.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(
|
||||||
|
Brush.linearGradient(
|
||||||
|
listOf(F7Colors.PrimaryGradientStart, F7Colors.PrimaryGradientEnd),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.clickable(onClick = onSubmitComment),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Filled.ArrowForward,
|
||||||
|
contentDescription = "Отправить",
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ fun FilesScreen(
|
|||||||
loading = state.detailsLoading,
|
loading = state.detailsLoading,
|
||||||
onDismiss = vm::hideDetails,
|
onDismiss = vm::hideDetails,
|
||||||
onTabChange = vm::setDetailsTab,
|
onTabChange = vm::setDetailsTab,
|
||||||
|
onToggleFavorite = { state.detailsItem?.let { vm.toggleFavorite(session, it) } },
|
||||||
onWebOnlyAction = vm::showWebOnlyMessage,
|
onWebOnlyAction = vm::showWebOnlyMessage,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user