replica(messages): recon, features, architecture, tokens
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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
|
||||
|
@@ -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 |
Reference in New Issue
Block a user