Skip to content
Browse documentation
Decided

Settings and account

Everything under the gear: appearance, language, contact display, notifications, devices, privacy (export, delete, blocked, telemetry), help and about.

Updated: 29 October 2026

Settings is reached from the gear on the Profile tab (and the rail’s gear on a tablet) and lives as a pushed screen (”‹ Profile”); on tablets it takes both panes. Mock: mobile/design/redesign/redesign-round-3.html, grouped list (round 3, variant 1, A111).

The Settings screen is a grouped list. At the top an account card: avatar, name and “marieke@gmail.com · Google” (the sign-in address and method), opening Account. Then three groups, each a section label and a list box, each row an icon in a fill tile, the section’s name and, as its second line, its current value (Saybook’s status line), with a chevron:

GroupRowSecond line, for example
AppAppearance“Follows the system”
AppLanguage“English”
AppContacts“Show phone · sort by nickname”
AppNotifications“On for 5 of 6 circles”
Privacy and dataPrivacy“Findable by e-mail and phone”
Privacy and dataSigned-in devices“2 devices”
HelpHelp“FAQ, support, diagnostics log”
HelpAbout“Version 3.0 (227)”

Sign out lives in Account and Delete account in Privacy. Tapping a row pushes that section on a phone; on a tablet the list stays on the left and the section opens in the pane beside it, with Account selected on arrival (Tablets). A section whose screen is not in the build yet is left out of the list rather than leading to an empty screen. Inside a section the controls keep their v2 shape (title, one line of explanation, the control), drawn in the circle-first style.

Sections

Account

  • Sign-in e-mail with Change (A86): Supabase updateUser(email) with secure e-mail change on (double_confirm_changes), so a six-digit code goes to the current address and another to the new one; both are entered in one sheet (verifyEmailOtp, type email_change), each labelled with the address it went to and each verified against that address. The sheet refuses the current address (“That is already your sign-in e-mail”) and an invalid one before calling Auth, and says so when the address belongs to another account. With secure change on, the first accepted code is spent, so after one wrong code only the other is sent again; Send the codes again starts both afresh. The mails use the email_change template in the account’s language.
  • Linked sign-in methods (A87): the Auth identities, with Link (Google) and Unlink (any, never the last). Shown only when the build names a social provider (INCICO_SOCIAL_PROVIDERS); until then the section is “E-mail and password” with Set password / Change password. Apple is not offered on Android; an Apple identity linked on another platform is still listed and can be unlinked. Link needs the Google client of the build as well (the Credential Manager sheet), and is not offered without it. When only one method is left the card says it cannot be unlinked instead of offering Unlink. Auth does not say whether a password exists, so the app shows Change password when the session was signed in with one (the token’s amr claim) or when it has set one itself (a has_password flag in the user’s metadata), and Set password with “No password yet. You sign in with a code.” otherwise. The password sheet asks for one new password of at least 10 characters with letters and digits.
  • Sign out (red, confirm; clears cache and secure store for this account).

Appearance

  • Theme: segmented System / Light / Dark, default System (A101; was Dark under A100), applied immediately. Stored as accounts.theme; until the account row has been read, and while signed out, the app follows the system.
  • Text size: link to the OS display settings (no in-app override).

Language

  • App language: picker over “Device language” and SUPPORTED (English, Nederlands), default “Device language” (A81). Stored on the account as accounts.preferred_language, where null means Device language; the app resolves null to the device language (English when the device is neither). A change applies at once, without a restart, and is also written to devices.locale for this device and to the Auth user’s metadata (data.language), which the push texts and the Auth e-mails read. “Device language” names what it currently means (“Device language (Nederlands)”), and each language is listed in its own name. The app follows the cached account row, so a language chosen on a second device arrives with the delta fetch and switches this one too, moving its devices.locale and renaming its notification channels; the metadata is written only by the device where the choice was made. A failed devices.locale or metadata write does not undo the choice: the account row is the truth and the next registration or choice writes them again.

