bottombar: навигационная панель по дизайну «Новое меню»

Панель стала единой навигационной: слева назад (в Конференциях — чаты),
далее ярлыки-разделы Почта · Карточки · Конференции (активный подсвечен),
справа выдвижное меню. Профиль/Уведомления/Настройки/Создать убраны из
панели (Профиль/Уведомления/Настройки — в выдвижном меню; бейдж
уведомлений перенесён на кнопку меню).

Создание раньше вызывалось ТОЛЬКО из панели → чтобы не потерять:
- новый общий компонент F7CreateButton (зелёный круг «+»);
- добавлен в шапку Файлов (→ меню создания) и Контактов (→ добавить контакт).
Задачи/Поддержка уже имели свои кнопки; Почта — «+» в шапке.

F7BottomBarConfig упрощён (единый layout), F7MobileBottomBar получил
слоты SectionMail/SectionCards/SectionConferences + activeTabKey.
This commit is contained in:
b-dev-mobile
2026-07-08 15:26:24 +00:00
parent a30af98a8d
commit 89e32d2875
6 changed files with 126 additions and 120 deletions
@@ -483,6 +483,7 @@ fun AppScaffold(
serverUrl = currentSession.serverUrl,
userId = userId,
config = bottomBarConfig,
activeTabKey = activeTab.name,
menuOpen = menuOpen,
chatsHighlighted = activeTab == AppTab.Talk && !talkInRoom,
navBackHighlighted = (activeTab == AppTab.Mail && mailSidebarOpen) ||
@@ -499,36 +500,22 @@ fun AppScaffold(
else -> Unit
}
},
onCreateClick = {
when (activeTab) {
AppTab.Files -> {
if (F7AppPermissions.missing(context).isNotEmpty()) {
onRequestRuntimePermissions { filesUploadRequest++ }
} else {
filesUploadRequest++
}
}
AppTab.Contacts -> contactsCreateRequest++
AppTab.Tasks -> tasksCreateRequest++
AppTab.Support -> supportCreateRequest++
else -> Unit
onMailClick = {
if (activeTab != AppTab.Mail) {
pushTabHistory(activeTab)
activeTab = AppTab.Mail
}
},
onProfileClick = {
menuOpen = false
profileOpen = true
onCardsClick = {
if (activeTab != AppTab.Deck) {
pushTabHistory(activeTab)
activeTab = AppTab.Deck
}
},
onNotificationsClick = {
menuOpen = false
hasNotificationBadge = false
notificationsOpen = true
},
onSettingsClick = {
when (activeTab) {
AppTab.Mail -> mailSettingsOpen = true
AppTab.Calendar -> calendarSettingsRequest++
AppTab.Files -> filesSettingsOpen = true
else -> Unit
onConferencesClick = {
if (activeTab != AppTab.Talk) {
pushTabHistory(activeTab)
activeTab = AppTab.Talk
}
},
onMenuClick = { menuOpen = !menuOpen },
@@ -647,6 +634,7 @@ fun AppScaffold(
// Нативные пункты по дизайну меню
label.equals("Уведомления", ignoreCase = true) -> {
menuOpen = false
hasNotificationBadge = false
notificationsOpen = true
}
label.equals("Настройки", ignoreCase = true) -> {
@@ -3,10 +3,9 @@ package ru.forbion.f7cloud.core.designsystem
enum class F7BottomBarSlot {
Chats,
NavBack,
Create,
Profile,
Notifications,
Settings,
SectionMail,
SectionCards,
SectionConferences,
Menu,
}
@@ -16,69 +15,21 @@ data class F7BottomBarConfig(
val buttonCount: Int get() = slots.size
companion object {
// Единая навигационная панель по дизайну «Новое меню»:
// слева «назад» (в Конференциях — «чаты»), затем ярлыки-разделы
// Почта · Карточки · Конференции, справа — выдвижное меню.
// Профиль/Уведомления/Настройки/Создать вынесены (меню и шапки экранов).
fun forContext(
tabKey: String,
talkInRoom: Boolean,
): F7BottomBarConfig = when (tabKey) {
"Talk" -> if (talkInRoom) {
F7BottomBarConfig(listOf(F7BottomBarSlot.Profile, F7BottomBarSlot.Notifications, F7BottomBarSlot.Menu))
} else {
F7BottomBarConfig(
): F7BottomBarConfig {
val left = if (tabKey == "Talk") F7BottomBarSlot.Chats else F7BottomBarSlot.NavBack
return F7BottomBarConfig(
listOf(
F7BottomBarSlot.Chats,
F7BottomBarSlot.Profile,
F7BottomBarSlot.Notifications,
F7BottomBarSlot.Menu,
),
)
}
"Files" -> F7BottomBarConfig(
listOf(
F7BottomBarSlot.NavBack,
F7BottomBarSlot.Profile,
F7BottomBarSlot.Notifications,
F7BottomBarSlot.Create,
F7BottomBarSlot.Settings,
F7BottomBarSlot.Menu,
),
)
"Contacts" -> F7BottomBarConfig(
listOf(
F7BottomBarSlot.Create,
F7BottomBarSlot.Profile,
F7BottomBarSlot.Notifications,
F7BottomBarSlot.Menu,
),
)
"Tasks" -> F7BottomBarConfig(
listOf(
F7BottomBarSlot.Create,
F7BottomBarSlot.Profile,
F7BottomBarSlot.Notifications,
F7BottomBarSlot.Menu,
),
)
"Support" -> F7BottomBarConfig(
listOf(
F7BottomBarSlot.Create,
F7BottomBarSlot.Profile,
F7BottomBarSlot.Notifications,
F7BottomBarSlot.Menu,
),
)
"Mail", "Calendar" -> F7BottomBarConfig(
listOf(
F7BottomBarSlot.NavBack,
F7BottomBarSlot.Profile,
F7BottomBarSlot.Notifications,
F7BottomBarSlot.Settings,
F7BottomBarSlot.Menu,
),
)
else -> F7BottomBarConfig(
listOf(
F7BottomBarSlot.Profile,
F7BottomBarSlot.Notifications,
left,
F7BottomBarSlot.SectionMail,
F7BottomBarSlot.SectionCards,
F7BottomBarSlot.SectionConferences,
F7BottomBarSlot.Menu,
),
)
@@ -0,0 +1,53 @@
package ru.forbion.f7cloud.core.designsystem
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
* Зелёная круглая кнопка «+» — единый элемент создания в шапках экранов
* (Почта/Файлы/Контакты/…). Градиент Primary, как у других зелёных кнопок.
*/
@Composable
fun F7CreateButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
size: Dp = 44.dp,
) {
Box(
modifier = modifier
.size(size)
.shadow(3.dp, CircleShape, spotColor = F7Colors.Primary.copy(alpha = 0.25f))
.clip(CircleShape)
.background(
Brush.linearGradient(
listOf(F7Colors.PrimaryGradientStart, F7Colors.PrimaryGradientEnd),
),
)
.border(1.dp, F7Colors.Primary.copy(alpha = 0.22f), CircleShape)
.clickable(onClick = onClick),
contentAlignment = Alignment.Center,
) {
Text(
text = "+",
color = Color.White,
fontSize = 26.sp,
fontWeight = FontWeight.Light,
)
}
}
@@ -27,10 +27,9 @@ import coil.compose.AsyncImage
data class F7BottomBarActions(
val onChatsClick: () -> Unit = {},
val onNavBackClick: () -> Unit = {},
val onCreateClick: () -> Unit = {},
val onProfileClick: () -> Unit = {},
val onNotificationsClick: () -> Unit = {},
val onSettingsClick: () -> Unit = {},
val onMailClick: () -> Unit = {},
val onCardsClick: () -> Unit = {},
val onConferencesClick: () -> Unit = {},
val onMenuClick: () -> Unit = {},
)
@@ -53,6 +52,7 @@ fun F7MobileBottomBar(
userId: String,
config: F7BottomBarConfig,
actions: F7BottomBarActions,
activeTabKey: String = "",
menuOpen: Boolean = false,
chatsHighlighted: Boolean = false,
navBackHighlighted: Boolean = false,
@@ -97,26 +97,23 @@ fun F7MobileBottomBar(
iconRotation = if (navBackHighlighted) 180f else 0f,
onClick = actions.onNavBackClick,
)
F7BottomBarSlot.Create -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/green-plus.svg",
contentDescription = "Создать",
onClick = actions.onCreateClick,
F7BottomBarSlot.SectionMail -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/mail-header-icon.svg",
contentDescription = "Почта",
highlighted = activeTabKey == "Mail",
onClick = actions.onMailClick,
)
F7BottomBarSlot.Profile -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/profile-menu-icon-big.svg",
contentDescription = "Профиль",
onClick = actions.onProfileClick,
F7BottomBarSlot.SectionCards -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/deck-header-icon.svg",
contentDescription = "Карточки",
highlighted = activeTabKey == "Deck",
onClick = actions.onCardsClick,
)
F7BottomBarSlot.Notifications -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/not-menu-icon-big.svg",
contentDescription = "Уведомления",
showBadge = showNotificationBadge,
onClick = actions.onNotificationsClick,
)
F7BottomBarSlot.Settings -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/setting-menu-icon.svg",
contentDescription = "Настройки",
onClick = actions.onSettingsClick,
F7BottomBarSlot.SectionConferences -> F7BottomBarIconSlot(
iconUrl = "$base/themes/forbion/images/header/spreed-header-icon.svg",
contentDescription = "Конференции",
highlighted = activeTabKey == "Talk",
onClick = actions.onConferencesClick,
)
F7BottomBarSlot.Menu -> F7BottomBarIconSlot(
iconUrl = if (menuOpen) {
@@ -126,6 +123,7 @@ fun F7MobileBottomBar(
},
contentDescription = "Меню",
highlighted = menuOpen,
showBadge = showNotificationBadge,
onClick = actions.onMenuClick,
)
}
@@ -48,6 +48,7 @@ import coil.compose.AsyncImage
import coil.request.ImageRequest
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.designsystem.F7Colors
import ru.forbion.f7cloud.core.designsystem.F7CreateButton
import ru.forbion.f7cloud.core.designsystem.F7ModuleScreen
@Composable
@@ -75,12 +76,24 @@ fun ContactsScreen(
modifier = modifier,
loading = state.loading && state.contacts.isEmpty(),
error = state.error,
) {
// Поиск + кнопка создания контакта (создание раньше вызывалось из нижней
// панели, теперь — из шапки, панель стала навигационной)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
ContactsSearchBar(
serverUrl = session.serverUrl,
query = state.searchQuery,
onQueryChange = vm::setSearchQuery,
modifier = Modifier.weight(1f),
)
F7CreateButton(onClick = vm::openAddSheet, size = 40.dp)
}
if (state.syncing && state.contacts.isNotEmpty()) {
Text(
"Обновление…",
@@ -297,12 +310,11 @@ private fun ContactsSearchBar(
serverUrl: String,
query: String,
onQueryChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
val base = serverUrl.trimEnd('/')
Box(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
modifier = modifier
.height(40.dp)
.shadow(2.dp, RoundedCornerShape(100.dp), spotColor = Color(0xFFCBCBCB))
.clip(RoundedCornerShape(100.dp))
@@ -41,6 +41,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.designsystem.F7AlertDialog
import ru.forbion.f7cloud.core.designsystem.F7Colors
import ru.forbion.f7cloud.core.designsystem.F7CreateButton
import ru.forbion.f7cloud.core.designsystem.F7ModuleScreen
import ru.forbion.f7cloud.core.designsystem.F7OverlayDismissHandler
import ru.forbion.f7cloud.core.designsystem.F7OutlinedField
@@ -189,6 +190,9 @@ 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() }