diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt index ec79fd73c4..1ca80fd7e8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt @@ -51,7 +51,7 @@ val reserveTimestampStyle = SpanStyle(color = Color.Transparent) val boldFont = SpanStyle(fontWeight = FontWeight.Medium) @Composable -fun messageTextColor(): Color = MaterialTheme.colors.onSurface.copy(alpha = if (MaterialTheme.colors.isLight) 1f else 0.87f) +fun messageTextColor(): Color = MaterialTheme.colors.onSurface.copy(alpha = if (MaterialTheme.colors.isLight) 1f else 0.95f) fun appendSender(b: AnnotatedString.Builder, sender: String?, senderBold: Boolean) { if (sender != null) { diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-100.png index 53a05f4a0c..36f713afc1 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-140.png b/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-140.png index a5b4ee678c..ba4915f081 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-140.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-140.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-150.png b/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-150.png index 3cc4b8c1f4..fb8627098c 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-150.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ChatItemView.PreviewChatItemView-dark-font-150.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewChatLayout-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewChatLayout-dark-font-100.png index 7e24a89508..df3d05d551 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewChatLayout-dark-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewChatLayout-dark-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewGroupChatLayout-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewGroupChatLayout-dark-font-100.png index 3689d34fbb..605698edd9 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewGroupChatLayout-dark-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ChatView.PreviewGroupChatLayout-dark-font-100.png differ diff --git a/apps/multiplatform/common/src/desktopTest/snapshots/ContextItemView.PreviewContextItemView-dark-font-100.png b/apps/multiplatform/common/src/desktopTest/snapshots/ContextItemView.PreviewContextItemView-dark-font-100.png index b76f86fa07..6ee00eb81b 100644 Binary files a/apps/multiplatform/common/src/desktopTest/snapshots/ContextItemView.PreviewContextItemView-dark-font-100.png and b/apps/multiplatform/common/src/desktopTest/snapshots/ContextItemView.PreviewContextItemView-dark-font-100.png differ diff --git a/apps/multiplatform/spec/client/chat-view.md b/apps/multiplatform/spec/client/chat-view.md index a6e39c01b0..18b458da62 100644 --- a/apps/multiplatform/spec/client/chat-view.md +++ b/apps/multiplatform/spec/client/chat-view.md @@ -188,7 +188,7 @@ When `LocalTerminalLayout.current` is true, `ChatItemView` and the item views it - [`CIMarkdownText`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt#L481): top padding `terminalTop` (required): 0 below the sender row, a header, the quote, a voice message or a file, `TERMINAL_BLOCK_BOTTOM_GAP` below an image, video, link preview or chat link card; plus [`terminalHeadingGap(item)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TerminalItemView.kt#L121-L122), `TERMINAL_LEVEL_STEP` when the text starts with a heading and is not at the top of the item (below a quote, a header or a block); no bottom padding. Its text style: `body1` in `messageTextColor()`, merged with `terminalTextStyle` at the `body1` size. The default style of `MarkdownText` is classic and is used outside the rows (the compose context preview). - Code block cards, table cards and rules of `MarkdownBlocks`: `TERMINAL_BLOCK_TOP_GAP` above, `TERMINAL_BLOCK_BOTTOM_GAP` below, except the last block of a message without a meta line (`marksLastBlock`; a live message has the meta line with the typing indicator, so its last block keeps the gap). That block has no bottom margin and is marked with `messageLastBlock()`: `TERMINAL_BLOCK_BOTTOM_GAP` is added below it when the time stays beside it, and the time moved under it is `TERMINAL_META_GAP` below its edge (section 10). In a message of rules only, the rules are at least as wide as the copy of the time (`ruleMinWidth` of `MarkdownBlocks`, measured by `MarkdownText`; user, D31), as in classic, where the meta line sets the width; the time stays beside them unless a wider quote or header widens the item so that the rule leaves no room for the time. - Text blocks of [`MarkdownBlocks`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkdownBlocks.kt#L59): a paragraph break, and a heading that does not start the text, add `TERMINAL_LEVEL_STEP` (4.75sp) (classic: half the line height, before a paragraph break only); above a heading that starts the text, the gap is added by `terminalRowGap` or `terminalHeadingGap`. A block after a heading, other than a heading, has `TERMINAL_HEADING_BOTTOM_GAP` (half of `TERMINAL_LEVEL_STEP`, 2.375sp) above it instead, so a heading is nearer to its own text than to the text above it. -- Text styles: [`terminalTextStyle(fontSize, color)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TerminalItemView.kt#L78-L84) sets the line height to font size × `TERMINAL_LINE_HEIGHT_RATIO` (22/16) × f(w) and `LineHeightStyle(Proportional, Trim.None)`. Every line takes the full line height, one-line texts included. The space added to a line is split in the ratio of the font's ascent to its descent. On desktop each run of a line is given its own line box; with this alignment, runs of different sizes (the 12sp time in the copy of the meta) are placed on one baseline in a line of the body line height. f(w) = 1 − `TERMINAL_LINE_HEIGHT_CONTRAST_SLOPE` (0.1515) × (1 − w); w: the L* contrast of the text colour against `MaterialTheme.colors.background`, divided by that of `messageTextColor()`, limited to 0…1, and 1 when the message text has no contrast. Used for message text and captions (the style of `CIMarkdownText`), Markdown quotes (text colour at 70%), code, headings (body line height × scale), quote text and the quote's sender (in `body1`, Inter, in terminal rows), the sender name and role, event texts ([`chatEventTextStyle(base)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt#L22-L28): 12sp, `secondary`, merged into `base`, by default `LocalTextStyle.current`; classic: `base`; in both layouts the text direction is left unspecified, which Compose resolves to the layout direction, so that an event starting with a name in the opposite direction keeps the copy of the time at the end of the row), header texts and the time (classic: unchanged). For names and the white time over media (`lineHeightColor` of `CIMetaView`), f is computed with the message text colour. Line heights in the light and dark themes: message text 22sp, quote text 19.6sp and 20.1sp, event text and time 15.1sp and 15.5sp. +- Text styles: [`terminalTextStyle(fontSize, color)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TerminalItemView.kt#L78-L84) sets the line height to font size × `TERMINAL_LINE_HEIGHT_RATIO` (22/16) × f(w) and `LineHeightStyle(Proportional, Trim.None)`. Every line takes the full line height, one-line texts included. The space added to a line is split in the ratio of the font's ascent to its descent. On desktop each run of a line is given its own line box; with this alignment, runs of different sizes (the 12sp time in the copy of the meta) are placed on one baseline in a line of the body line height. f(w) = 1 − `TERMINAL_LINE_HEIGHT_CONTRAST_SLOPE` (0.1515) × (1 − w); w: the L* contrast of the text colour against `MaterialTheme.colors.background`, divided by that of `messageTextColor()`, limited to 0…1, and 1 when the message text has no contrast. Used for message text and captions (the style of `CIMarkdownText`), Markdown quotes (text colour at 70%), code, headings (body line height × scale), quote text and the quote's sender (in `body1`, Inter, in terminal rows), the sender name and role, event texts ([`chatEventTextStyle(base)`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt#L22-L28): 12sp, `secondary`, merged into `base`, by default `LocalTextStyle.current`; classic: `base`; in both layouts the text direction is left unspecified, which Compose resolves to the layout direction, so that an event starting with a name in the opposite direction keeps the copy of the time at the end of the row), header texts and the time (classic: unchanged). For names and the white time over media (`lineHeightColor` of `CIMetaView`), f is computed with the message text colour. Line heights in the light and dark themes: message text 22sp, quote text 19.6sp and 19.9sp, event text and time 15.1sp and 15.4sp. - Headings: body size × `TERMINAL_HEADING_RATIO` (1.3125) to the power 2, 1, 0 for levels 1, 2, 3, 27.6, 21 and 16sp for 16sp text; levels 4–6 also at the body size; all bold (classic: 1.5, 1.3, 1.15); line heights scaled the same. `Modifier.headingBox` lays a heading out with the space of a body line above its capitals and below its last baseline (Inter cap height 2048/2816 em). It uses the px ratio of the body and heading font sizes: Android scales font sizes non-linearly from font scale 1.03 and, above 1.05, sets line heights in proportion to the font size in px, so the capitals are placed as body capitals at every font scale (within 1px at 1, 1.15 and 1.3). - Files: `TERMINAL_FILE_TOP_GAP` (2sp) above the file row, no padding below, no padding of its icon. Voice messages drawn by `FramedItemView` (with a caption, a quote or a header; `hasText = true`): the layout of a voice message drawn alone (4sp above, 6sp below, its indicator), without its own time; no padding below the item. - Emoji-only items, also under a quote or a header: `TERMINAL_EMOJI_TOP_GAP` (4.25sp) above, `TERMINAL_EMOJI_BOTTOM_GAP` (1.5sp) below (classic: 8dp; under a quote or header 6dp above, 8dp below). Under a quote or a header the emoji is not stretched to the item width, so its reported last line is the emoji itself; `PriorityLayout` places it at the start edge in both directions. @@ -205,7 +205,7 @@ level 4 last line of a message → next sender name; above and below an event - Time and status: placed at the end of the row by [`ItemWithMeta`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TerminalItemView.kt#L200-L214), the baseline of the time on the last baseline of the content (section 10). Message text ends with [`terminalReserveSpaceForMeta`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt#L227-L270): a transparent copy of the meta joined to the last word by no-break spaces, in `FontWeight.Normal`. The copy starts with a word joiner (U+2060), which forbids a line break before the copy, also after the three spaces of an idle live message (`noTyping`), so the time is not moved alone to the next line. The direction mark of its paragraph follows (U+200E left-to-right, U+200F right-to-left), so that the bidirectional algorithm places the copy at the end of the line, also after words in the other direction on a wrapped last line. Its spaces and texts are appended by [`reserveSpaceForMeta`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt#L145-L225), which has the conditions of `CIMetaText`, except that the timer is reserved for every timed message, and returns the classic string unchanged, through span parameters: icon spaces and the spaces between them are no-break spaces at the `body1` size in dp (16dp converted to sp with the density, so that they keep their width at every font scale, as the icons do; also after a heading and in event text), 18dp for each icon, wider than a 12dp icon with its spacer (15dp or 16dp); after the 17dp icons one more space is added (22.5dp against 21dp): the sent and received status icons (`tallStatusIcon`, also used by `CIMetaText`) and the via-proxy icon; the time and the timer text of a disappearing message are at 12sp. The copy includes the file encryption and signature icons (`encrypted` and `signedFileVerified` of `MarkdownText`, passed by `CIMarkdownText` from the item). When the last word and the copy do not fit in the line, the last word moves to the next line with the time. Text whose paragraph direction (`textLayoutDirection`: right-to-left when the first 50 characters of the message text hold a right-to-left character; all blocks and lines of a message take it) differs from the outer direction (`parentDirection`, passed by the blocks and by the meta line of a live message; otherwise `LocalLayoutDirection`) ends with a line break instead of the copy, so the time is placed on an added empty line. Emoji, and the last code block, table or rule of a message, report their width (`Modifier.messageLastLine()`, `Modifier.messageLastBlock()`); the time moves under them when the row leaves no room for it beside them. The meta line with the time is drawn under such a block only for live messages, for the typing indicator. File text keeps the space reserved for the time at the end of its size line. Images and videos without a caption (`captionlessMedia`) keep the meta in `secondary` beside the media when it fits in the row, otherwise the meta is drawn over the bottom end of the media in white, as in the classic layout (`overlayMeta`). Event items are wrapped in [`ChatEventWithMeta`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIEventView.kt#L35-L48), which passes the separator and the end of the event text apart (`separator`, `ts`): in the classic layout two spaces and the time without a style, in the terminal layout three transparent no-break spaces in `chatEventStyle` and the transparent `terminalReserveSpaceForMeta` of the meta without status and edit icons, so the last word of the event text is moved to the next line with the time when they do not fit. Each view puts the separator where the classic layout has two spaces (user, D28): after the event text, or in the "Open" and "Accept" links, so the classic output is unchanged. - Group invitations, deleted items and error items (`IntegrityErrorItemView`, `CIRcvDecryptionError`) keep their rounded coloured card. -Both layouts: `MarkdownText` draws message text in [`messageTextColor()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt#L54), `onSurface` at 87% in dark themes. +Both layouts: `MarkdownText` draws message text in [`messageTextColor()`](../../common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt#L54), `onSurface` at 95% in dark themes. --- diff --git a/plans/2026-10-13-flat-chat-layout-ui.md b/plans/2026-10-13-flat-chat-layout-ui.md index 6cd111acb9..5426701de8 100644 --- a/plans/2026-10-13-flat-chat-layout-ui.md +++ b/plans/2026-10-13-flat-chat-layout-ui.md @@ -51,12 +51,12 @@ captions laid out as the text of a close follow-up message: full row wi rule-only message rules as wide as the copy of the time; the time beside them unless a wider quote or header widens the rule (D31) item clip none, so headings and emoji are drawn whole cards link preview and chat link in the quote colour, message corner radius -message text onSurface at 87% in dark themes, both layouts +message text onSurface at 95% in dark themes, both layouts (user, 2026-10-29) line height font size × 22/16 × f(w): message text, captions, quotes, code, headings, sender row, headers, event text, time (D1) cards, files, voice and calls: classic until their redesign f(w) = 1 − 0.1515 × (1 − w); w: L* contrast of the text colour divided by that of the message text, 0…1; names and the white time: f = 1 full line height for every line, one-line texts included (D7); extra space split by ascent and descent (Proportional, Trim.None) - slope of f(w) set by the quote (D2); light theme: message 22sp, quote 19.6sp, event text and time 15.1sp; dark theme: quote 20.1sp + slope of f(w) set by the quote (D2); light theme: message 22sp, quote 19.6sp, event text and time 15.1sp; dark theme: quote 19.9sp spacing level 1 lines of a quote spacing level 2 lines of a message; messages of one sender within 60 s; blocks of a message against the text next to them spacing level 3 messages of one sender 60 s or more apart; sender name → message or header; paragraph break; above a heading, also at the start of a message (D9b)