diff --git a/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppNavigation.kt b/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppNavigation.kt
index 46b2434..ef6102d 100644
--- a/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppNavigation.kt
+++ b/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppNavigation.kt
@@ -1,10 +1,10 @@
package ru.forbion.f7cloud.mobile.ui
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Notifications
-import androidx.compose.material.icons.filled.Settings
import ru.forbion.f7cloud.core.designsystem.F7AppMenuItem
+// Иконки-glass шторки — локальные (assets/menu/, из живой темы forbion): офлайн + точные.
+private fun glass(name: String): String = "file:///android_asset/menu/$name"
+
enum class AppTab(
val title: String,
val headerIconPath: String,
@@ -59,7 +59,7 @@ fun appMenuItems(
val core = coreAppMenuEntries.map { entry ->
F7AppMenuItem(
label = entry.label,
- iconUrl = "$base/themes/forbion/images/header/${entry.iconPath}",
+ iconUrl = glass(entry.iconPath),
selected = entry.tab == active,
externalUrl = entry.webPath?.let { "$base$it" },
)
@@ -72,19 +72,17 @@ fun appMenuItems(
externalUrl = site.openUrl,
)
}
- // Нативные пункты по дизайну — действия обрабатываются в AppScaffold по label
+ // Нативные пункты — тоже glass-иконки живой темы (mobile-*-glass), действия по label в AppScaffold
val native = listOf(
F7AppMenuItem(
label = "Уведомления",
- iconUrl = "",
+ iconUrl = glass("mobile-notifications-glass.svg"),
selected = false,
- localIcon = Icons.Filled.Notifications,
),
F7AppMenuItem(
label = "Настройки",
- iconUrl = "",
+ iconUrl = glass("mobile-settings-glass.svg"),
selected = false,
- localIcon = Icons.Filled.Settings,
),
)
return core + external + native
diff --git a/core/designsystem/src/main/assets/menu/calendar-glass.svg b/core/designsystem/src/main/assets/menu/calendar-glass.svg
new file mode 100644
index 0000000..6908875
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/calendar-glass.svg
@@ -0,0 +1,44 @@
+
diff --git a/core/designsystem/src/main/assets/menu/contact-glass.svg b/core/designsystem/src/main/assets/menu/contact-glass.svg
new file mode 100644
index 0000000..8f30fa1
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/contact-glass.svg
@@ -0,0 +1,48 @@
+
diff --git a/core/designsystem/src/main/assets/menu/deck-glass.svg b/core/designsystem/src/main/assets/menu/deck-glass.svg
new file mode 100644
index 0000000..471ba7d
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/deck-glass.svg
@@ -0,0 +1,52 @@
+
diff --git a/core/designsystem/src/main/assets/menu/deck-header-icon.svg b/core/designsystem/src/main/assets/menu/deck-header-icon.svg
new file mode 100644
index 0000000..77928ff
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/deck-header-icon.svg
@@ -0,0 +1,17 @@
+
diff --git a/core/designsystem/src/main/assets/menu/files-glass.svg b/core/designsystem/src/main/assets/menu/files-glass.svg
new file mode 100644
index 0000000..0e6dc37
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/files-glass.svg
@@ -0,0 +1,52 @@
+
diff --git a/core/designsystem/src/main/assets/menu/mail-glass.svg b/core/designsystem/src/main/assets/menu/mail-glass.svg
new file mode 100644
index 0000000..57f7f4e
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/mail-glass.svg
@@ -0,0 +1,46 @@
+
diff --git a/core/designsystem/src/main/assets/menu/mail-header-icon.svg b/core/designsystem/src/main/assets/menu/mail-header-icon.svg
new file mode 100644
index 0000000..382e3a5
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/mail-header-icon.svg
@@ -0,0 +1,8 @@
+
diff --git a/core/designsystem/src/main/assets/menu/menu-burger-gray.svg b/core/designsystem/src/main/assets/menu/menu-burger-gray.svg
new file mode 100644
index 0000000..4f30232
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/menu-burger-gray.svg
@@ -0,0 +1,12 @@
+
diff --git a/core/designsystem/src/main/assets/menu/mobile-lk-glass.svg b/core/designsystem/src/main/assets/menu/mobile-lk-glass.svg
new file mode 100644
index 0000000..72634fb
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/mobile-lk-glass.svg
@@ -0,0 +1,31 @@
+
diff --git a/core/designsystem/src/main/assets/menu/mobile-notifications-glass.svg b/core/designsystem/src/main/assets/menu/mobile-notifications-glass.svg
new file mode 100644
index 0000000..cd500ef
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/mobile-notifications-glass.svg
@@ -0,0 +1,39 @@
+
diff --git a/core/designsystem/src/main/assets/menu/mobile-settings-glass.svg b/core/designsystem/src/main/assets/menu/mobile-settings-glass.svg
new file mode 100644
index 0000000..f05c975
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/mobile-settings-glass.svg
@@ -0,0 +1,29 @@
+
diff --git a/core/designsystem/src/main/assets/menu/notes-glass.svg b/core/designsystem/src/main/assets/menu/notes-glass.svg
new file mode 100644
index 0000000..a5099be
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/notes-glass.svg
@@ -0,0 +1,51 @@
+
diff --git a/core/designsystem/src/main/assets/menu/notification-badge.svg b/core/designsystem/src/main/assets/menu/notification-badge.svg
new file mode 100644
index 0000000..fd4104f
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/notification-badge.svg
@@ -0,0 +1,5 @@
+
diff --git a/core/designsystem/src/main/assets/menu/search-glass.svg b/core/designsystem/src/main/assets/menu/search-glass.svg
new file mode 100644
index 0000000..61d72b7
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/search-glass.svg
@@ -0,0 +1,40 @@
+
diff --git a/core/designsystem/src/main/assets/menu/sidebar-chevron-left.svg b/core/designsystem/src/main/assets/menu/sidebar-chevron-left.svg
new file mode 100644
index 0000000..61a0ea5
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/sidebar-chevron-left.svg
@@ -0,0 +1,3 @@
+
diff --git a/core/designsystem/src/main/assets/menu/spreed-glass.svg b/core/designsystem/src/main/assets/menu/spreed-glass.svg
new file mode 100644
index 0000000..308ce00
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/spreed-glass.svg
@@ -0,0 +1,42 @@
+
diff --git a/core/designsystem/src/main/assets/menu/spreed-header-icon.svg b/core/designsystem/src/main/assets/menu/spreed-header-icon.svg
new file mode 100644
index 0000000..819744d
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/spreed-header-icon.svg
@@ -0,0 +1,15 @@
+
diff --git a/core/designsystem/src/main/assets/menu/task-glass.svg b/core/designsystem/src/main/assets/menu/task-glass.svg
new file mode 100644
index 0000000..c34d9b3
--- /dev/null
+++ b/core/designsystem/src/main/assets/menu/task-glass.svg
@@ -0,0 +1,58 @@
+
diff --git a/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7AppMenuSheet.kt b/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7AppMenuSheet.kt
index 957100f..6dfd071 100644
--- a/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7AppMenuSheet.kt
+++ b/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7AppMenuSheet.kt
@@ -13,21 +13,20 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.PaddingValues
-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.navigationBarsPadding
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.itemsIndexed
+import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
@@ -53,13 +52,12 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
+// Живой forbion (_header.css .app-menu--mobile): нижний отступ 90 под плавающую панель,
+// иконки 62, сетка 3 колонки с gap 8, БЕЗ скругления верха и БЕЗ ручки-хендла,
+// высота — по контенту (почти весь экран), фон #FBFBFB.
private val BottomBarReserve: Dp = 90.dp
private val MenuIconSize = 62.dp
-private val MenuGridGap = 20.dp
-
-// Меню-лист выезжает снизу и занимает ~пол-экрана (не весь), по требованию владельца
-private const val MenuSheetHeightFraction = 0.6f
-private val MenuSheetShape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
+private val MenuGridGap = 8.dp
data class F7AppMenuItem(
val label: String,
@@ -110,7 +108,8 @@ fun F7AppMenuSheet(
),
)
}
- // Сам лист — выезжает снизу вверх на ~пол-экрана, скруглённый верх + тень
+ // Сам лист — выезжает снизу вверх (translateY 100%→0), как в живом forbion:
+ // без скругления верха и без ручки, высота по контенту до почти всего экрана.
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(250)) + slideInVertically(
@@ -126,61 +125,47 @@ fun F7AppMenuSheet(
Column(
modifier = Modifier
.fillMaxWidth()
- .fillMaxHeight(MenuSheetHeightFraction)
- .padding(bottom = BottomBarReserve)
- .navigationBarsPadding()
- .shadow(16.dp, MenuSheetShape, clip = false)
- .clip(MenuSheetShape)
+ .heightIn(max = 640.dp)
.background(F7Colors.Background)
- .padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 8.dp),
+ .navigationBarsPadding()
+ .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = BottomBarReserve)
+ .verticalScroll(rememberScrollState()),
) {
- // Ручка-хендл
- Box(
- modifier = Modifier
- .align(Alignment.CenterHorizontally)
- .padding(top = 2.dp, bottom = 10.dp)
- .width(40.dp)
- .height(4.dp)
- .clip(RoundedCornerShape(2.dp))
- .background(F7Colors.Border),
- )
F7AppMenuSearchField(
serverUrl = base,
value = searchQuery,
onValueChange = { searchQuery = it },
modifier = Modifier
.fillMaxWidth()
- .padding(bottom = 20.dp),
+ .padding(bottom = 24.dp),
)
- LazyVerticalGrid(
- columns = GridCells.Fixed(3),
- horizontalArrangement = Arrangement.spacedBy(MenuGridGap),
+ // Сетка 3 колонки, gap 8 (живой forbion). Не-ленивая — лист сам в verticalScroll.
+ Column(
+ modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(MenuGridGap),
- contentPadding = PaddingValues(horizontal = 2.dp),
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f),
) {
- itemsIndexed(
- items = filteredItems,
- key = { index, item -> "${item.label}-$index" },
- ) { index, item ->
- val originalIndex = items.indexOf(item)
- Box(
- modifier = Modifier.fillMaxWidth(),
- contentAlignment = Alignment.TopCenter,
- ) {
- F7AppMenuGridItem(
- item = item,
- onClick = {
- if (originalIndex >= 0) {
- onItemClick(originalIndex)
+ filteredItems.chunked(3).forEach { rowItems ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(MenuGridGap),
+ ) {
+ rowItems.forEach { item ->
+ val originalIndex = items.indexOf(item)
+ Box(
+ modifier = Modifier.weight(1f),
+ contentAlignment = Alignment.TopCenter,
+ ) {
+ F7AppMenuGridItem(
+ item = item,
+ onClick = { if (originalIndex >= 0) onItemClick(originalIndex) },
+ )
}
- },
- )
+ }
+ // добить ряд пустыми ячейками для выравнивания по левому краю
+ repeat(3 - rowItems.size) { Box(modifier = Modifier.weight(1f)) {} }
+ }
}
}
- }
}
}
}
diff --git a/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7MobileBottomBar.kt b/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7MobileBottomBar.kt
index d20f507..6075a7e 100644
--- a/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7MobileBottomBar.kt
+++ b/core/designsystem/src/main/java/ru/forbion/f7cloud/core/designsystem/F7MobileBottomBar.kt
@@ -9,7 +9,8 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.wrapContentWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
@@ -34,17 +35,19 @@ data class F7BottomBarActions(
val onMenuClick: () -> Unit = {},
)
+// Живой forbion (_header.css .header__mobile-bottom): пилюля 328×61, паддинг 3,
+// фон #F5F5F5; кнопки 55, фон #FBFBFB, рамка #E6E6E6; активная кнопка-раздел и открытый
+// бургер — полупрозрачный зелёный градиент 104°.
+private val BottomBarWidth = 328.dp
+private val BottomBarHeight = 61.dp
private val BottomBarButtonSize = 55.dp
private val BottomBarIconSize = 34.dp
-
-// Иконки-разделы (Почта/Карточки/Конференции) — серверные SVG с внутренним
-// «воздухом», поэтому рендерим крупнее, чтобы заполняли кружок.
private val BottomBarSectionIconSize = 46.dp
-private val BottomBarGap = 8.dp
-private val BottomBarOuterPaddingH = 6.dp
-private val BottomBarOuterPaddingV = 6.dp
+private val BottomBarOuterPadding = 3.dp
private val BottomBarButtonShape = RoundedCornerShape(100.dp)
-private val BottomBarBorderBrush = Brush.linearGradient(
+private val BottomBarButtonBg = Color(0xFFFBFBFB)
+private val BottomBarButtonBorder = Color(0xFFE6E6E6)
+private val BottomBarActiveBrush = Brush.linearGradient(
listOf(Color(0x33C0FF7B), Color(0x3370B62B)),
)
@@ -62,10 +65,11 @@ fun F7MobileBottomBar(
modifier: Modifier = Modifier,
) {
val base = serverUrl.trimEnd('/')
- val pillShape = RoundedCornerShape(percent = 50)
+ val pillShape = RoundedCornerShape(100.dp)
Row(
modifier = modifier
- .wrapContentWidth()
+ .width(BottomBarWidth)
+ .height(BottomBarHeight)
.shadow(
elevation = 2.dp,
shape = pillShape,
@@ -73,11 +77,8 @@ fun F7MobileBottomBar(
)
.clip(pillShape)
.background(Color(0xFFF5F5F5))
- .padding(
- horizontal = BottomBarOuterPaddingH,
- vertical = BottomBarOuterPaddingV,
- ),
- horizontalArrangement = Arrangement.spacedBy(BottomBarGap),
+ .padding(BottomBarOuterPadding),
+ horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
config.slots.forEach { slot ->
@@ -89,6 +90,7 @@ fun F7MobileBottomBar(
"$base/themes/forbion/images/header/chat-icon-gray.svg"
},
contentDescription = "Чаты",
+ active = chatsHighlighted,
onClick = actions.onChatsClick,
)
F7BottomBarSlot.NavBack -> F7BottomBarIconSlot(
@@ -101,18 +103,21 @@ fun F7MobileBottomBar(
iconUrl = "$base/themes/forbion/images/header/mail-header-icon.svg",
contentDescription = "Почта",
iconSize = BottomBarSectionIconSize,
+ active = activeTabKey.equals("mail", ignoreCase = true),
onClick = actions.onMailClick,
)
F7BottomBarSlot.SectionCards -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/deck-header-icon.svg",
contentDescription = "Карточки",
iconSize = BottomBarSectionIconSize,
+ active = activeTabKey.equals("deck", ignoreCase = true),
onClick = actions.onCardsClick,
)
F7BottomBarSlot.SectionConferences -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/spreed-header-icon.svg",
contentDescription = "Конференции",
iconSize = BottomBarSectionIconSize,
+ active = activeTabKey.equals("talk", ignoreCase = true),
onClick = actions.onConferencesClick,
)
F7BottomBarSlot.Menu -> F7BottomBarIconSlot(
@@ -122,6 +127,7 @@ fun F7MobileBottomBar(
"$base/themes/forbion/images/header/menu-burger-gray.svg"
},
contentDescription = "Меню",
+ active = menuOpen,
showBadge = showNotificationBadge,
onClick = actions.onMenuClick,
)
@@ -135,19 +141,27 @@ private fun F7BottomBarIconSlot(
iconUrl: String,
contentDescription: String,
onClick: () -> Unit,
+ active: Boolean = false,
iconRotation: Float = 0f,
showBadge: Boolean = false,
iconSize: Dp = BottomBarIconSize,
) {
- // Зелёной подложки под активной кнопкой быть не должно — фон всегда нейтральный.
+ // Живой forbion: кнопка 55, фон #FBFBFB + рамка #E6E6E6; активная (текущий раздел /
+ // открытый бургер) — полупрозрачный зелёный градиент 104°.
Box(
modifier = Modifier
.size(BottomBarButtonSize)
.clip(BottomBarButtonShape)
- .background(Color(0x99FFFFFF), BottomBarButtonShape)
+ .then(
+ if (active) {
+ Modifier.background(BottomBarActiveBrush, BottomBarButtonShape)
+ } else {
+ Modifier.background(BottomBarButtonBg, BottomBarButtonShape)
+ },
+ )
.border(
width = 1.dp,
- brush = BottomBarBorderBrush,
+ color = BottomBarButtonBorder,
shape = BottomBarButtonShape,
)
.clickable(