Files
simplex-chat/apps
Evgeny df5ea3d460 android, desktop: new settings section design (#6777)
* android, desktop: new settings section design

* Section facelift: LIGHT canvas swap, equal padding, 2dp item dividers

- LIGHT canvas (themedBackground) now paints the off-white formula
  (bg.mixWith(onBackground, 0.95f)) so white cards read as raised.
  DARK/BLACK keep palette bg (cards already raised via founder's
  formula in Section.kt). SIMPLEX keeps its gradient.
- Section cards in LIGHT switch from formula to pure white via
  Color.White. DARK/BLACK keep the formula, unchanged.
- Section card horizontal padding equalized to 16dp on outer + inner
  for clean canvas-edge alignment. extraPadding (icon-indented rows)
  keeps DEFAULT_PADDING * 1.7f.
- 2dp dividers between rows inside section cards, color matches the
  per-theme canvas (SIMPLEX uses gradient bottom stop). Implemented via
  Modifier.drawBehind on each SectionItemView, gated by a private
  LocalInSectionCard CompositionLocal set true only by SectionView's
  inner Column — standalone SectionItemView usage (alerts, pickers)
  stays unaffected. Single canvas helper canvasColorForCurrentTheme()
  in Theme.kt is the source of truth for both canvas paint and divider
  color.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: paint item divider on top of clickable's hover indication

Previously sectionItemDivider() was inside the modifier val before
clickable, so the hover background drew over it inconsistently — on
hover the row's content area got a tinted overlay while the 2dp
divider area stayed at canvas color, creating visible contrast that
read as a "dark line below hovered row".

Moving the modifier to the end of the chain (after clickable+padding)
makes drawBehind paint after the hover indication, so the divider
color is consistently #F2F2F2-ish regardless of hover state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: trim section item horizontal padding to 15dp

CARD_PADDING (16dp) still drives outer card margin from screen edge.
Item content inside the card now uses CARD_ITEM_PADDING = CARD_PADDING - 1.dp,
giving the row text a slightly tighter horizontal inset that reads
better at the current card width.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Appearance: drop redundant 10dp spacer between Apply-to row and wallpaper preview

Before section facelift the spacer separated the Apply-to row from the
wallpaper preview block visually. With the new 2dp item divider drawing
under the Apply-to row that separation is already provided, and the spacer
leaves an awkward white gap between the divider and the preview.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: repurpose SectionDivider() as explicit 2dp canvas-color line; use in Appearance themes card

SectionDivider() composable had 0 callsites and used Material Divider
with horizontal inset (unused legacy). Repurposed to draw a 2dp
canvas-color Box matching the auto-divider style used by
SectionItemView, gated by LocalInSectionCard so it no-ops outside a
section card.

Use it in Appearance themes card between WallpaperPresetSelector
(custom composable, not a SectionItemView, so no auto-divider) and the
following content (Remove image / Color mode / Dark mode), providing
the visual separator the user expects between the theme grid and the
rows below it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Appearance: symmetric vertical padding around profile avatar row

ProfileImageSection's Row had Modifier.padding(top = 10.dp), giving 10dp
above the avatar and 0dp below — visibly asymmetric inside the card.
Changed to vertical = 10.dp so top and bottom padding match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* NetworkAndServers: move messages card footer/spacer out of SectionView

Before PR #6777 SectionView had no card chrome, so SectionTextFooter and
SectionDividerSpaced placed inside its content lambda rendered as plain
inline content. After the card chrome was added, the same code rendered
the footer caption and the spacer INSIDE the white card area, producing
an unwanted gap (and visible auto-divider tail) under Advanced network
settings.

Move both out of the SectionView lambda so the footer reads as a caption
below the card (iOS-style) and the spacer separates this card from the
next one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* DeveloperView: move card footer/bottom-spacer out of SectionView lambdas

Same pre-card-chrome pattern as NetworkAndServers: SectionTextFooter
("Show: Database IDs and Transport isolation...") and SectionBottomSpacer
were inside SectionView lambdas, so after PR #6777 added card chrome they
rendered inside the white card area — the footer caption sat inside the
first card and the 48dp bottom spacer appeared as an empty row at the
end of the deprecated-options card (after SimpleX links).

Move both out of the SectionView lambda so the footer reads as a caption
below the first card and the bottom spacer adds safe-area room after the
deprecated-options card (not inside it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ChatInfoView: move chat-ttl footer caption out of SectionView lambda

Same pre-card-chrome pattern: SectionTextFooter("Delete chat messages
from your device.") was inside the ChatTTLOption SectionView lambda, so
after PR #6777 added card chrome it rendered inside the card. Move it
out so the caption sits below the card iOS-style.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ChatWallpaperEditor: wrap loose params in SectionView cards

In ChatInfo > Chat theme screen the wallpaper preset selector, the
wallpaper setup controls, the reset/set-default buttons and the
"Apply to" mode dropdown were rendered as loose composables on a gray
canvas — no card chrome, inconsistent with the rest of Appearance.

Wrap them in SectionView so they read as raised iOS-style cards:
- wallpaper preset selector + setup view → one card
- reset-to-global + set-default buttons → one card
- (advanced mode) Apply-to dropdown → one card
- (collapsed mode) Advanced-settings button → one card

CustomizeThemeColorsSection and ImportExportThemeSection were already
SectionView-wrapped and remain unchanged. UserWallpaperEditor (sister
function with similar layout, lines 28-220) is intentionally left
alone — user reported only the chat-theme entry point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* GroupChatInfoView: render group members inside the same SectionView card as owner

Previously the members card showed only the current user (owner) and the
add-members button — the actual group members were rendered as separate
LazyColumn items() OUTSIDE the SectionView, so they sat on the gray
canvas without card chrome. Visually inconsistent: owner in a card,
everyone else floating.

Move filteredMembers.value.forEach { ... } INSIDE the SectionView lambda
so every member row is part of the same card as the owner. Drop the
explicit Divider() call (auto-divider handles it now). Move remember
key to member.groupMemberId so per-member state survives reorders.

Trade-off: lazy rendering of member rows is replaced with eager
composition inside a Column. For typical groups (<100 members) this is
imperceptible; very large groups may compose slower on open. Watching
for reports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ChatInfoView: move E2E encryption card spacer out of SectionView lambda

Same pre-card-chrome pattern: SectionDividerSpaced was inside the
single-row SectionView around the InfoRow, so after PR #6777 added
card chrome it rendered as a white gap inside the card (under the
auto-divider on the InfoRow), producing the "extra divider + gap"
the user reported.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ServersSummaryView: wrap Message reception sections in SectionView card

SubscriptionsSectionView and SMPSubscriptionsSection both rendered their
InfoRows + control item in a plain Column without card chrome — so on
the Servers info screen the "Message reception" section title sat above
loose rows on the gray canvas (no card), inconsistent with the rest of
the screen. Wrap the inner Column in SectionView so the rows get the
raised iOS-style card look. The custom header Row (title + subscription
status indicator) stays outside the card so the icon stays inline with
the title text.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Appearance: add missing SectionDivider import

f922d8fc introduced SectionDivider() call in the themes card but forgot
to add the per-symbol import. SectionView/SectionDividerSpaced etc. in
this codebase are imported individually (Section.kt declares them at
top level, not inside a package), so SectionDivider needs its own
import line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: fix item divider position — paint via drawWithContent at full row bottom

8050676b moved sectionItemDivider() AFTER clickable.padding() in the
modifier chain to make the line paint on top of clickable's hover
indication. Side effect: drawBehind then saw the size of the
padding-reduced content area, not the full row, so dividers rendered
15dp ABOVE the actual row bottom (in the middle of the row's bottom
padding zone) instead of at the row edge between adjacent items.

Fix: keep sectionItemDivider() in the modifier val BEFORE clickable/
padding (so size = full row outer bounds) AND switch from drawBehind
to drawWithContent { drawContent(); drawLine(...) } so the line is
painted AFTER the chain's content + hover indication draw. Both
goals satisfied: divider sits at the true row bottom AND paints on
top of hover overlay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: bump hover ripple alpha to 0.08 on LIGHT theme for visibility against canvas

Default Compose Material 1 RippleTheme uses hoveredAlpha=0.04 for LIGHT,
producing a Black·0.04 overlay (~#F5F5F5) on white cards — visually 3
units per channel away from the off-white canvas (~#F2F2F2), so the
hover state blends into the canvas and the row looks unfocused.

Add a section-local SectionRippleTheme that mirrors Material's defaults
for everything except hoveredAlpha on LIGHT (raised to 0.08 → ~#EBEBEB
overlay, ~7 units delta from canvas — visibly distinct). Dark themes
keep Material defaults since their hover contrast is already adequate.

Provided via CompositionLocalProvider in all three SectionView variants
alongside LocalInSectionCard, so it scopes only to section card items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ProtocolServerView: pad CustomServer address field inside its card

The TextEditor (144dp tall input) sat flush against the top and bottom
edges of its containing SectionView card on the New server screen.
Pass padding = PaddingValues(vertical = DEFAULT_PADDING_HALF) to the
SectionView so the field gets 10dp of breathing room top and bottom
inside the card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* NetworkAndServers: move ConditionsButton into operators card; wrap Save servers in its own card

- "Review conditions" (ConditionsButton — a bare SectionItemView) was
  rendered between the operators card and the messages card on the
  canvas without card chrome. Move it inside the operators SectionView
  lambda after the operator rows, so it shares the card and gets a
  2dp auto-divider above it separating it from the operator list.
- "Save servers" (a bare SectionItemView further down) is now wrapped
  in its own SectionView so it reads as a single-item card matching
  the iOS-style facelift of the rest of the screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Appearance: fix Transparency truncation and SettingsActionItem horizontal padding

Two layout regressions from earlier facelift commits where slider/item
math assumed DEFAULT_PADDING (20dp) for inner card padding, but the
facelift uses CARD_PADDING (16dp outer) + CARD_ITEM_PADDING (15dp inner)
= 31dp per side instead of 20dp.

- Slider widthIn calc in AppToolbarsSection and MessageShapeSection
  used (maxWidth - DEFAULT_PADDING * 2) so the slider was ~22dp wider
  than it should be, shrinking the label Box (weight 1f) and clipping
  "Transparency" to "Transparenc". Switched to
  (CARD_PADDING + CARD_ITEM_PADDING) * 2.
- SettingsActionItemWithContent explicitly passed
  PaddingValues(horizontal = DEFAULT_PADDING) to its SectionItemView,
  overriding the new CARD_ITEM_PADDING default. That made any row using
  SettingsPreferenceItem/SettingsActionItem sit 5dp further inset than
  rows using plain SectionItemViewWithoutMinPadding — visible as a left
  indent on "Tail" relative to "Corner". Replaced with CARD_ITEM_PADDING
  so it matches.

Removed `private` from CARD_PADDING and CARD_ITEM_PADDING in Section.kt
to allow imports from other files (used the same way as SectionView etc.
are imported individually).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Appearance + Customize theme: bring loose items into cards, drop spurious spacer

Four small fixes on the same theme-related screens:

- Move "Customize theme" SectionItemView INSIDE the THEMES SectionView
  in AppearanceView so it sits in the same card as Color mode / Dark
  mode colors with an auto-divider above it.
- Wrap WallpaperPresetSelector (theme slots + chat preview) and the
  conditional Remove-image button in CustomizeThemeView with a
  SectionView so they read as a card, matching the Appearance themes
  card pattern. Add a SectionDividerSpaced after.
- Drop SectionSpacer() that sat between the Wallpaper tint row and the
  Sent message row inside WallpaperSetupView — auto-divider on the
  Wallpaper tint SectionItemView already provides separation; the
  30dp spacer rendered as extra empty padding inside the card.
- Wrap the Reset colors action in a single-item SectionView so it
  reads as its own card, matching the export/import card below.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ServersSummaryView: wrap "Showing info for" dropdown in SectionView card

The user-selection ExposedDropDownSettingRow at the top of the
servers info screen was rendered loose on the canvas with no card
chrome. Wrap in SectionView so it reads as a card matching the rest
of the screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* GroupChatInfoView: move chat-ttl footer caption out of SectionView lambda

Same pre-card-chrome pattern as ChatInfoView (fixed in b1a1dad8):
SectionTextFooter("Delete chat messages from your device.") sat inside
the SectionView around ChatTTLOption, so it rendered inside the white
card after PR #6777 added card chrome. Move it out so the caption sits
below the card iOS-style.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* NewChatSheet: render filtered contact list inside SectionView card

The "Contacts" header was a SectionView with empty content lambda, and
the actual contact rows were rendered as separate LazyColumn items
OUTSIDE the SectionView — so they sat on canvas without card chrome.

Move filteredContactChats.forEachIndexed { ContactListNavLinkView }
INSIDE the SectionView lambda in both OneHandLazyColumn and
NonOneHandLazyColumn so the contacts read as a single card matching
the iOS-style facelift.

Same trade-off as GroupChatInfoView members fix (fa29bb7a): lazy
rendering of contact rows replaced with eager composition inside a
Column. For typical contact lists (<100) imperceptible; very long
lists may compose slower on open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* AddGroupView/AddChannelView: wrap action buttons + toggles in SectionView card

In Create group and Create public channel screens the action buttons
(Create / Configure relays) and incognito toggle were rendered as loose
SectionItemViews on the gray canvas with no card chrome. Wrap them in
SectionView so they read as a single card matching the iOS-style facelift.
The display-name input above and the descriptive footer below stay
outside the card (text input keeps its own padding, footer reads as
caption).

Added missing `import SectionView` in AddGroupView.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* TagListView: wrap Add/Save list button in SectionView card

The "Add to list" / "Save list" action button in TagListEditor (opened
from chatlist "+" Add list) was a loose SectionItemView on the canvas
with no card chrome. Wrap in SectionView so it reads as a single-item
card. ChatTagInput stays as a form field above.

Added missing `import SectionView`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* UserAddressView: move "contacts remain connected" footer out; pad welcome message field; wrap Save in card

Three SimpleX address fixes:
- "Your contacts will remain connected" SectionTextFooter moved out of
  the DeleteAddressButton SectionView (was rendering inside the card).
- Address settings > welcome message field gets 10dp vertical
  contentPadding on its SectionView so the TextEditor doesn't sit flush
  against the card top/bottom.
- Address settings > Save action wrapped in its own SectionView so it
  reads as a single-item card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* WelcomeView: wrap Create profile action button in SectionView card

The Create profile action SettingsActionItem at the bottom of the
Create profile screen was loose on canvas. Wrap in SectionView so it
reads as a single-item card matching the iOS-style facelift. The two
SectionTextFooter captions below stay outside the card.

Added missing `import SectionView`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ConnectMobileView: move footer + spacer out of "this device name" SectionView lambda

Same pre-card-chrome pattern: SectionTextFooter and SectionDividerSpaced
were inside the SectionView around DeviceNameField + multicast toggle,
so they rendered inside the white card after PR #6777 — visible as an
extra empty padding below the "Discoverable via local network" toggle
(the SectionDividerSpaced 10dp Spacer inside the card).

Move both outside the SectionView so the footer reads as caption below
the card and the spacer separates this card from the next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: suppress RippleTheme deprecation warnings (build was treating warnings as errors)

`androidx.compose.material.ripple.RippleTheme` and `LocalRippleTheme`
were deprecated in newer Compose Material in favor of the modern
Indication APIs. Our SectionRippleTheme override (a5b199660) hit those
deprecations and the project's Kotlin compiler flags treat warnings as
errors, breaking the build.

Add `@file:Suppress("DEPRECATION")` to Section.kt — narrow file-level
scope. Modern Indication-based ripple migration is a separate, larger
concern; suppress for now so the section hover-alpha override keeps
working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: also suppress DEPRECATION_ERROR — RippleTheme is @Deprecated(level=ERROR)

Previous attempt (4bf981a6b) suppressed DEPRECATION but the Compose
library deprecated RippleTheme with level=DeprecationLevel.ERROR, which
requires the DEPRECATION_ERROR suppression key instead. Add both so
either severity is covered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: replace deprecated RippleTheme override with modern Modifier.hoverable + background

Drop SectionRippleTheme/RippleAlpha/LocalRippleTheme machinery (deprecated
in newer Compose Material, would not compile with the project's warnings-
as-errors policy without DEPRECATION_ERROR suppress, which is a code
smell). Replace with a Modifier.hoverable + Modifier.background pattern —
the modern Compose-native way to apply a hover overlay:

- New private @Composable Modifier.sectionItemHover() that:
  - returns Modifier as-is outside SectionView card (LocalInSectionCard = false)
  - inside a card, attaches its own MutableInteractionSource via .hoverable()
    and paints a transparent or onBackground@0.08-alpha background based on
    collectIsHoveredAsState

- Applied alongside .sectionItemDivider() in each SectionItemView modifier
  chain. Click ripple keeps coming from Modifier.clickable's own indication
  (default ripple, no changes there).

- Drop @file:Suppress deprecation lines; drop SectionRippleTheme object;
  drop ripple imports; drop LocalRippleTheme from CompositionLocalProvider
  calls in three SectionView variants.

Visual result identical to the previous attempt (hovered row gets a visible
gray overlay on LIGHT canvas), no deprecated APIs, no warnings-as-errors
fight. Click ripple unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: suppress sectionItemHover on disabled SectionItemView

sectionItemHover was applied unconditionally inside section cards, so a
disabled row would still show the hover overlay on mouseover —
misleading: the visible interactive feedback contradicts the disabled
state (no click reaction).

Add `enabled: Boolean = true` parameter; the helper now returns `this`
unchanged when `enabled = false`. The 3 SectionItemView family
functions that own a modifier chain pass `enabled = !disabled`.
SectionItemViewWithoutMinPadding inherits through SectionItemView delegation.

Non-clickable info rows (click == null but disabled = false) still get
the hover overlay — that's intentional cursor feedback matching iOS
Settings behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: lighten section item hover overlay from 0.08 to 0.05 alpha

0.08 read as too dark on white cards. Original Compose default 0.04
blended with the off-white canvas (#F2F2F2 vs ~#F5F5F5). 0.05 is the
midpoint — still visibly distinct from canvas (~#F2F2F2 canvas vs
~#F3F3F3 hover on white card) but no longer reads as a heavy box.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* RTCServers: wrap Configure ICE servers toggle in SectionView card

Your ICE servers screen had its Configure-ICE toggle and the description
text / editor / read-only display all directly in a raw Column with no
card chrome. Wrap the toggle row in SectionView so it reads as a card
matching the iOS-style facelift. The description text and the
TextEditor / read-only Surface stay in the same loose Column below
(they're a form/display block, not a settings row).

Removed the explicit `padding = PaddingValues()` on the
SectionItemViewSpaceBetween — inside SectionView it inherits
CARD_ITEM_PADDING by default which is what we want now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* NetworkAndServers: rewrite UseSocksProxySwitch via SettingsActionItemWithContent

UseSocksProxySwitch was a custom Row with hard-coded horizontal padding
of DEFAULT_PADDING (20dp) — but its neighbours on the messages card are
SettingsActionItem rows that go through SectionItemView with the new
CARD_ITEM_PADDING (15dp). 5dp icon misalignment between the SOCKS
toggle row and the rest, plus no auto-divider underneath since it
wasn't a SectionItemView.

Replace the custom Row with SettingsActionItemWithContent — same
icon + label + DefaultSwitch shape, now wrapped in SectionItemView so
it shares padding and auto-divider with siblings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SocksProxySettings: move section text footers out of SectionView lambdas

Same pre-card-chrome pattern as elsewhere: two SectionTextFooter calls
("Disable onion hosts when not supported" and the proxy-auth footer)
were inside their SectionView lambdas in SocksProxySettings, so after
the card chrome was added they rendered inside the white cards as
inline content. Move both out so they read as captions below the
corresponding cards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* SocksProxySettings: split UseOnionHosts so the dynamic description footer renders outside the card

UseOnionHosts wrapped its ExposedDropDownSettingRow and a dynamic
SectionTextFooter ("Onion hosts will be used when available." / similar)
in a Column, so when UseOnionHosts was called inside a SectionView
lambda the footer rendered inside the white card.

Split into two composables:
- UseOnionHosts — only the dropdown row (no longer wraps in Column)
- UseOnionHostsDescription — only the dynamic SectionTextFooter,
  called separately by the caller

Shared `onionHostsValues` is now a private @Composable val accessible
to both. In SocksProxySettings, UseOnionHostsDescription is now placed
AFTER the SectionView block (alongside the existing
"Disable onion hosts when not supported" caption) so the dynamic
description reads as a caption below the card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: suppress hover on rows whose action is an inline control (switch/dropdown)

sectionItemHover used to show on every row inside a section card. But
rows where the action is an inline control (switch via PreferenceToggle,
dropdown via ExposedDropDownSettingRow) are not "interactive as a row"
— the user has to hit the actual control, not the whole row. Showing
hover on the whole row was misleading.

Add `clickable: Boolean = true` param to sectionItemHover; suppress when
false. SectionItemView and SectionItemViewSpaceBetween pass
`clickable = click != null`. SectionItemViewLongClickable keeps the
default (its click is non-nullable, always interactive).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section + Theme: lighten LIGHT canvas to 0.97; revert custom hover overlay

User feedback: the off-white canvas at 0.95 (#F2F2F2) read as too
dark. Two coordinated changes:

- canvasColorForCurrentTheme LIGHT branch: 0.95f → 0.97f. Canvas now
  #F7F7F7 (3% darker than white, was 5%). Still distinct from pure
  white card but lighter.

- Drop the custom sectionItemHover Modifier helper (and its hoverable
  + InteractionSource + background machinery). The reason for the
  custom hover was that the default Material 0.04-alpha ripple hover
  (#F5F5F5 on white card) blended with the old #F2F2F2 canvas. With
  the lighter canvas at #F7F7F7 the default hover #F5F5F5 is now
  visibly darker than canvas (2 units delta) — visible enough at
  Material default without our custom override.

Removed unused MutableInteractionSource and collectIsHoveredAsState
imports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: bump section item minHeight by 6dp (50 → 56)

User asked for taller settings rows. Bump the default minHeight in
all four SectionItemView family functions from DEFAULT_MIN_SECTION_ITEM_HEIGHT
(50dp) to DEFAULT_MIN_SECTION_ITEM_HEIGHT + 6.dp (56dp).

Scoped to SectionItemView callers only — does not touch the global
DEFAULT_MIN_SECTION_ITEM_HEIGHT constant, so non-section callers
(ChatItemInfoView, ComposeContextProfilePicker, TagListView,
UserPicker) keep the 50dp baseline.

Callers that pass explicit minHeight (e.g. 54dp in GroupChatInfoView
members) are unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Move SectionTextFooter / Spacer out of cards in 5 screens

Fixes from the user's verified list of misplaced footers/spacers:

- ChatInfoView: SimpleX address footer ("You can share this address
  with your contacts to let them connect with you.") moved out of the
  address SectionView lambda.
- GroupMemberInfoView: same string for member address.
- Appearance: SectionSpacer in the Image-wallpaper branch (after
  "Remove image" button) removed — it created 30dp empty padding
  inside the THEMES card only when a custom image was selected.
- NotificationsSettingsView: Xiaomi battery-optimization footer
  ("Xiaomi devices: please enable Autostart...") moved out of the
  notifications SectionView lambda (visible only on Xiaomi devices
  in Periodic/Service notification mode).
- ConnectMobileView: dropped the 20dp Spacer that sat inside the QR
  SectionView after the developer-tools "Share link" row — visible
  as extra padding below Share link inside the card.

Same pre-card-chrome pattern as other moves: helpers placed inside
SectionView lambdas before PR #6777 rendered fine when SectionView was
a plain Column; after card chrome they render inside the white card.
Moved them outside so footers read as captions and spacers actually
separate cards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Migrate views: move all SectionTextFooter / SectionSpacer out of SectionView lambdas

Same pre-card-chrome pattern as elsewhere. MigrateToDevice (4 footers)
and MigrateFromDevice (9 footers + 1 SectionSpacer in error view)
historically wrote captions and inter-card spacers inside their
SectionView content lambdas. After PR #6777 added card chrome these
rendered inside the white cards.

MigrateToDevice fixes (4 footers, one per sub-view):
- Confirm network settings footer
- Database init failed retry footer
- Archive import failed retry footer
- Passphrase entering dynamic footer

MigrateFromDevice fixes (9 footers + 1 SectionSpacer):
- ChatStopFailed view footer
- Passphrase confirmation footer
- Upload confirmation footer
- Upload failed retry footer
- Link shown view: archive-will-be-deleted + choose-migrate footers
- Finished view: 2 warning footers (must-not-use-two-devices,
  using-on-two-devices-breaks-encryption)
- Implicit SectionSpacer at ChatStopFailed view also moved out

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section + ChatListNavLink.android: align in-card chat row divider with desktop; canvas to 0.94

Two changes:

1) Theme.kt LIGHT canvas: 0.97f → 0.94f (#F0F0F0). User wants more
   contrast against cards. With Material's default 0.04-alpha hover
   (#F5F5F5) this puts hover LIGHTER than canvas by 5 units — unusual
   direction but it's the user's call; they'll evaluate visually.

2) ChatListNavLinkView.android: when rendered inside a SectionView card
   (e.g. contact list inside NewChatSheet after the forEach-into-card
   refactor), use SectionDivider() — same 2dp full-width canvas-color
   divider as desktop. Outside a card (main chat list), fall back to
   the original Material `Divider(Modifier.padding(horizontal = 8.dp))`
   so unchanged for that context.

3) LocalInSectionCard made `internal` so the android-specific file can
   read it. Same pattern as LocalAppColors etc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Section: bump section item minHeight by 2dp more (56 → 58)

* GroupWelcomeView: wrap message editor/preview and buttons in SectionView cards

* GroupLinkView: wrap action items below QR in SectionView card

* Section: align InfoRow / IndentedInfoRow horizontal padding with CARD_ITEM_PADDING

InfoRow defaulted to DEFAULT_PADDING (20dp), but card chrome adopted CARD_ITEM_PADDING (15dp) for SectionItemView and InfoRowTwoValues. Inside a card, rows of different kinds visibly jumped left/right. Bring InfoRow and its IndentedInfoRow variant onto the same baseline.

* ServersSummaryView: align Message reception header indent with other section headers

* ServersSummaryView: split SMP/XFTP server summary into separate top-level cards

The summary layouts wrapped Stats / Subscriptions / Sessions inside the outer Server address SectionView, which produced nested cards and pushed the Statistics 'Starting from...' footer inside a card. Unnest them so each section is its own card with proper spacing and the footer renders outside.

* CreateProfile: add vertical gap between profile fields and Create profile action card

* UserPicker: wrap menu options in SectionView cards (SecondSection + GlobalSettingsSection)

* UserPicker: gate SectionView card wrap on Android only

Desktop UserPicker doesn't have a canvas background, so white cards on the white surface were invisible and the existing desktop divider above inactive users looked stray next to the SectionItemView mini-dividers.

* Revert "UserPicker: gate SectionView card wrap on Android only"

This reverts commit be365e55ae.

* UserPicker: use canvas color on desktop and split SecondSection around inactive-users grid

Desktop background was MaterialTheme.colors.surface (white) so the SectionView cards introduced earlier were invisible. Switch to canvasColorForCurrentTheme() to match Android.

Drop the explicit Divider above the inactive-users grid: split SecondSection into two SectionView cards with the avatar grid between them so the section dividers come from the cards themselves.

* ChatListNavLinkView.android: fix LocalInSectionCard import path

LocalInSectionCard is declared in Section.kt which has no package (root package), so it must be imported as 'import LocalInSectionCard', not as 'chat.simplex.common.views.helpers.LocalInSectionCard'.

* UserPicker: merge SecondSection + GlobalSettings into one card on portrait

Both Android and desktop portrait now show address, preferences, (desktop: inactive-users grid), profiles, link mobile / use from desktop, and settings inside a single SectionView card. Desktop landscape keeps the side-by-side two-card layout.

* ChatInfoImage: default placeholder icon color to secondary

secondaryVariant is near-white (#F1F2F6) and disappears against the gray canvas. Use the visible secondary tone instead so default avatars without a photo are legible on both card and canvas backgrounds.

* UserPicker: align item padding with Settings, add divider after inactive-users grid

UserPickerOptionRow no longer applies extraPadding on desktop, and the Settings row uses default SectionItemView padding instead of its own. Both now match the CARD_ITEM_PADDING used in Settings screens. After the inactive-users avatar grid in the unified card, paint a SectionDivider so it visually separates from Your chat profiles.

* ChatInfoImage: place default avatar one canvas-shade darker than the canvas

secondary (#8B8786) was too dark. Use background mixed with onBackground at 0.88 — same darkening recipe as canvasColorForCurrentTheme uses with 0.94, applied a step further. On LIGHT this lands near #E1E1E1: 15 units darker than the canvas, matching how the canvas sits 15 darker than white.

* Appearance: symmetric vertical padding around Font size and Zoom preview tiles

Both rows used Modifier.padding(top = 10.dp) so the tile hugged the bottom of the SectionView card. Switch to padding(vertical = 10.dp) to match the symmetric padding used by ProfileImageSection.

* ChatInfoImage: dark themes use secondaryVariant for default avatar to match UserPicker

The mixWith canvas-darkening formula only lands well in LIGHT. For DARK / BLACK / SIMPLEX, fall back to secondaryVariant, which UserPicker already uses for the active profile avatar — keeps placeholder avatars consistent across the app on dark themes.

* Section: tighten icon-to-text spacing in TextIconSpaced by 2dp

* ChatInfoImage: lighten LIGHT default avatar to halfway between white and canvas

* ChatInfoView: hide Servers section header when there is no server content

When both chatSubStatus and cStats are null, the SectionView body rendered as empty (zero-height card) but the SERVERS title still appeared, leaving an orphan header between E2E encryption and Clear chat. Gate the whole section on having at least one of the two.

* GroupLink / WelcomeMessage: use SectionDividerSpaced between adjacent cards

Three places had adjacent SectionView cards with no spacer (GroupLinkView QR + actions, WelcomeMessageView non-owner preview + copy), or used a one-off Spacer(8.dp) instead of the conventional helper (owner mode-button card). Replace with SectionDividerSpaced() so all between-card gaps live behind one helper.

* Chat info / Group info: use full spacing between two title-less cards

ChatInfoView (Contact prefs/Send receipts/Chat theme → Delete messages) and GroupChatInfoView (Member reports → Edit group profile) both used SectionDividerSpaced(maxBottomPadding = false) = 10dp between two cards that have neither header nor footer touching the gap, so the tight variant wasn't justified. Switch to the default 20dp.

* remove diff noise

* Sections: drop .uppercase() from all section / header titles

Source string resources are already in sentence case (e.g. "Profile images", "Message reception"). The .uppercase() calls forced them to ALL CAPS, which is the Android settings convention but conflicts with the iOS-style facelift. Remove the call everywhere so headers render as in the source.

* Section: shrink icon-to-text spacing to 5dp (-3dp from previous 8dp)

* Appearance: shrink ColorModeSwitcher tap target to keep UserPicker Settings row at standard 58dp height

* UserPicker.android: align profile boxes with menu card left edge (CARD_PADDING)

* ChatListNavLinkView.desktop: restore chat list dividers outside SectionView

Commit 633e0f414 made SectionDivider() a no-op outside SectionView card, which removed the desktop chat list dividers (the list is not wrapped in a SectionView). Mirror the Android conditional: SectionDivider in-card, padded Divider otherwise.

* UserPicker: restore SectionView wrap around desktop active-profile row

Commit 3a7118235 extracted the profile out of its original SectionView when wrapping the menu in its own card. Without the card chrome the profile shifted to the screen edge instead of sitting at CARD_PADDING like the menu below. Wrap it back in SectionView and add SectionDividerSpaced before the menu card.

* Strings: convert section-title resources from ALL CAPS to sentence case

26 section header strings used as SectionView titles (SETTINGS, CHAT DATABASE, HELP, SERVERS, etc.) were stored ALL CAPS in source. The .uppercase() removal commit did nothing for them. Convert the source values to sentence case with proper-noun preservation (SimpleX, SOCKS). LIVE and OK stay all-caps (status badge and button).

* UserPicker: add top spacer above active profile card and tighten its left padding

Card was flush against the sheet's top edge; add DEFAULT_PADDING spacer above. Left padding inside the card was 16dp while the avatar (60dp) sat in an 80dp minHeight row so visual top/bottom were ~10dp — bring start down to 10dp so the photo sits equidistant from card top, bottom and left.

* Section: bump card-title font size from 12sp to 14sp across all 3 SectionView variants

* ServersSummaryView: bump Message reception custom header to 14sp to match other card titles

* Sections: add SemiBold weight to card titles across all 4 places

* Sections: drop card-title weight from SemiBold to Medium (W500)

* ServersSummaryView: Message reception header bottom padding 5dp -> 8dp to match SectionView default

* Strings: convert group_info_section_title_num_members to sentence case

* Strings: convert settings_section_title_interface to sentence case (Interface)

* Revert UserPicker to pre-card-wraps state per founder's request

Restore UserPicker.kt and UserPicker.android.kt to their state at 43855ae07
(before commit 3a7118235 introduced SectionView wraps). The founder asked in
chat to keep UserPicker out of the cards facelift — undo all of our changes
to it, including the followup tweaks (avatar padding, divider above grid,
active-profile wrap, etc.) and the founder's own followup cleanup 23b0e41d8
which only existed to refactor our wraps.

* Section / Theme: extract sectionCardColor() helper

Three SectionView overloads were each computing the same cardColor inline:
if (CurrentColors.value.base == DefaultTheme.LIGHT) Color.White
else MaterialTheme.colors.background.mixWith(...). DRY violation paired with
the canvasColorForCurrentTheme() helper that already covers the canvas side
of the same theme split. Add a sectionCardColor() function in Theme.kt and
collapse the 3 inline formulas to one call.

* Theme: document why canvasColorForCurrentTheme reads CurrentColors.value directly

Reviewer asked why this helper uses CurrentColors.value.base instead of the
Compose MaterialTheme/CompositionLocal route. Reason is that the helper is
intentionally callable from both @Composable bodies and DrawScope (inside
sectionItemDivider's drawWithContent), and DrawScope can't invoke @Composable
getters. Add a paragraph to the doc-comment so future readers don't try to
'fix' it back to MaterialTheme.colors and break the divider draw path.

* ConnectMobileView: collapse double blank line left over from move-footer edit

* Sections: normalize redundant SectionDividerSpaced flag combinations

After founder simplified SectionDividerSpaced to one Spacer height (any flag
true -> DEFAULT_PADDING; both false -> DEFAULT_PADDING_HALF), many call sites
still pass combinations like (maxTopPadding = true) or
(maxTopPadding = true, maxBottomPadding = false) that all produce the same
20dp gap as the default. The flag names no longer match what they do —
reviewer flagged this as misleading.

Collapse all call sites to two canonical forms: SectionDividerSpaced() for
the 20dp gap, SectionDividerSpaced(maxBottomPadding = false) for the 10dp
tight gap. Behavior identical. Function signature kept (founder's API).

* NewChatSheet: render filtered contacts in search mode (regression fix)

Commit 3a9ece8d1 moved contacts forEach inside the if-branch and made the
else-branch fall back to NoFilteredContactsItem. That broke search: when
the user typed text and the filter returned non-empty results, the
if-condition (filtered.isNotEmpty() && searchText.isEmpty()) was false,
the else ran NoFilteredContactsItem, NoFilteredContactsItem's internal
guard saw a non-empty filter and rendered nothing — search results disappeared.

Restore three-way branching with when{}: header + contacts in card when
no search; contacts in plain card when search has matches; NoFilteredContactsItem
when filter is empty. Applied at both OneHandLazyColumn and the regular layout.

* GroupChatInfoView: keep Invite + owner in card, render members as lazy items

fa29bb7a7 put filteredMembers.value.forEach inside the same SectionView as
the Invite button and the owner row to get a unified card visual. That
sacrificed lazy rendering — all members composed at once, hurting big-group
scroll perf. Founder asked to bring lazy back.

Compromise: keep Invite + (search row) + owner row inside the SectionView
card (the 'hero' rows). Move the rest of the members out to a sibling
items(filteredMembers.value, key = { it.groupMemberId }) call in the
parent LazyColumn — bare SectionItemViewLongClickable rows below the card,
lazy-composed by LazyColumn.

* ChatInfoImage: LIGHT default avatar at midpoint of white card and gray canvas

Was at 0.91 mix (~#E8) — designed to sit 'below' the white card, but on the
~#F0 canvas it nearly blended (delta ~8). Switch to 0.97 mix (~#F7), which
is the geometric midpoint between #FF (white card) and ~#F0 (canvas) and so
sits at equal absolute contrast against either background.

* MemberProfileImage: use defaultProfileIconColor instead of secondaryVariant

MemberProfileImage hard-coded color = MaterialTheme.colors.secondaryVariant
as default, which is LightGray (#F1F2F6) on LIGHT — slightly bluish and
nearly invisible against the ~#F0F0F0 canvas. Reuse the defaultProfileIconColor()
helper so the LIGHT default matches the rest of the app (midpoint between
canvas and white card), and DARK themes keep their palette secondaryVariant.

defaultProfileIconColor() in ChatInfoImage.kt promoted from private to file-
level visibility so it can be referenced from GroupMemberInfoView.

* ProfileImage colors: split into card vs canvas variants

Revert defaultProfileIconColor back to 0.91 mix (~#E8) — that's the right
amount of contrast against a white SectionView card. Add a sibling helper
defaultProfileIconColorOnCanvas() at 0.85 mix (~#D9), which sits 23 units
below the ~#F0 canvas — same absolute contrast as the card variant achieves
on white.

Switch MemberProfileImage default from defaultProfileIconColor to the canvas
variant. Member avatars almost always render on canvas (chat list rows,
chat-bubble author avatar, group members list outside the card, channel
members, channel relays). Callers that need the card variant pass an
explicit color.

* GroupChatInfoView: move owner row out of the members card into the lazy list

Per review #1: card holds only Invite + (optional) search; the user-as-owner
row joins the same lazy column as the rest of the members, picking up the
canvas-variant avatar color through MemberProfileImage's updated default.

* Revert "ProfileImage colors: split into card vs canvas variants"

This reverts commit 379f84a4ae.

* Revert "MemberProfileImage: use defaultProfileIconColor instead of secondaryVariant"

This reverts commit bea3f24664.

* Revert "ChatInfoImage: LIGHT default avatar at midpoint of white card and gray canvas"

This reverts commit 05fbd6e0b1.

* Theme: darken LightColorPalette.secondaryVariant from #F1F2F6 (LightGray) to #E0E0E0

Old value (LightGray = #F1F2F6) was nearly invisible against the ~#F0F0F0
canvas — slightly bluish hue, ~1-2 units of contrast. The new #E0E0E0 sits
~16 units below canvas and ~31 below white card, visible on both. Affects
all LIGHT-theme avatar placeholders, UserPicker icons, DevicePill borders
and a handful of subtle UI surfaces using secondaryVariant.

* Card-less screens: paint background with Material surface

Form-only and link/QR screens have no card sections — the off-white canvas
under them just adds an extra visual layer with nothing to lift. Switch
their background to MaterialTheme.colors.surface (white on LIGHT, palette
surface on DARK/BLACK/SIMPLEX) so the screen reads as a single sheet.

Two patterns by container:
- 11 ModalView callsites get background = MaterialTheme.colors.surface.
- 4 screens rendered inside someone else's ModalView (GroupLinkView,
  HiddenProfileView, TagListView, UserProfilesView) wrap their root
  ColumnWithScrollBar in Box(Modifier.fillMaxSize().background(...))
  so they own their background regardless of caller.
- 1 BottomSheet root (CreateProfile in WelcomeView) gets background on
  the fillMaxSize Box.

Touched screens: Create profile, Create first profile (mobile/desktop),
Create group, Create channel (3 wizard steps), Edit group profile,
Group link, Add welcome message / Welcome message, Edit own profile,
Hide profile, Tag list editor, Your chat profiles, Add server,
Add chat relay (new variant only — Edit relay stays settings-style).

* NewServerView: add missing MaterialTheme import after previous commit

* Revert "GroupLink / WelcomeMessage: use SectionDividerSpaced between adjacent cards"

This reverts commit 29be15404f.

* Revert "CreateProfile: add vertical gap between profile fields and Create profile action card"

This reverts commit 43855ae07d.

* Revert "WelcomeView: wrap Create profile action button in SectionView card"

This reverts commit c61ea01092.

* Revert "AddGroupView/AddChannelView: wrap action buttons + toggles in SectionView card"

This reverts commit 4d9319d12a.

* Revert "GroupLinkView: wrap action items below QR in SectionView card"

This reverts commit f2ef38092a.

* Revert "GroupWelcomeView: wrap message editor/preview and buttons in SectionView cards"

This reverts commit edb3495a8f.

* Revert "TagListView: wrap Add/Save list button in SectionView card"

This reverts commit c25f36a900.

* UserProfilesView: drop SectionView wraps to remove card chrome

The Your-chat-profiles screen is now on white surface bg; the SectionView
cards (founder's original from PR #6777) painted white-on-white and only
contributed padding. Unwrap the two SectionViews (hidden-profile reveal
button + main profiles list) so the rows render directly inside the
ColumnWithScrollBar without card chrome.

* NewChatSheet: use standard 20dp gap between cards instead of tight 10dp

* WelcomeMessageView/GroupChatInfoView: unify owner button row, restore member dividers

WelcomeMessageView: drop SectionView wrap on SaveButton so all three
owner-mode action rows (Edit/Preview, Copy, Save) render uniformly as
loose rows on canvas, matching the post-revert direction of the
card-chrome cleanup.

GroupChatInfoView: restore per-item Divider() in the members lazy list
(lost during card-chrome experimentation). Owner row stays attached
to the "N members" card by design; divider appears between owner and
first lazy member, and between each subsequent member.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Strings: sentence case for 29 section-title keys across all locales

Base file was converted in 8292a815f / de36f1f40 / 314384b69 but other
locales still rendered titles like SETTINGS, НАСТРОЙКИ, EINSTELLUNGEN,
PARAMÈTRES, USTAWIENIA, ÎMPOSTAZIONI in ALL CAPS. Bring every locale to
sentence case with a single sweep.

Implementation: Python script (/tmp/fix_uppercase_locales.py) walks every
non-base locale dir, finds the 29 key strings, and rewrites them when the
value is entirely uppercase (no lowercase letter). Placeholders like %1$s
are preserved as-is; SimpleX and SOCKS are kept as proper nouns after the
lowercase pass. Values already in sentence case, empty, or in scripts with
no case distinction are left alone.

540 string changes across 33 locales (ar, bg, ca, cs, da, de, el, es, fa,
fi, fr, hr, hu, in, it, iw, ja, ko, ku, lt, nb-rNO, nl, pl, pt, pt-rBR,
ro, ru, th, tr, uk, vi, zh-rCN, zh-rTW). Locales bn, hi, ml, sv, lv had
nothing to change.

* UserProfilesView: use Divider() between rows (SectionDivider no-op outside SectionView)

* ShareListView: use MaterialTheme.colors.surface background (Forward picker)

* ChatItemInfoView: white surface background + drop SectionView card wraps

Message info screen (right-click → Info on desktop) had off-white themedBackground
canvas with white SectionView cards inside. Switch to MaterialTheme.colors.surface
background and replace 7 SectionView wraps with plain Column (preserving the
contentPadding the SectionViews had) — content reads as a single sheet, no
ghost card edges on white-on-white.

* Card-less screens batch 2: surface bg for conditions + how-to-use + about + version

Six more screens get white surface background to match the form-screen visual:

- UsageConditionsView (Network & servers → Review conditions): root
  ColumnWithScrollBar gets .background(surface).
- SingleOperatorUsageConditionsView (operator-conditions modal opened from
  enabling an operator): same.
- HowItWorks (Settings → How to use it): root Column gets .background(surface).
- WhatsNewView (Settings → What's new): ModalView gets background = surface.
- SimpleXInfoLayout (Settings → About SimpleX Chat): conditional on
  onboardingStage == null so the onboarding entry keeps its themedBackground
  while the settings entry switches to surface.
- VersionInfoView (Settings → App version): root ColumnWithScrollBar gets
  .background(surface).

* fix language strings

* fix contact list to be lazy

* more language fixes

* fix greek

* fix indentation

* refactor and simplify

* remove dividers

* background for settings pages with cards

* fix section titles

* remove footers outside of section cards

* move footers out of cards

* fix appearance etc

* fix members lists, add background

* appearance

* reduce paddings inside cards

* paddings

* more paddings

* card item paddings

* fix paddings

* toolbar color

* more toolbar color

* fix toolbar

* add padding

* refactor modals hierarchy

* more cards

* more cards

* fix theme

* split walpaper settings to two sections

* better grid

* grid

---------

Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
Co-authored-by: another-simple-pixel <anton.m.egorov@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:45:02 +01:00
..
2026-05-18 09:06:25 +00:00
2025-07-08 12:22:57 +01:00