Contacts

  • Show under a contact’s name: E-mail / Phone (v2’s contact detail preference), used when both exist.
  • Sort by: Nickname, then name (default) / Name. Stored as accounts.contact_sort (nickname, name), so a tablet sorts like the phone (A82). It decides where “All contacts” files a row and under which index letter; the row still shows the nickname the viewer gave.
  • Phone format: National when same country (default) / Always international. Stored as accounts.phone_format (national, international) (A82). Used by the Contacts list and the contact card; the card under the choices shows an example number of the device’s country in the chosen format. Search finds a number typed either way.

Notifications

See Notifications: permission state, per-kind switches (three account columns that push-fanout reads, A83; the Contact removed switch is gone with its kind, A102).

Signed-in devices

List from list_sessions() (A84): the caller’s own sessions with when they signed in, when they were last active and the user agent, the current one first and marked “This device”. The device model is parsed from the user agent the app sends to Auth, Incico/<version> (Android <release>; <manufacturer> <model>); the IP address is never returned or shown. Sign out per device is revoke_session(id), which refuses the current session (that is the Sign out button; the current row has no Sign out of its own, and a refusal reads “That is this device. Use Sign out instead.”); Sign out everywhere else is Auth’s sign-out with the “others” scope and is offered only while another session exists (“You’re only signed in on this device” otherwise). Both ask first and read the list again afterwards. A signed-out device notices at its next token refresh, within the hour, which the card says. A session from before the app sent its own user agent, or from any other client, reads “Unknown device”; a browser (the website’s account page) reads “Web browser”. supabase-kt 3.8 has no user-agent setting and its shared Ktor configuration is internal API, so the header is set by an interceptor on the OkHttp engine the client is built with, and it goes with every call of the client, not only Auth’s.

Privacy

  • Blocked people: list from blocked_profiles() (name and avatar of each profile the account blocked, A85) with Unblock (unblock_profile), which lifts the block from every own profile that placed it and writes an audit row. The person can find and invite you again; the connection the block ended does not come back, and nobody is notified. Unblock asks first and names the person; a profile whose account has left is listed as “Account removed”; with nobody blocked the card says “You haven’t blocked anyone.”
  • Findable by: summary of the main profile’s searchable details from the cache (names marked searchable, e-mail addresses marked searchable and verified, phone numbers marked searchable), one line per kind, each opening that kind’s editor; “Nobody can find you by searching.” with a link to the e-mail editor when there is none, and “You turned off being found.” when the profile opted out.
  • Help improve Incico: telemetry switch (the onboarding consent): the same accounts.telemetry_consent write, and switching it off drops whatever is queued.
  • Export my data: asks first, then calls export-account; while it works the card says so, and when it is done it says the link is in the e-mail too and offers Download, which opens the signed link (valid 24 hours) in the browser; a failure says “The export didn’t work. Try again later.” and offers Export again. The zip contains JSON of profiles and connections’ snapshots plus vCards.
  • Delete account (red): a sheet listing what will happen (all profiles, connections, photos removed; your card disappears for your contacts; every connected service is disconnected by the app first, keeping or removing the entries Incico wrote according to that service’s Allow Incico to delete setting, because once the account is gone the server can no longer reach the phone’s address book (A79); backups purged within 30 days), requires typing DELETE or re-authenticating (Face ID / password) and then calls request_account_deletion. The app signs out and shows a confirmation; a confirmation e-mail follows. On Android the sheet also names the services connected on another device (“Connected on another device: … This phone can’t reach them, so what Incico wrote there stays.”), offers “Want a copy first? Export your data.”, and asks for DELETE typed in capitals (no re-authentication). Delete then runs, in this order and with a progress line per step (“Disconnecting services…”, “Deleting your account…”): every service connected on this phone is disconnected through the same disconnector as Connected services, under the services lock, with its entries kept or removed as its allow_delete says; request_account_deletion; delete-account, retried up to three times on a network or server error (it is idempotent), where a retry refused as unauthorised counts as done because an earlier attempt already removed the Auth user; then the ordinary sign-out wipes the cache and the secure store. A failure at any step before the sign-out stops there, leaves the person signed in and the sheet open with “Your account couldn’t be deleted yet…”, and Delete can be tapped again; every step is safe to repeat. While it runs the sheet cannot be dismissed. The run belongs to the process, not the screen, because its own sign-out takes the screen away; the confirmation (“Your account has been deleted”, Close) is shown over the sign-in screen until it is closed. The website offers the same for users without the app, at /{lang}/account/delete/ (A91): sign in with a six-digit e-mail code, then Export my data or Delete my account (type DELETE). Connected services on the phone cannot be reached from there, so the page says that the entries Incico wrote stay in the address book.

