IchWillHeim
A dementia-friendly Android home screen launcher. The patient sees a locked-down, simple screen. Caregivers configure everything remotely via a web app.
Product architecture
The product is split into two distinct parts:
1. Android launcher (this app) The only thing installed on the patient's device. It fetches its configuration from NocoBase on startup and renders a simple, locked-down home screen. No workflow configuration happens on the device.
2. Caregiver web app Where caregivers manage everything: patients, devices, contacts, and workflows. Built in two phases:
- Near term: NocoBase's interface builder — Table, Form, Grid Card, and Details blocks are sufficient to build a functional caregiver dashboard with no frontend code. Caregivers can add/edit/remove workflows and contacts directly.
- Long term: a custom web app (and eventually mobile) with a proper onboarding questionnaire (ask what the patient needs → generate the first workflows automatically) plus a manual editor for ongoing changes.
Settings on the device
Patient-accessible settings
Simple, non-technical controls the patient can reach directly from the home screen:
- Font / button size
- Screen brightness
PIN-locked advanced settings
Accessible via long-press (PIN-protected). The access screen is labelled Settings Access and the menu Settings Menu:
- Device ID — the identifier used to fetch this device's config from NocoBase
- Language — in-app language picker
- Phone Settings — opens the Android system settings (for Wi-Fi, accessibility, etc.)
Workflow configuration, home screen grid layout, and help screen editing are never done on the device — all managed via the web app.
How the app works
From the patient's perspective
The patient sees one screen with large buttons driven by workflows configured remotely. Pressing Back does nothing. Pressing Home always returns to this screen. No other apps are accessible.
From the caregiver's perspective
Caregivers manage the patient's experience entirely through the web app (NocoBase or future custom app). On the device, they can access PIN-locked settings for device ID and language. They never need to touch workflow config on the device.
Home screen grid
The home screen displays workflow buttons in a fixed 2-column grid. The caregiver sets homepage_position (1-indexed, left-to-right, top-to-bottom) and button_span (1 = one cell, 2 = wide / full row) for each workflow via the web app.
Grid rendering rules (same logic in Android and web preview):
- Build a flat array of cells (size = effective grid size).
- For each workflow sorted by position: place it at
position - 1. Ifbutton_span == 2AND the button starts in the left column (even index) AND there is a next cell, mark the next cell as "consumed" so nothing else renders there. - If a
span-2button starts in the right column, it is silently downgraded tospan-1(would overflow the row). - Auto-extend: the configured
gridSize(6 or 8, set in caregiver settings) is a minimum. If a workflow's position+span exceeds it, the grid grows to fit —effectiveGridSize = max(configuredRows, rowsNeededByContent) × 2.
The same buildGridCells algorithm runs in HomeScreen.kt (Android) and Workflows.tsx (web preview), so what the caregiver sees in the browser is exactly what the patient sees on their phone.
Every button on the home screen triggers a workflow — a sequence of steps. Each step calls one function (show a screen, place a call, etc.) and returns a result code. Steps execute in sort_order ascending; the engine automatically advances to the next step unless an if step redirects the flow.
Step 1 (sort=1) → Step 2 (sort=2) → if step → yes: Step 3 / no: Step 4 → …
Everything is data-driven. New buttons, screens, and branching logic are configured in the web app with no code changes.
Step types
| function_id | behaviour |
|---|---|
show_call_confirm |
Shows a confirm/cancel screen. Emits confirmed. Cancel always ends the workflow. |
show_speaker_choice |
Shows speakerphone vs normal choice. Emits speakerphone or normal. |
call_contact |
Dials phone from parameter_json. Reads speakerphone from parameter_json (bool), falls back to lastResultCode == "speakerphone". |
show_reminder |
Renders ContentScreen with the step's content_block list. Advances with null when the patient taps the button. |
show_tutorial |
Same as show_reminder — mechanically identical. Both render ContentScreen. |
if |
Checks lastResultCode == if_result_code. Jumps to then_step if true, else_step if false. Falls back to next by sort_order if targets not set. |
label |
No-op. Used as a named jump target for if steps. Advances to next step by sort_order. |
lastResultCode is preserved across all steps, so a value set in step 2 is still readable by step 5.
Result codes
| result_code | emitted by |
|---|---|
confirmed |
show_call_confirm (user tapped confirm) |
cancelled |
show_call_confirm or show_speaker_choice (user tapped cancel) |
speakerphone |
show_speaker_choice |
normal |
show_speaker_choice |
NocoBase schema
Server: https://nocobase.rucki.ch
API notes:
- Filter only on direct fields of the collection — nested relation filters (e.g.
device.device_id) are not supported. To filter workflows by device, do a two-step lookup: resolvedevice_id→ device PK via/api/devices:list, then filter workflows bydevice_key. - Appends use
appends[]query parameter (array format). - When creating relations, always specify the existing FK explicitly in advanced options. If you let NocoBase auto-generate, it creates a second FK column and rows added later won't appear in API responses.
- Create belongsTo relations first (they create the FK column), then hasMany reusing the same FK.
Tables
| Table | Purpose |
|---|---|
device_users |
One row per patient (renamed from beneficiaries) |
devices |
One row per physical device; linked to a device user |
contacts |
Contacts; linked to device user (not device — shared across all their devices) |
workflow_function |
Global catalog of executable functions (defined by the app) |
workflow |
One row per home screen button per device |
workflow_step |
Steps within a workflow; each calls one function |
content_block |
Ordered UI content (heading / text / button) for screen steps |
event_log |
One row per completed workflow step — used by the caregiver dashboard |
device_users
| field | type |
|---|---|
name |
string |
date_of_birth |
date |
notes |
text |
devices
| field | type | notes |
|---|---|---|
device_id |
string | unique; used by the app to fetch its config (underscores, e.g. redmi_test) |
label |
string | human-readable name e.g. "Renate's tablet" |
beneficiary |
belongsTo → beneficiaries |
contacts
| field | type | notes |
|---|---|---|
name |
string | |
phone |
string | |
photo_url |
text (url) | |
device_user |
belongsTo → device_users | linked to the person, shared across all their devices |
workflow_function — global catalog, defined by the app
| field | type | notes |
|---|---|---|
function_id |
string | unique identifier used by the app |
name |
string | displayed in the web app step editor |
description |
text |
Current entries:
| function_id | name |
|---|---|
show_call_confirm |
Show Call Confirm |
show_speaker_choice |
Show Speaker Choice |
call_contact |
Call Contact |
if |
If / Branch |
label |
Label |
workflow — one per home screen button per device
| field | type | notes |
|---|---|---|
name |
string | e.g. "Call Sandro" |
homepage_position |
integer / null | 1-indexed position in the 2-column grid; null = not on home screen |
button_span |
integer | 1 = normal (1 cell), 2 = wide (full row); default 1 |
is_active |
boolean | |
device |
belongsTo → devices | |
workflow_steps |
hasMany → workflow_step | fetched via workflow_steps append |
workflow_step — steps execute in sort_order ascending; lowest = start step
| field | type | notes |
|---|---|---|
name |
string | |
sort_order |
integer | lowest value = start step |
parameter_json |
text | static inputs e.g. {"phone": "+41768224400"} |
function |
belongsTo → workflow_function | app reads function_id from the related record |
if_result_code |
string / null | if steps only: the lastResultCode value to match |
then_step |
belongsTo → workflow_step / null | if steps only: jump target when condition is true |
else_step |
belongsTo → workflow_step / null | if steps only: jump target when condition is false |
content_block |
hasMany → content_block | fetched via workflow_steps.content_block append |
Unused DB fields (can be deleted from NocoBase):
output_id,input_mapping_json,is_active
event_log — one row per completed workflow step
Written by the Android app fire-and-forget after each step. No UI impact.
| field | type | notes |
|---|---|---|
device_id |
string | matches devices.device_id |
workflow_name |
string | name of the running workflow |
function_id |
string | step type that completed |
result_code |
string / null | outcome (confirmed, cancelled, speakerphone, normal, or null for automatic steps) |
duration_seconds |
number | time the patient spent on this step |
created_at |
datetime | auto-set by NocoBase |
The caregiver web app dashboard groups these rows by device user → workflow → function+outcome to show usage statistics.
content_block — ordered content elements for screen steps
| field | type | notes |
|---|---|---|
type |
select | heading, text, image, button |
value |
text | display text, image URL, or button label |
sort_order |
integer | render order |
result_code |
string | button type only — emitted when tapped |
workflow_step |
belongsTo → workflow_step |
Relation map
| Relation | Type | FK column |
|---|---|---|
devices.device_user |
belongsTo → device_users | device_users_key on devices |
contacts.device_user |
belongsTo → device_users | device_users_key on contacts |
workflow.device |
belongsTo → devices | device_key on workflow |
workflow.workflow_steps |
hasMany → workflow_step | workflow_key on workflow_step |
workflow_step.function |
belongsTo → workflow_function | function_key on workflow_step |
workflow_step.then_step |
belongsTo → workflow_step | then_step_key on workflow_step |
workflow_step.else_step |
belongsTo → workflow_step | else_step_key on workflow_step |
workflow_step.content_block |
hasMany → content_block | content_block_key on content_block |
Example: Call Sandro (simple — no speaker choice)
| sort_order | function_id | parameter_json |
|---|---|---|
| 1 | show_call_confirm |
— |
| 2 | call_contact |
{"phone": "+41768224400"} |
Steps run in order. No transitions, no if steps needed.
Example: Call Sandro (with speaker choice via if step)
| sort_order | function_id | parameter_json | if_result_code | then_step | else_step |
|---|---|---|---|---|---|
| 1 | show_call_confirm |
— | — | — | — |
| 2 | show_speaker_choice |
— | — | — | — |
| 3 | if |
— | speakerphone |
→ step 4 | → step 5 |
| 4 | label |
— | — | — | — |
| 5 | call_contact |
{"phone":"…","speakerphone":true} |
— | — | — |
| 6 | label |
— | — | — | — |
| 7 | call_contact |
{"phone":"…","speakerphone":false} |
— | — | — |
Step 3 reads lastResultCode from step 2. If it equals "speakerphone", jumps to step 4 (label) → step 5 (call with speaker). Otherwise jumps to step 6 (label) → step 7 (normal call).
File structure
app/src/main/java/com/example/ichwillheim/
│
├── MainActivity.kt — entry point, navigation state, workflow runner, call utilities
├── HomeScreen.kt — patient home screen (clock, workflow buttons)
├── ContentScreen.kt — data-driven content renderer for show_reminder / show_tutorial steps
├── PreCallStepsScreen.kt — WorkflowConfirmScreen (show_call_confirm)
├── SpeakerChoiceScreen.kt — WorkflowSpeakerChoiceScreen, SpeakerOptionHalf
├── PinScreen.kt — PIN entry screen
├── LauncherSettingsScreen.kt — SettingsMenuScreen, LauncherSettingsScreen, LanguagePicker
├── CallOverlayService.kt — foreground service for call overlay
├── WorkflowModels.kt — Workflow, WorkflowStep, ContentBlock
├── WorkflowEngine.kt — WorkflowRunState: advance(), param(), start()
├── SettingsRepository.kt — AppSettings + SharedPreferences
└── RemoteConfigRepository.kt — Ktor HTTP client; fetches workflows from NocoBase
Internationalisation
All user-facing strings live in res/values/strings.xml (English default).
| Folder | Language |
|---|---|
res/values/ |
English (default) |
res/values-de/ |
German |
res/values-es/ |
Spanish |
Permissions
| Permission | Why |
|---|---|
CALL_PHONE |
Place calls directly without the dialer |
READ_PHONE_STATE |
Detect call state (for speakerphone timing) |
ANSWER_PHONE_CALLS |
Answer incoming calls from the overlay |
MODIFY_AUDIO_SETTINGS |
Switch to speakerphone |
SYSTEM_ALERT_WINDOW |
Draw call overlay — must be granted manually |
FOREGROUND_SERVICE + FOREGROUND_SERVICE_SPECIAL_USE |
Run CallOverlayService |
POST_NOTIFICATIONS |
Required on Android 13+ for foreground service notification |
Security
The app authenticates to NocoBase via a Bearer token in every request. The token is stored in local.properties (never committed) and injected at build time via BuildConfig.
Before any public release: replace with a proper auth layer — caregiver accounts, server-side token exchange, per-user data isolation in NocoBase. The database schema does not need to change; only the authentication mechanism on top of it.
Color palette
Both the Android launcher and the caregiver web app use the same three-color system:
| Role | Hex | Usage |
|---|---|---|
| Background / surface | #FFF6E5 |
Screen background, sidebar, cards |
| Primary | #A6CDB2 |
Buttons, active nav items, focus rings |
| Accent | #FCAB7E |
Cancel buttons, secondary actions, highlights |
| Text | #2D2D2D |
All foreground text on the above backgrounds |
All three background colors pass WCAG AA contrast with #2D2D2D text (≥ 7:1).
Ideas backlog
- Custom caregiver web app — onboarding questionnaire (generates first workflows) + manual editor; eventually iOS/Android caregiver app
- Unified inbox — SMS + WhatsApp in one patient-facing screen
- Photo slideshow management — upload/manage slideshow photos remotely via NocoBase
- Message effectiveness tracking — log whether reminders led to calls or cancellations
- Caregiver accounts — multiple caregivers per patient, each with their own login
- Community platform — caregivers share workflow presets, tips, and experiences