replica(messages): recon, features, architecture, tokens

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-17 16:49:43 +02:00
co-authored by Claude Opus 5.5
parent 612c017c2b
commit 2f1ddd0ff0
11 changed files with 211 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
# Architecture: Messages (Android, ohne Google)
Same conventions as the others; **no network permission** (SMS goes through the radio, not the internet).
| Schicht | Wahl | warum |
| --- | --- | --- |
| UI | Compose, M3 Expressive, dynamic colour | same as the others |
| Rolle | `RoleManager.ROLE_SMS` | only the default SMS app may write the SMS provider and receives SMS_DELIVER |
| Empfangen | `SMS_DELIVER` receiver (BROADCAST_SMS), joins multipart PDUs, inserts into `Telephony.Sms.Inbox` | default app duty |
| MMS-Pflicht | `WAP_PUSH_DELIVER` receiver (accepted, stored later with MMS support) | required for the role |
| Senden | `SmsManager.divideMessage` + `sendMultipartTextMessage` with sent/delivered PendingIntents; row in outbox → sent/failed | status updates in the provider |
| Antwort ohne UI | `RESPOND_VIA_MESSAGE` service (SEND_RESPOND_VIA_MESSAGE) | phone app "decline with message"; required for the role |
| Daten | system provider via ContentResolver + ContentObserver | no copies |
| Kontakte | PhoneLookup, same helper as Telefon | |
Data: no own schema (DataStore for settings).
Bites: multipart and encoding, dual SIM (`SubscriptionManager`), status callbacks after process death (receiver updates by message id), notification grouping per thread, short codes and alphanumeric senders (no reply possible).
Build order: role + receive + list → conversation + send + status → notifications with reply → new chat, search, archive, delete → MMS.
+62
View File
@@ -0,0 +1,62 @@
// Generated from tokens.json by gen_theme.py. Do not edit by hand.
package app.messages.ui.theme
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
object WColor {
val bg = Color(0xFFF8F9FF)
val row = Color(0xFFFFFFFF)
val text = Color(0xFF191C20)
val textMuted = Color(0xFF44474E)
val bubbleIn = Color(0xFFE8EAF1)
val onBubbleIn = Color(0xFF191C20)
val bubbleOut = Color(0xFF2B5EA7)
val onBubbleOut = Color(0xFFFFFFFF)
val unread = Color(0xFF2B5EA7)
val error = Color(0xFFBA1A1A)
val fab = Color(0xFFD6E3FF)
val onFab = Color(0xFF001B3E)
val composer = Color(0xFFECEEF4)
val onComposer = Color(0xFF191C20)
val darkBg = Color(0xFF111318)
val darkRow = Color(0xFF1D2024)
val darkText = Color(0xFFE2E2E9)
val darkTextMuted = Color(0xFFC4C6D0)
val darkBubbleIn = Color(0xFF2B2F36)
val darkOnBubbleIn = Color(0xFFE2E2E9)
val darkBubbleOut = Color(0xFFA8C7FA)
val darkOnBubbleOut = Color(0xFF062E6F)
val darkUnread = Color(0xFFA8C7FA)
val darkError = Color(0xFFFFB4AB)
val darkFab = Color(0xFF284777)
val darkOnFab = Color(0xFFD6E3FF)
val darkComposer = Color(0xFF2B2F36)
val darkOnComposer = Color(0xFFE2E2E9)
}
object WType {
val label = TextStyle(fontSize = 12.sp, lineHeight = 16.sp, fontWeight = FontWeight.W500)
val bodySm = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.W400)
val body = TextStyle(fontSize = 16.sp, lineHeight = 22.sp, fontWeight = FontWeight.W400)
val title = TextStyle(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.W400)
}
object WSpace {
val s0 = 0.dp
val s2 = 2.dp
val s4 = 4.dp
val s8 = 8.dp
val s12 = 12.dp
val s16 = 16.dp
val s24 = 24.dp
}
object WRadius {
val bubble = 20.dp
val bubbleTight = 6.dp
val pill = 999.dp
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
"""tokens.json -> Tokens.kt (Compose). Re-run after every token change."""
import json, re, sys, pathlib
src = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else "tokens.json")
out = pathlib.Path(sys.argv[2] if len(sys.argv) > 2 else src.with_name("Tokens.kt"))
t = json.loads(src.read_text())
camel = lambda s: re.sub(r"-(\w)", lambda m: m.group(1).upper(), s)
L = ["// Generated from tokens.json by gen_theme.py. Do not edit by hand.",
"package app.messages.ui.theme", "",
"import androidx.compose.ui.graphics.Color",
"import androidx.compose.ui.text.TextStyle",
"import androidx.compose.ui.text.font.FontWeight",
"import androidx.compose.ui.unit.dp",
"import androidx.compose.ui.unit.sp", "",
"object WColor {"]
L += [f" val {camel(k)} = Color(0xFF{v.lstrip('#').upper()})" for k, v in t["color"].items()]
L += ["}", "", "object WType {"]
for k, v in t["type"].items():
L.append(f" val {camel(k)} = TextStyle(fontSize = {v['size']}.sp, lineHeight = {v['line']}.sp, fontWeight = FontWeight.W{v['weight']})")
L += ["}", "", "object WSpace {"]
L += [f" val s{v} = {v}.dp" for v in t["space"]]
L += ["}", "", "object WRadius {"]
L += [f" val {camel(k)} = {v}.dp" for k, v in t["radius"].items()]
L += ["}", ""]
out.write_text("\n".join(L))
print("wrote", out)
+16
View File
@@ -0,0 +1,16 @@
{
"_about": "Messages fallback palette; dynamic colour on Android 12+.",
"color": {
"bg": "#f8f9ff", "row": "#ffffff", "text": "#191c20", "text-muted": "#44474e",
"bubble-in": "#e8eaf1", "on-bubble-in": "#191c20", "bubble-out": "#2b5ea7", "on-bubble-out": "#ffffff",
"unread": "#2b5ea7", "error": "#ba1a1a", "fab": "#d6e3ff", "on-fab": "#001b3e", "composer": "#eceef4", "on-composer": "#191c20",
"dark-bg": "#111318", "dark-row": "#1d2024", "dark-text": "#e2e2e9", "dark-text-muted": "#c4c6d0",
"dark-bubble-in": "#2b2f36", "dark-on-bubble-in": "#e2e2e9", "dark-bubble-out": "#a8c7fa", "dark-on-bubble-out": "#062e6f",
"dark-unread": "#a8c7fa", "dark-error": "#ffb4ab", "dark-fab": "#284777", "dark-on-fab": "#d6e3ff", "dark-composer": "#2b2f36", "dark-on-composer": "#e2e2e9"
},
"pairs": [["text","bg"],["text","row"],["text-muted","row"],["on-bubble-in","bubble-in"],["on-bubble-out","bubble-out"],["unread","row"],["error","bg"],["on-fab","fab"],["on-composer","composer"],
["dark-text","dark-bg"],["dark-text","dark-row"],["dark-text-muted","dark-row"],["dark-on-bubble-in","dark-bubble-in"],["dark-on-bubble-out","dark-bubble-out"],["dark-unread","dark-row"],["dark-error","dark-bg"],["dark-on-fab","dark-fab"],["dark-on-composer","dark-composer"]],
"type": {"label": {"size": 12, "line": 16, "weight": 500}, "body-sm": {"size": 14, "line": 20, "weight": 400}, "body": {"size": 16, "line": 22, "weight": 400}, "title": {"size": 22, "line": 28, "weight": 400}},
"space": [0, 2, 4, 8, 12, 16, 24],
"radius": {"bubble": 20, "bubble-tight": 6, "pill": 999}
}
+25
View File
@@ -0,0 +1,25 @@
feature,area,priority,original,clone,notes
Become the default SMS app (role),core,must,yes,no,
Receive SMS and store it in the system provider,core,must,yes,no,
Send SMS incl. long multipart messages,core,must,yes,no,
Sent and delivered status; failed with retry,core,must,yes,no,
Conversation list with unread state,list,must,yes,no,
Contact names and avatars,list,must,yes,no,
Conversation bubbles with day separators,chat,must,yes,no,
Composer with send button,chat,must,yes,no,
Notification with direct reply and mark as read,notifications,must,yes,no,
Start a new chat from contacts or a number,compose,must,yes,no,
Mark as read when opened,core,must,yes,no,
Delete conversations,list,should,yes,no,
Archive conversations,list,should,yes,no,
Search messages,search,should,yes,no,
Copy message text and links clickable,chat,should,yes,no,
Call from the conversation,chat,should,yes,no,
Quick reply from the phone app (respond via message),core,must,yes,no,needed for the role
Dual SIM choice,core,could,yes,no,
Dynamic colour and dark mode,design,must,yes,no,
Delivery reports setting,settings,could,yes,no,
MMS pictures and group MMS,mms,should,yes,no,later
RCS chat features,rcs,could,yes,skip,Google/carrier backend
Spam protection and Gemini features,other,could,yes,skip,Google services
Messages for web,other,could,yes,skip,Google service
1 feature area priority original clone notes
2 Become the default SMS app (role) core must yes no
3 Receive SMS and store it in the system provider core must yes no
4 Send SMS incl. long multipart messages core must yes no
5 Sent and delivered status; failed with retry core must yes no
6 Conversation list with unread state list must yes no
7 Contact names and avatars list must yes no
8 Conversation bubbles with day separators chat must yes no
9 Composer with send button chat must yes no
10 Notification with direct reply and mark as read notifications must yes no
11 Start a new chat from contacts or a number compose must yes no
12 Mark as read when opened core must yes no
13 Delete conversations list should yes no
14 Archive conversations list should yes no
15 Search messages search should yes no
16 Copy message text and links clickable chat should yes no
17 Call from the conversation chat should yes no
18 Quick reply from the phone app (respond via message) core must yes no needed for the role
19 Dual SIM choice core could yes no
20 Dynamic colour and dark mode design must yes no
21 Delivery reports setting settings could yes no
22 MMS pictures and group MMS mms should yes no later
23 RCS chat features rcs could yes skip Google/carrier backend
24 Spam protection and Gemini features other could yes skip Google services
25 Messages for web other could yes skip Google service
+64
View File
@@ -0,0 +1,64 @@
# Recon map: Google Messages (Android)
Scope: conversation list, conversation with bubbles, compose/new chat, SMS send/receive/delivery, notifications with reply, search, archive/delete, contacts integration
For: personal use, an SMS app without Google services (project NoGooleApps)
Date: 2026-10-17
## Sources
| # | source | URL | notes |
| --- | --- | --- | --- |
| 1 | screenshots | replica/messages/screens/ref-01..05.png | supplied by user: launcher icon, RCS group chat with reactions, list (store art), list on a phone (older), chat with image. Reference only |
| 2 | platform docs | https://developer.android.com/reference/android/provider/Telephony | SMS/MMS provider, default SMS app duties |
| 3 | platform docs | https://developer.android.com/reference/android/app/role/RoleManager#ROLE_SMS | default SMS app role requirements |
## Core loop
See new texts at a glance and answer in two taps, from the list or straight from the notification.
## Screens
| ID | screen | how | purpose | components | states |
| --- | --- | --- | --- | --- | --- |
| S01 | Conversations | launch | all threads | top bar (title, search, menu), rows (avatar, name or number, snippet with "Du: ", time, unread bold + dot), FAB "Chat starten" | empty, no role, filled, unread |
| S02 | Conversation | tap a row | read and reply | top bar (back, avatar, name, call, menu), bubbles (incoming left surface, outgoing right primary), day separators, time + delivery status, composer pill (attach, field, send) | sending, sent, delivered, failed (tap to retry), long text, links |
| S03 | New chat | FAB | pick a recipient | "An:" field, contact suggestions, type a number | empty, matches |
| S04 | Search | search icon | find messages | field, results with highlighted match | |
| S05 | Archived | menu | archived threads | list | empty |
| S06 | Settings | menu | options | default app, notifications (system), delivery reports, theme | |
| S07 | Notification | system | read and reply without opening | MessagingStyle with sender, direct reply, mark as read | |
## Flows
```
F01 Reply to a new text: notification → reply inline → sent (taps: 2)
F02 Read and answer in the app: S01 → S02 → type → send
F03 Start a chat: S01 FAB → S03 pick contact → S02 → send
F04 Delete / archive a thread: long press in S01 → action
F05 Become the default SMS app: first start → system dialog
edge: long messages (multipart), no signal (failed, retry), dual SIM, unknown sender, short codes, delivery report
```
## Components
Avatar (photo, initial, unknown) · ConversationRow · Bubble (in, out; first/middle/last grouping radius) · DaySeparator · Composer pill · Status line (Wird gesendet, Gesendet, Zugestellt, Fehler) · FAB extended "Chat starten" · Selection top bar.
## Data model
Messages and threads live in the system provider (`Telephony.Sms`, `Telephony.Threads`); as default SMS app we write incoming messages there ourselves.
```
Thread (system) thread_id, recipients, snippet, date, read, archived (Telephony.Threads.ARCHIVED) confidence: high
Sms (system) _id, thread_id, address, body, date, type (inbox|sent|outbox|failed|queued), read, status confidence: high
Prefs own: delivery_reports, theme confidence: high
```
## Out of scope
- RCS (typing indicators, reactions, high-res media, read receipts over data): Google's RCS backend (Jibe) and carrier deals; not open to other apps.
- Google account backup, spam protection, Magic Compose/Gemini, "Messages for web": Google services.
- MMS (pictures, group MMS): technically possible for a default SMS app but needs carrier MMS settings and PDU handling; planned after SMS works.
## Size
Screens 7, flows 5. Hard parts: (1) default-SMS duties (store incoming, mark read, respond-via-message service, sent/delivered intents, multipart), (2) fast thread list with contact names, (3) MMS later. Size: M.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB