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