notifications P1: лента с действиями + «Скрыть все»

- NotificationsRepository: парс actions[] (label/link/type/primary),
  dismissAll (DELETE /api/v2/notifications), executeAction (GET/POST/PUT/DELETE по action.link)
- F7NotificationRow: FlowRow кнопок-действий (primary=заливка, прочие=контур)
- NotificationsSheet: заголовок «Уведомления» + «Скрыть все», проброс действий,
  оптимистичное скрытие после действия/скрыть-все

Контракты сверены с исходником notifications на forbion (deleteAllNotifications, actionToArray).
This commit is contained in:
b-dev-mobile
2026-07-13 10:11:32 +00:00
parent 5f81bd92f4
commit a30627a07d
3 changed files with 250 additions and 16 deletions
@@ -59,9 +59,11 @@ import org.json.JSONObject
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.designsystem.F7Colors
import ru.forbion.f7cloud.core.designsystem.F7FloatingPanel
import ru.forbion.f7cloud.core.designsystem.F7NotificationActionUi
import ru.forbion.f7cloud.core.designsystem.F7NotificationRow
import ru.forbion.f7cloud.core.designsystem.formatNotificationRelativeTime
import ru.forbion.f7cloud.core.network.F7Notification
import ru.forbion.f7cloud.core.network.F7NotificationAction
import ru.forbion.f7cloud.core.network.NetworkFactory
import ru.forbion.f7cloud.core.network.NotificationsRepository
import ru.forbion.f7cloud.core.network.UnauthorizedException
@@ -337,6 +339,53 @@ fun NotificationsSheet(
}
}
fun dismissAllNotifications() {
items = emptyList()
scope.launch {
runCatching {
withContext(Dispatchers.IO) {
repository.dismissAll(
serverUrl = session.serverUrl,
username = session.username,
appPassword = session.appPassword,
trustAllCerts = session.trustAllCerts,
)
}
}.onFailure { t ->
if (t is UnauthorizedException) {
onDismiss()
onUnauthorized()
} else {
loadNotifications()
}
}
}
}
fun runAction(notification: F7Notification, action: F7NotificationAction) {
items = items.filter { it.id != notification.id }
scope.launch {
runCatching {
withContext(Dispatchers.IO) {
repository.executeAction(
serverUrl = session.serverUrl,
username = session.username,
appPassword = session.appPassword,
action = action,
trustAllCerts = session.trustAllCerts,
)
}
}.onFailure { t ->
if (t is UnauthorizedException) {
onDismiss()
onUnauthorized()
} else {
loadNotifications()
}
}
}
}
F7FloatingPanel(
visible = visible,
onDismiss = onDismiss,
@@ -395,24 +444,65 @@ fun NotificationsSheet(
else -> Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.verticalScroll(rememberScrollState()),
.fillMaxHeight(),
) {
items.forEachIndexed { index, item ->
F7NotificationRow(
subject = item.subject,
message = item.message,
relativeTime = formatNotificationRelativeTime(item.datetime),
iconUrl = resolveNotificationIcon(session.serverUrl, item.icon),
closeIconUrl = closeIconUrl,
authHeader = authHeader,
onClick = {
onDismiss()
onNotificationClick(item)
},
onDismiss = { dismissNotification(item) },
showDivider = index < items.lastIndex,
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"Уведомления",
style = MaterialTheme.typography.titleMedium.copy(
fontSize = MaterialTheme.typography.titleMedium.fontSize * 2,
lineHeight = MaterialTheme.typography.titleMedium.lineHeight * 2,
),
fontWeight = androidx.compose.ui.text.font.FontWeight.SemiBold,
color = F7Colors.TextPrimary,
)
TextButton(onClick = { dismissAllNotifications() }) {
Text(
"Скрыть все",
style = MaterialTheme.typography.labelLarge.copy(
fontSize = MaterialTheme.typography.labelLarge.fontSize * 2,
lineHeight = MaterialTheme.typography.labelLarge.lineHeight * 2,
),
fontWeight = androidx.compose.ui.text.font.FontWeight.SemiBold,
color = F7Colors.Primary,
)
}
}
HorizontalDivider(color = F7Colors.Border, thickness = 1.dp)
Column(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.verticalScroll(rememberScrollState()),
) {
items.forEachIndexed { index, item ->
F7NotificationRow(
subject = item.subject,
message = item.message,
relativeTime = formatNotificationRelativeTime(item.datetime),
iconUrl = resolveNotificationIcon(session.serverUrl, item.icon),
closeIconUrl = closeIconUrl,
authHeader = authHeader,
onClick = {
onDismiss()
onNotificationClick(item)
},
onDismiss = { dismissNotification(item) },
showDivider = index < items.lastIndex,
actions = item.actions.map {
F7NotificationActionUi(label = it.label, primary = it.primary)
},
onActionClick = { actionIndex ->
item.actions.getOrNull(actionIndex)?.let { runAction(item, it) }
},
)
}
}
}
}