* 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 importf922d8fcintroduced 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 bottom8050676bmoved 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 inb1a1dad8): 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 commitbe365e55ae. * 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 Commit633e0f414made 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 Commit3a7118235extracted 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 at43855ae07(before commit3a7118235introduced 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 cleanup23b0e41d8which 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) Commit3a9ece8d1moved 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 itemsfa29bb7a7put 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 commit379f84a4ae. * Revert "MemberProfileImage: use defaultProfileIconColor instead of secondaryVariant" This reverts commitbea3f24664. * Revert "ChatInfoImage: LIGHT default avatar at midpoint of white card and gray canvas" This reverts commit05fbd6e0b1. * 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 commit29be15404f. * Revert "CreateProfile: add vertical gap between profile fields and Create profile action card" This reverts commit43855ae07d. * Revert "WelcomeView: wrap Create profile action button in SectionView card" This reverts commitc61ea01092. * Revert "AddGroupView/AddChannelView: wrap action buttons + toggles in SectionView card" This reverts commit4d9319d12a. * Revert "GroupLinkView: wrap action items below QR in SectionView card" This reverts commitf2ef38092a. * Revert "GroupWelcomeView: wrap message editor/preview and buttons in SectionView cards" This reverts commitedb3495a8f. * Revert "TagListView: wrap Add/Save list button in SectionView card" This reverts commitc25f36a900. * 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>
Android App Development
This is a guide to contributing to the develop of the SimpleX android and desktop apps.
Project Overview
This is the Kotlin Multiplatform (KMP) mobile and desktop client for SimpleX Chat, sharing code between Android and Desktop (JVM) platforms using Compose Multiplatform for UI.
Build Commands
# Android debug APK
./gradlew assembleDebug
# Android release APK
./gradlew assembleRelease
# Desktop distribution (current OS)
./gradlew :desktop:packageDistributionForCurrentOS
# Run desktop/JVM tests
./gradlew desktopTest
# Run Android instrumented tests (requires connected device/emulator)
./gradlew connectedAndroidTest
# Build native libraries for all platforms
./gradlew common:cmakeBuild -PcrossCompile
# Clean build
./gradlew clean
Architecture
Module Structure
common/- Shared code (Compose UI, models, business logic)src/commonMain/- Cross-platform codesrc/androidMain/- Android-specific implementationssrc/desktopMain/- Desktop-specific implementations
android/- Android app containerdesktop/- Desktop JVM app container
Key Components (common/src/commonMain/kotlin/chat/simplex/common/)
model/ChatModel.kt- Main state container with reactive properties (MutableState, MutableStateFlow)model/SimpleXAPI.kt- API bindings to Haskell core library via FFIplatform/Core.kt- FFI interface to nativelibapplibraryplatform/- Platform abstraction layer (expect/actual pattern for Android/Desktop specifics)views/- Compose UI screens organized by feature (chat, chatlist, call, usersettings, etc.)ui/theme/- Design system (colors, typography, shapes)
Native Integration
The app calls into a Haskell core library via JNI/FFI:
- CMake builds in
common/src/commonMain/cpp/android/andcpp/desktop/ - Cross-compilation toolchains in
cpp/toolchains/ - Built libraries go to
cpp/desktop/libs/(organized by platform)
Configuration
local.properties (create from local.properties.example)
compression.level=0 # APK compression (0-9)
enable_debuggable=true # Debug mode
application_id.suffix=.debug # Multiple app instances on same device
app.name=SimpleX Debug # App name for debug builds
gradle.properties
Contains versions (Kotlin, Compose, AGP) and app version info. Key settings:
kotlin.jvm.target=11database.backend=sqlite(orpostgres)
Testing
Tests are in:
common/src/commonTest/kotlin/- Cross-platform testscommon/src/desktopTest/kotlin/- Desktop-specific tests (run with./gradlew desktopTest)android/src/androidTest/- Android instrumented tests
Resources & Localization
- String resources:
common/src/commonMain/resources/MR/base/strings.xml+ 21 language variants - Uses Moko Resources (
dev.icerock.moko:resources) for cross-platform resource management - The
adjustFormattinggradle task validates string resources during build
Platform-Specific Notes
Android
- Min SDK 26, Target SDK 35
- NDK 23.1.7779620
- Supports ABI splits:
arm64-v8a,armeabi-v7a - Deep linking requires SHA certificate fingerprint in
assetlinks.json(see README.md)
Desktop
- Distributions: DMG (macOS), MSI/EXE (Windows), DEB (Linux)
- Mac signing/notarization configured via
local.properties - Video playback uses VLCJ
Gotchas
SHA Signature for verification for app links/deep links
In order for the SimpleX app to be automatically adopted for opening links from https://simplex.chat the SHA certificate fingerprint for the App installed on the phone must be in the hosted assetlinks.json file on simplex.chat.
The accepted fingerprints are in the sha256_cert_fingerprints list.
To find your SHA certificate fingerprint perform the following steps.
- Build and install your development version of the app as usual
- From the terminal in Android studio run
adb shell pm get-app-links chat.simplex.app - Copy the signature listed in
signaturesin the result - Add your signature to assetlinks.json in the website repo and make a PR. On approval, wait a few minutes for the changes to propagate to the public website and then you should be able to verify SimpleX.
More information is available here. If there is no response when running the pm get-app-links command, the intents in AndroidManifest.xml are likely misspecified. A verification attempt can be triggered using adb shell pm verify-app-links --re-verify chat.simplex.app.
Note that this is not an issue for the app store build of the app as this is signed with our app store credentials and thus there is a stable signature over users. Developers do not have general access to these credentials for development and testing.
Adding icons
-
Find a Material symbol in Rounded category.
-
Set weight to 400, grade to -25 and size to 48px.
-
Click on the icon, choose Android and download XML file.
-
Update the color to black (#FF000000) and the size to "24.dp", as in other icons.
For example, this is add reaction icon.