design(files): навигация, панель выделения, свойства, поповеры — 1:1 по теме
_mobile-files.css, оставшиеся секции: - Левая панель: ширина 90%, фон #FBFBFB, отступы темы, градиентная кнопка «Создать или загрузить» (40dp, 104°, белые папка/плюс/разделитель) — кнопка создания из шапки уехала сюда, как в живом; строки 40dp/r8, иконка 20, текст 16/20, активная #ECF9DE, без «⋮»; квота по центру серым. - Панель выделения: 40dp, чекбокс темы «выбрать все» (провязан selectAllVisible/clearSelection), «Выбрать все» чёрным + «(Выбран N)» серым, пилюля «Действия» 25dp. - Панель свойств: на весь экран без скруглений, фон #FBFBFB, крестик 40, заголовок 18/600 + звезда темы 20 (зелёная/серая), чип владельца-пилюля 25dp (аватар 16), «дата / размер» серым справа; табы-пилюли 30dp с иконками темы (comment/people-icon-black), активная #ECF9DE с рамкой #70B62B 20%. - Поповеры действий (строка и массовые): белые r8 с рамкой и тенью 8%, пункты 25dp/r6, иконка 16, текст 14. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_655_2917)">
|
||||
<path d="M2.5 5C2.5 3.61929 3.61929 2.5 5 2.5H10H15C16.3807 2.5 17.5 3.61929 17.5 5V12.2727C17.5 13.6534 16.3807 14.7727 15 14.7727H11.6654C11.3957 14.7727 11.1333 14.86 10.9172 15.0214L6.32676 18.4514C6.16189 18.5746 5.92712 18.4569 5.92712 18.2511V15.0227C5.92712 14.8847 5.81519 14.7727 5.67712 14.7727H5C3.61929 14.7727 2.5 13.6534 2.5 12.2727V5Z" stroke="black" stroke-width="1.5"/>
|
||||
<path d="M6.25 8.75H6.25972" stroke="black" stroke-width="1.875" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.0098 8.75H10.0195" stroke="black" stroke-width="1.875" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.7695 8.75H13.7793" stroke="black" stroke-width="1.875" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_655_2917">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 983 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 4.72274V12.6619C15 13.4009 14.4009 14 13.6619 14H2.33813C1.5991 14 1 13.4009 1 12.6619L1 3.33812C1 2.5991 1.5991 2 2.33813 2H8.17409C8.55792 2 8.92327 2.16483 9.17728 2.45259L9.60049 2.93203C9.8545 3.21979 10.2199 3.38462 10.6037 3.38462H13.6619C14.4009 3.38462 15 3.98371 15 4.72274Z" stroke="#ffffffff" stroke-width="1.5"/>
|
||||
<path d="M1 8.74989V12.6619C1 13.4009 1.5991 14 2.33813 14H13.6619C14.4009 14 15 13.4009 15 12.6619V7.33812C15 6.5991 14.4009 6 13.6619 6H6.86607C6.52921 6 6.20475 6.12705 5.95745 6.35578L5.20044 7.05598C4.95315 7.28472 4.62868 7.41176 4.29183 7.41176H2.33812C1.5991 7.41176 1 8.01086 1 8.74989Z" stroke="#ffffffff" stroke-width="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 779 B |
@@ -0,0 +1,13 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_655_7627)">
|
||||
<path d="M15.3624 8.67973C15.2352 10.4449 13.9258 11.8048 12.4978 11.8048C11.0698 11.8048 9.75817 10.4454 9.63317 8.67973C9.50296 6.84333 10.7777 5.55469 12.4978 5.55469C14.2179 5.55469 15.4926 6.87675 15.3624 8.67973Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12.5004 14.584C9.67175 14.584 6.95162 15.989 6.27017 18.7251C6.17989 19.0871 6.50327 19.3756 6.87524 19.3756H18.3187C18.6907 19.3756 18.82 19.0871 18.731 18.7251C18.0496 15.9451 15.3295 14.584 12.5004 14.584Z" stroke="black" stroke-width="1.5" stroke-miterlimit="10"/>
|
||||
<path d="M8.74367 3.83406C8.63931 5.28263 7.55198 6.42347 6.37993 6.42347C5.20787 6.42347 4.11876 5.28308 4.01618 3.83406C3.90959 2.32706 4.96792 1.25 6.37993 1.25C7.79193 1.25 8.85026 2.35471 8.74367 3.83406Z" stroke="black" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.75 9.39002C7.97203 8.92775 7.11518 8.75 6.20704 8.75C3.96579 8.75 1.80642 10.1949 1.26551 13.0094C1.19439 13.3816 1.37455 13.75 1.66936 13.75H6.50875" stroke="black" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_655_7627">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_245_63266)">
|
||||
<path d="M1.5 8L14.5 8" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M8 1.5L8 14.5" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_245_63266">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 415 B |
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_12335_36924)">
|
||||
<path d="M6.10476 3.60501C6.94943 2.09034 7.37143 1.33301 8.00276 1.33301C8.63409 1.33301 9.05609 2.09034 9.90076 3.60501L10.1194 3.99701C10.3594 4.42767 10.4794 4.64301 10.6661 4.78501C10.8528 4.92701 11.0861 4.97967 11.5528 5.08501L11.9768 5.18101C13.6168 5.55234 14.4361 5.73767 14.6314 6.36501C14.8261 6.99167 14.2674 7.64567 13.1494 8.95301L12.8601 9.29101C12.5428 9.66234 12.3834 9.84834 12.3121 10.0777C12.2408 10.3077 12.2648 10.5557 12.3128 11.051L12.3568 11.5023C12.5254 13.247 12.6101 14.119 12.0994 14.5063C11.5888 14.8937 10.8208 14.5403 9.28609 13.8337L8.88809 13.651C8.45209 13.4497 8.23409 13.3497 8.00276 13.3497C7.77143 13.3497 7.55343 13.4497 7.11743 13.651L6.72009 13.8337C5.18476 14.5403 4.41676 14.8937 3.90676 14.507C3.39543 14.119 3.48009 13.247 3.64876 11.5023L3.69276 11.0517C3.74076 10.5557 3.76476 10.3077 3.69276 10.0783C3.62209 9.84834 3.46276 9.66234 3.14543 9.29167L2.85609 8.95301C1.73809 7.64634 1.17943 6.99234 1.37409 6.36501C1.56876 5.73767 2.38943 5.55167 4.02943 5.18101L4.45343 5.08501C4.91943 4.97967 5.15209 4.92701 5.33943 4.78501C5.52676 4.64301 5.64609 4.42767 5.88609 3.99701L6.10476 3.60501Z" stroke="#808080" stroke-width="1.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_12335_36924">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -27,11 +28,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
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.Icon
|
||||
import androidx.compose.material3.CheckboxDefaults
|
||||
@@ -105,6 +102,7 @@ fun FilesNavigationSidebar(
|
||||
onToggleTreePath: (String) -> Unit,
|
||||
onToggleSection: (String) -> Unit,
|
||||
onWebOnlyClick: (String) -> Unit,
|
||||
onCreateClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val base = serverUrl.trimEnd('/')
|
||||
AnimatedVisibility(
|
||||
@@ -126,11 +124,18 @@ fun FilesNavigationSidebar(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxWidth(0.86f)
|
||||
.background(F7Colors.Surface)
|
||||
.fillMaxWidth(0.9f)
|
||||
.background(F7Colors.Background)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = 12.dp, vertical = 12.dp),
|
||||
.padding(start = 10.dp, top = 10.dp, end = 16.dp, bottom = 26.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
onCreateClick?.let { create ->
|
||||
FilesSidebarCreateButton(
|
||||
onClick = create,
|
||||
modifier = Modifier.padding(bottom = 4.dp),
|
||||
)
|
||||
}
|
||||
FilesSidebarRow(
|
||||
serverUrl = base,
|
||||
iconPath = "files/folder-gray.svg",
|
||||
@@ -233,11 +238,13 @@ fun FilesNavigationSidebar(
|
||||
}
|
||||
if (usage.isNotBlank()) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
// Живая тема: квота по центру, серым 16/20, без прогресс-бара.
|
||||
Text(
|
||||
text = "Использовано $usage",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = F7Colors.TextSecondary,
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -307,6 +314,56 @@ private fun FilesTreeNodeRows(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Градиентная кнопка «Создать или загрузить» сверху панели (живая тема: 40dp, r100,
|
||||
* градиент 104°, белая папка слева, разделитель и плюс справа).
|
||||
*/
|
||||
@Composable
|
||||
fun FilesSidebarCreateButton(onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp)
|
||||
.clip(RoundedCornerShape(F7Radius.pill))
|
||||
.background(
|
||||
Brush.linearGradient(listOf(Color(0xFFC0FF7B), Color(0xFF7CBC3D))),
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AsyncImage(
|
||||
model = FileIcons.chromeAsset("folder-white.svg"),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
Text(
|
||||
text = "Создать или загрузить",
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 12.dp),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = Color(0xFFFDFDFD),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
.height(40.dp)
|
||||
.background(Color.White),
|
||||
)
|
||||
AsyncImage(
|
||||
model = FileIcons.chromeAsset("plus-white.svg"),
|
||||
contentDescription = "Создать",
|
||||
modifier = Modifier
|
||||
.padding(start = 14.dp)
|
||||
.size(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FilesSidebarRow(
|
||||
serverUrl: String,
|
||||
@@ -315,36 +372,34 @@ private fun FilesSidebarRow(
|
||||
selected: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
// Живая тема: строка minHeight 40, r8, иконка 20 серым, текст 16/20 #151515,
|
||||
// активная — фон #ECF9DE; правых «⋮» в навигации нет.
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 40.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (selected) Color(0xFFE8F5E0) else Color.Transparent)
|
||||
.background(if (selected) F7Colors.PrimaryLight else Color.Transparent)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 8.dp, vertical = 10.dp),
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AsyncImage(
|
||||
model = "$serverUrl/themes/forbion/images/$iconPath",
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(20.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = if (selected) FontWeight.SemiBold else FontWeight.Normal,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = F7Colors.TextPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
AsyncImage(
|
||||
model = "$serverUrl/themes/forbion/images/files/dots-vertic-gray.svg",
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,16 +421,12 @@ private fun FilesSidebarChildRow(
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = F7Colors.TextPrimary,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
AsyncImage(
|
||||
model = "$serverUrl/themes/forbion/images/files/dots-vertic-gray.svg",
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,22 +443,23 @@ private fun FilesSidebarExpandableSection(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 40.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = onToggle)
|
||||
.padding(horizontal = 8.dp, vertical = 10.dp),
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AsyncImage(
|
||||
model = "$serverUrl/themes/forbion/images/$iconPath",
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(20.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
Text(text = if (expanded) "▴" else "▾", color = F7Colors.TextSecondary)
|
||||
@@ -744,47 +796,60 @@ private fun FilesHeaderColumn(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Панель выделения по живой теме: 40dp, чекбокс 16 «выбрать все», «Выбрать все» чёрным +
|
||||
* «(Выбран N)» серым, справа — пилюля «Действия» 25dp (без иконки).
|
||||
*/
|
||||
@Composable
|
||||
fun FilesBulkActionsBar(
|
||||
serverUrl: String,
|
||||
selectedCount: Int,
|
||||
allSelected: Boolean = false,
|
||||
onToggleSelectAll: (() -> Unit)? = null,
|
||||
onActionsClick: (Rect) -> Unit,
|
||||
) {
|
||||
var anchorBounds by remember { mutableStateOf<Rect?>(null) }
|
||||
val base = serverUrl.trimEnd('/')
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 8.dp),
|
||||
.height(40.dp)
|
||||
.padding(horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
F7ThemeCheckbox(checked = allSelected, onToggle = { onToggleSelectAll?.invoke() })
|
||||
Text(
|
||||
text = "Выбрано $selectedCount",
|
||||
text = buildAnnotatedString {
|
||||
append("Выбрать все ")
|
||||
withStyle(SpanStyle(color = F7Colors.TextSecondary)) {
|
||||
append("(Выбран $selectedCount)")
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = F7Colors.TextPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(25.dp)
|
||||
.clip(RoundedCornerShape(100.dp))
|
||||
.background(F7Colors.Background)
|
||||
.border(1.dp, F7Colors.Border, RoundedCornerShape(100.dp))
|
||||
.clickable {
|
||||
anchorBounds?.let(onActionsClick)
|
||||
}
|
||||
.clickable { anchorBounds?.let(onActionsClick) }
|
||||
.onGloballyPositioned { coords ->
|
||||
anchorBounds = coords.boundsInWindow()
|
||||
}
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
.padding(horizontal = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
AsyncImage(
|
||||
model = "$base/themes/forbion/images/files/dots-vertic-gray.svg",
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(14.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
Text(
|
||||
"Действия",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = F7Colors.TextPrimary,
|
||||
)
|
||||
Text("Действия", style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -929,12 +994,14 @@ fun FileActionMenuPopup(
|
||||
properties = PopupProperties(focusable = true),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.widthIn(min = 260.dp, max = 320.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
shadowElevation = 8.dp,
|
||||
modifier = Modifier
|
||||
.widthIn(min = 280.dp, max = 340.dp)
|
||||
.border(1.dp, F7Colors.Border, RoundedCornerShape(8.dp)),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
shadowElevation = 4.dp,
|
||||
color = Color.White,
|
||||
) {
|
||||
Column(modifier = Modifier.padding(vertical = 6.dp)) {
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
entries.forEachIndexed { index, entry ->
|
||||
if (entry.destructive && index > 0) {
|
||||
HorizontalDivider(
|
||||
@@ -945,18 +1012,20 @@ fun FileActionMenuPopup(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 25.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.clickable {
|
||||
onDismiss()
|
||||
onAction(entry.action)
|
||||
}
|
||||
.padding(horizontal = 14.dp, vertical = 11.dp),
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
AsyncImage(
|
||||
model = "$base/themes/forbion/images/${entry.iconPath}",
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp),
|
||||
modifier = Modifier.size(16.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
Text(
|
||||
@@ -1006,12 +1075,14 @@ fun FilesBulkActionMenuPopup(
|
||||
properties = PopupProperties(focusable = true),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.widthIn(min = 240.dp, max = 300.dp),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
shadowElevation = 8.dp,
|
||||
modifier = Modifier
|
||||
.widthIn(min = 240.dp, max = 300.dp)
|
||||
.border(1.dp, F7Colors.Border, RoundedCornerShape(8.dp)),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
shadowElevation = 4.dp,
|
||||
color = Color.White,
|
||||
) {
|
||||
Column(modifier = Modifier.padding(vertical = 6.dp)) {
|
||||
Column(modifier = Modifier.padding(4.dp)) {
|
||||
entries.forEachIndexed { index, (action, label, icon) ->
|
||||
if (index == entries.lastIndex) {
|
||||
HorizontalDivider(
|
||||
@@ -1063,25 +1134,26 @@ fun FilesDetailsSheet(
|
||||
) {
|
||||
if (item == null) return
|
||||
var commentDraft by remember(item.relativePath) { mutableStateOf("") }
|
||||
androidx.compose.ui.window.Dialog(onDismissRequest = onDismiss) {
|
||||
// Живая тема: панель на весь экран, фон #FBFBFB, без скруглений.
|
||||
androidx.compose.ui.window.Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = androidx.compose.ui.window.DialogProperties(usePlatformDefaultWidth = false),
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.92f),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
color = F7Colors.Surface,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = F7Colors.Background,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(20.dp),
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
) {
|
||||
// Крестик закрытия — правый верхний угол (по макету)
|
||||
// Крестик 40dp — правый верхний угол
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(28.dp)
|
||||
.clip(CircleShape)
|
||||
.size(40.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(onClick = onDismiss),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
@@ -1093,75 +1165,81 @@ fun FilesDetailsSheet(
|
||||
)
|
||||
}
|
||||
}
|
||||
// Заголовок 18/600 + звезда 20 справа (живая тема)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
item.name,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.titleMedium.copy(fontSize = 18.sp),
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = F7Colors.TextPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
AsyncImage(
|
||||
model = FileIcons.chromeAsset(
|
||||
if (item.favorite) "star-green-full.svg" else "star-gray.svg",
|
||||
),
|
||||
contentDescription = "Избранное",
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.clickable(onClick = onToggleFavorite),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(8.dp))
|
||||
// Чип владельца (пилюля 25dp) слева, «дата / размер» серым справа
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(25.dp)
|
||||
.clip(RoundedCornerShape(100.dp))
|
||||
.background(F7Colors.Background)
|
||||
.border(1.dp, F7Colors.Border, RoundedCornerShape(100.dp))
|
||||
.padding(horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.clip(CircleShape)
|
||||
.background(F7Colors.PrimaryLight),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
ownerLabel.firstOrNull()?.uppercaseChar()?.toString() ?: "?",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = F7Colors.Primary,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
item.name,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 2,
|
||||
ownerLabel,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = F7Colors.TextPrimary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
// Чип владельца (аватар-инициал + логин) — по макету
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(100.dp))
|
||||
.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,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
ownerLabel,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = F7Colors.TextSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
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)) }
|
||||
Spacer(Modifier.weight(1f))
|
||||
val dateSize = buildList {
|
||||
item.lastModified?.let {
|
||||
add(SimpleDateFormat("d MMMM", Locale("ru")).format(Date(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),
|
||||
item.size?.let { add(formatFileSize(it)) }
|
||||
}
|
||||
if (dateSize.isNotEmpty()) {
|
||||
Text(
|
||||
dateSize.joinToString(" / "),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = F7Colors.TextSecondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1171,14 +1249,14 @@ fun FilesDetailsSheet(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
FilesDetailsTabPill(
|
||||
icon = Icons.Filled.ChatBubbleOutline,
|
||||
iconAsset = "comment-icon-black.svg",
|
||||
contentDescription = "Обсуждение",
|
||||
selected = tab == FileDetailsTab.Events,
|
||||
onClick = { onTabChange(FileDetailsTab.Events) },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
FilesDetailsTabPill(
|
||||
icon = Icons.Filled.Group,
|
||||
iconAsset = "people-icon-black.svg",
|
||||
contentDescription = "Общий доступ",
|
||||
selected = tab == FileDetailsTab.Sharing,
|
||||
onClick = { onTabChange(FileDetailsTab.Sharing) },
|
||||
@@ -1229,27 +1307,31 @@ fun FilesDetailsSheet(
|
||||
|
||||
@Composable
|
||||
private fun FilesDetailsTabPill(
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
iconAsset: String,
|
||||
contentDescription: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val shape = RoundedCornerShape(14.dp)
|
||||
// Живая тема: пилюля 30dp r100; активная — фон #ECF9DE, рамка #70B62B 20%; иконка темы 16.
|
||||
val shape = RoundedCornerShape(100.dp)
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(40.dp)
|
||||
.height(30.dp)
|
||||
.clip(shape)
|
||||
.background(if (selected) F7Colors.PrimaryLight else F7Colors.Surface)
|
||||
.border(1.dp, if (selected) Color.Transparent else F7Colors.Border, shape)
|
||||
.background(if (selected) F7Colors.PrimaryLight else F7Colors.Background)
|
||||
.border(
|
||||
1.dp,
|
||||
if (selected) F7Colors.Primary.copy(alpha = 0.2f) else F7Colors.Border,
|
||||
shape,
|
||||
)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
icon,
|
||||
AsyncImage(
|
||||
model = FileIcons.chromeAsset(iconAsset),
|
||||
contentDescription = contentDescription,
|
||||
tint = if (selected) F7Colors.Primary else F7Colors.TextSecondary,
|
||||
modifier = Modifier.size(20.dp),
|
||||
modifier = Modifier.size(16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,9 +191,8 @@ fun FilesScreen(
|
||||
FilesViewToggleButton(viewMode) {
|
||||
viewMode = if (viewMode == FilesViewMode.List) FilesViewMode.Grid else FilesViewMode.List
|
||||
}
|
||||
// Создание (загрузка/новая папка) — раньше вызывалось из нижней панели,
|
||||
// теперь кнопка в шапке (нижняя панель стала навигационной)
|
||||
F7CreateButton(onClick = { showCreateMenu = true }, size = 40.dp)
|
||||
// Кнопки создания в шапке нет (живая тема): «Создать или загрузить» —
|
||||
// градиентная кнопка сверху панели навигации.
|
||||
}
|
||||
// Хлебные крошки
|
||||
val crumbSegments = state.currentPath.split("/").filter { it.isNotBlank() }
|
||||
@@ -203,9 +202,15 @@ fun FilesScreen(
|
||||
onSegmentClick = { i -> vm.navigateToPath(session, crumbSegments.take(i + 1).joinToString("/")) },
|
||||
)
|
||||
if (selectionMode) {
|
||||
val allSelected = displayItems.isNotEmpty() &&
|
||||
state.selectedPaths.size >= displayItems.size
|
||||
FilesBulkActionsBar(
|
||||
serverUrl = session.serverUrl,
|
||||
selectedCount = state.selectedPaths.size,
|
||||
allSelected = allSelected,
|
||||
onToggleSelectAll = {
|
||||
if (allSelected) vm.clearSelection() else vm.selectAllVisible()
|
||||
},
|
||||
onActionsClick = { rect ->
|
||||
bulkMenuAnchor = rect
|
||||
bulkMenuOpen = true
|
||||
@@ -323,6 +328,10 @@ fun FilesScreen(
|
||||
vm.showWebOnlyMessage(title)
|
||||
onSidebarOpenChange(false)
|
||||
},
|
||||
onCreateClick = {
|
||||
onSidebarOpenChange(false)
|
||||
showCreateMenu = true
|
||||
},
|
||||
)
|
||||
|
||||
FilesCreateMenu(
|
||||
|
||||
Reference in New Issue
Block a user