build(uhr): alarms, ringing, timer, stopwatch, world clock, bedtime, settings, widgets

- exact alarms via setAlarmClock, rescheduled on boot/time/zone change and app update
- foreground ringing service: alarm-stream audio with gradual volume, vibration,
  full-screen screen with slide to snooze/stop, silence-after, missed notification
- timers and stopwatch keep timestamps only; live countdown/chronometer notifications
- world clock offline from the time zone database (no network permission at all)
- own alarm tone, own analog face and launcher icon
- next-ring logic unit-tested incl. DST gaps and zone changes; screenshot tests

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-17 15:16:50 +02:00
co-authored by Claude Opus 5.5
parent 887299a3a7
commit bc4cd8daf1
75 changed files with 4545 additions and 57 deletions
+289
View File
@@ -0,0 +1,289 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "577de12663ea3a99bc9d48c8ed391a91",
"entities": [
{
"tableName": "alarms",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `hour` INTEGER NOT NULL, `minute` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `days` INTEGER NOT NULL, `label` TEXT NOT NULL, `ringtone_uri` TEXT, `vibrate` INTEGER NOT NULL, `skip_until` TEXT, `is_bedtime_wake` INTEGER NOT NULL, `created_at` INTEGER NOT NULL, `updated_at` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "hour",
"columnName": "hour",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minute",
"columnName": "minute",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "days",
"columnName": "days",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "ringtoneUri",
"columnName": "ringtone_uri",
"affinity": "TEXT"
},
{
"fieldPath": "vibrate",
"columnName": "vibrate",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "skipUntil",
"columnName": "skip_until",
"affinity": "TEXT"
},
{
"fieldPath": "isBedtimeWake",
"columnName": "is_bedtime_wake",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "created_at",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updated_at",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_alarms_hour_minute",
"unique": false,
"columnNames": [
"hour",
"minute"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_alarms_hour_minute` ON `${TABLE_NAME}` (`hour`, `minute`)"
}
]
},
{
"tableName": "alarm_instances",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`alarm_id` INTEGER NOT NULL, `fire_at` INTEGER NOT NULL, `state` TEXT NOT NULL, `updated_at` INTEGER NOT NULL, PRIMARY KEY(`alarm_id`), FOREIGN KEY(`alarm_id`) REFERENCES `alarms`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "alarmId",
"columnName": "alarm_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "fireAt",
"columnName": "fire_at",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updated_at",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"alarm_id"
]
},
"indices": [
{
"name": "index_alarm_instances_state_fire_at",
"unique": false,
"columnNames": [
"state",
"fire_at"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_alarm_instances_state_fire_at` ON `${TABLE_NAME}` (`state`, `fire_at`)"
}
],
"foreignKeys": [
{
"table": "alarms",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"alarm_id"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "timers",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `label` TEXT NOT NULL, `duration_ms` INTEGER NOT NULL, `remaining_ms` INTEGER NOT NULL, `end_at` INTEGER, `state` TEXT NOT NULL, `created_at` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "label",
"columnName": "label",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "durationMs",
"columnName": "duration_ms",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "remainingMs",
"columnName": "remaining_ms",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "endAt",
"columnName": "end_at",
"affinity": "INTEGER"
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "created_at",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_timers_state_end_at",
"unique": false,
"columnNames": [
"state",
"end_at"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_timers_state_end_at` ON `${TABLE_NAME}` (`state`, `end_at`)"
}
]
},
{
"tableName": "cities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `zone_id` TEXT NOT NULL, `name` TEXT NOT NULL, `sort_order` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "zoneId",
"columnName": "zone_id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sortOrder",
"columnName": "sort_order",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_cities_zone_id",
"unique": true,
"columnNames": [
"zone_id"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_cities_zone_id` ON `${TABLE_NAME}` (`zone_id`)"
},
{
"name": "index_cities_sort_order",
"unique": false,
"columnNames": [
"sort_order"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_cities_sort_order` ON `${TABLE_NAME}` (`sort_order`)"
}
]
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '577de12663ea3a99bc9d48c8ed391a91')"
]
}
}