Help

  • Frequently asked questions opens https://www.incico.eu/{lang}/faq/ in a Custom Tab, in the app’s language.
  • Contact support is a mail to support@incico.eu with the subject “Incico support” and the app version, build and diagnostics id at the foot of the body. Without a mail app the screen says “No e-mail app found. Write to support@incico.eu.”
  • Diagnostics log (A88; the discoverable successor to v1’s hidden long-press viewer): what the log is, the install’s diagnostics id (a random UUID per install, kept in preferences of its own so a sign-out does not change it; a tap copies it), View log and Share log. The viewer shows the lines oldest first with their count, selectable, or “The log is empty.”; sharing hands a copy in cacheDir/diagnostics/ to the Android Sharesheet through the app’s FileProvider (the live file is never exposed), and an empty log is not shared. The viewer is a page of Help, so Back closes it before it leaves the section, on a phone and in the tablet’s pane alike.
  • Report a problem: a mail to support with “What happened?”, the version and the diagnostics id, and the log attached as a file unless Attach the diagnostics log is switched off; a mailto: selector limits the choice to mail apps.

About

  • Version and build, and the environment the build talks to (the lines that used to close the Settings list).
  • What’s new, Terms of use and Privacy policy open /{lang}/whats-new/, /{lang}/legal/terms/ and /{lang}/legal/privacy/ on the website in a Custom Tab.
  • Open-source licences (A90; Saymail lacks this screen): every library of the build, generated at build time by the AboutLibraries Gradle plugin into raw/aboutlibraries and rendered by the app itself, sorted by name. Each row shows the version and licence and opens the licence text, with a link to the project’s website; “No licences were found in this build.” when the list cannot be read. The licence pages are pages of About, so Back walks back through them.
  • Imprint: “Incico is made by Sefaja, Bongersweg 22, 8095PH ‘t Loo Oldebroek, the Netherlands.” and the copyright line.

Developer (debug builds only)

The environment the build talks to (a label: the environment is chosen at build time with INCICO_ENV, and there is no switch in the app), Seed demo data (on the local stack only: two demo circles on the main profile through the normal write path; the connected demo people of supabase/seed.sql come from supabase db reset, which a client cannot run), Fetch changes now (a forced delta fetch), Copy access token (the session’s JWT to the clipboard, for calling the API by hand) and Crash the app (asks first, then throws an uncaught DeveloperCrashTest to exercise crash reporting). On Android the screen lives in the debug source set and the release source set has an empty stub, so none of it is compiled into a release build; iOS uses #if DEBUG.

Account lifecycle rules

  • Sign out keeps the account; deletion is separate and deliberate.
  • Account deletion is irreversible; there is no grace period (the export exists for regret). The delete-account function removes storage objects, then all rows via cascade from accounts, then the Auth user, and writes an audit event without personal data.
  • Managed profiles die with the account. A future “hand over profile” flow is noted in the roadmap.
  • E-mail change keeps the old address as a verified detail unless the user removes it.

Not in Settings

Ads or supporter purchase (none at launch); per-app passcode (the OS lock is sufficient for a contacts app); backup/restore (the server is the backup).