397 lines
15 KiB
Markdown
397 lines
15 KiB
Markdown
# Android-App-Konzept fuer YpChat
|
|
|
|
## Zielbild
|
|
|
|
YpChat soll als echte Android-App verfuegbar werden, nicht nur als WebView-Wrapper. Die Android-App nutzt die bestehenden Backend-Endpunkte und spricht mit dem vorhandenen Socket.IO-Server dasselbe Ereignisprotokoll wie das Vue-Web-Frontend.
|
|
|
|
Das Ziel fuer den ersten Release ist Funktionsgleichheit mit dem Kern-Chat:
|
|
|
|
- Login mit Benutzername, Geschlecht, Alter und Land
|
|
- Anzeige aktiver Benutzer
|
|
- Suche nach Benutzern
|
|
- Einzelchat mit Textnachrichten
|
|
- Bildversand ueber bestehenden Upload-Endpunkt
|
|
- Inbox mit ungelesenen Chats
|
|
- Verlauf geoeffneter Konversationen
|
|
- Blockieren und Entblockieren von Benutzern
|
|
- Session-Wiederherstellung, Logout und 30-Minuten-Inaktivitaetslogik
|
|
- Feedback- und Partnerseiten optional im MVP, aber technisch ueber vorhandene REST-Endpunkte moeglich
|
|
|
|
## Bestand Im Repo
|
|
|
|
Das aktuelle System besteht aus:
|
|
|
|
- Backend: Node.js/Express in `server/index.js`
|
|
- REST-Endpunkte in `server/routes.js`
|
|
- Socket.IO-Chat-Protokoll in `server/broadcast.js`
|
|
- Vue/Pinia-Webclient mit Socket.IO-Client in `client/src/stores/chat.js`
|
|
- Bild-Upload im Webclient ueber `client/src/components/ChatInput.vue`
|
|
|
|
Der Server erlaubt Socket.IO mit `websocket` und `polling`. Das Web-Frontend nutzt aktuell absichtlich nur `polling`, vermutlich wegen Proxy-/WebSocket-Problemen. Fuer Android sollte WebSocket als bevorzugter Transport genutzt werden, mit Polling als Fallback.
|
|
|
|
## Technische Empfehlung
|
|
|
|
### App-Technologie
|
|
|
|
Empfohlen: native Android-App mit Kotlin und Jetpack Compose.
|
|
|
|
Gruende:
|
|
|
|
- echte App-Erfahrung statt WebView
|
|
- robuste Hintergrund-/Reconnect-Logik
|
|
- gute Kontrolle ueber Cookies, Sessions und Uploads
|
|
- moderne UI mit Compose schneller wartbar
|
|
- bessere Basis fuer spaetere Push Notifications
|
|
|
|
Alternative: React Native oder Flutter waeren moeglich, bringen aber fuer diese App keinen klaren Vorteil, weil das Protokoll einfach ist und Android explizit das Ziel ist.
|
|
|
|
### Zielarchitektur
|
|
|
|
```text
|
|
Android App
|
|
UI: Jetpack Compose
|
|
State: ViewModel + StateFlow
|
|
REST: Retrofit/OkHttp
|
|
Socket: Socket.IO Android Client
|
|
Session: OkHttp CookieJar + EncryptedSharedPreferences/DataStore
|
|
Images: Android Photo Picker + Multipart Upload
|
|
|
|
Existing Backend
|
|
Express REST API
|
|
Socket.IO Chat Events
|
|
express-session Cookie connect.sid
|
|
```
|
|
|
|
## Backend-Anbindung
|
|
|
|
### Basis-URL
|
|
|
|
Production:
|
|
|
|
```text
|
|
https://www.ypchat.net
|
|
```
|
|
|
|
Development:
|
|
|
|
```text
|
|
http://10.0.2.2:3300
|
|
```
|
|
|
|
`10.0.2.2` ist im Android Emulator der Host-Rechner. Auf echtem Geraet braucht es die lokale LAN-IP oder einen Dev-Tunnel.
|
|
|
|
### REST-Endpunkte
|
|
|
|
Die Android-App kann folgende vorhandene Endpunkte direkt verwenden:
|
|
|
|
| Zweck | Methode | Pfad | Android-Nutzung |
|
|
|---|---:|---|---|
|
|
| Health Check | GET | `/api/health` | Diagnose/Verbindungscheck |
|
|
| Session holen | GET | `/api/session` | Cookie initialisieren, Session wiederherstellen, Express-Session-ID erhalten |
|
|
| Logout | POST | `/api/logout` | serverseitige Session beenden und Socket trennen |
|
|
| Laenderliste | GET | `/api/countries` | Login-Land-Auswahl und Flag-Code |
|
|
| Bild hochladen | POST | `/api/upload-image` | Multipart-Upload mit Session-Cookie |
|
|
| Bild abrufen | GET | `/api/image/:code` | Anzeige empfangener Bilder |
|
|
| Partner | GET | `/api/partners` | optionaler App-Screen |
|
|
| Feedback laden | GET | `/api/feedback` | optionaler Feedback-Screen |
|
|
| Feedback senden | POST | `/api/feedback` | optionaler Feedback-Screen |
|
|
| Feedback Admin Login | POST | `/api/feedback/admin-login` | optional, eher nicht MVP |
|
|
| Feedback Admin Logout | POST | `/api/feedback/admin-logout` | optional, eher nicht MVP |
|
|
| Feedback loeschen | DELETE | `/api/feedback/:id` | optional, eher nicht MVP |
|
|
|
|
Wichtig: REST und Socket muessen dieselbe Cookie-Session verwenden. Android braucht daher einen gemeinsamen `OkHttpClient` mit persistenter Cookie-Verwaltung.
|
|
|
|
## Socket.IO-Protokoll
|
|
|
|
### Verbindungsaufbau
|
|
|
|
Ablauf analog zum Web-Frontend:
|
|
|
|
1. `GET /api/session` aufrufen, damit das Backend ein `connect.sid`-Cookie setzt und die `sessionId` zurueckgibt.
|
|
2. Socket.IO zu derselben Basis-URL verbinden.
|
|
3. Nach `connect` das Event `setSessionId` senden:
|
|
|
|
```json
|
|
{
|
|
"expressSessionId": "<sessionId aus /api/session>"
|
|
}
|
|
```
|
|
|
|
4. Server sendet `connected`.
|
|
5. Wenn `connected.loggedIn === true`, App-State wiederherstellen.
|
|
6. Sonst Login-Screen anzeigen.
|
|
|
|
### Client sendet
|
|
|
|
| Event | Payload | Zweck |
|
|
|---|---|---|
|
|
| `setSessionId` | `{ "expressSessionId": string }` | Socket mit Express-Session verknuepfen |
|
|
| `login` | `{ "userName": string, "gender": string, "age": number, "country": string, "expressSessionId": string }` | Chat-Login |
|
|
| `message` | `{ "toUserName": string, "message": string, "messageId": string }` | Textnachricht |
|
|
| `message` | `{ "toUserName": string, "message": imageCode, "messageId": string, "isImage": true, "imageUrl": string }` | Bildnachricht nach REST-Upload |
|
|
| `requestConversation` | `{ "withUserName": string }` | Konversation laden und als gelesen markieren |
|
|
| `userSearch` | `{ "nameIncludes"?, "minAge"?, "maxAge"?, "countries"?, "genders"? }` | Benutzer suchen |
|
|
| `requestHistory` | kein Payload | Chatverlauf-Liste laden |
|
|
| `requestOpenConversations` | kein Payload | Inbox/ungelesene Chats laden |
|
|
| `blockUser` | `{ "userName": string }` | Benutzer blockieren |
|
|
| `unblockUser` | `{ "userName": string }` | Benutzer entblockieren |
|
|
|
|
### Server sendet
|
|
|
|
| Event | Payload | Android-State |
|
|
|---|---|---|
|
|
| `connected` | `{ "sessionId": string, "loggedIn"?: boolean, "user"?: User }` | Session setzen, ggf. Login wiederherstellen |
|
|
| `loginSuccess` | `{ "sessionId": string, "user": User }` | Login-State setzen |
|
|
| `userList` | `{ "users": User[] }` | Online-Liste aktualisieren |
|
|
| `message` | `{ "from": string, "message": string, "messageId": string, "timestamp": string, "isImage"?, "imageUrl"?, "imageCode"? }` | Nachricht empfangen |
|
|
| `messageSent` | `{ "messageId": string, "to": string }` | lokale Nachricht bestaetigen |
|
|
| `conversation` | `{ "with": string, "messages": Message[] }` | Konversation anzeigen |
|
|
| `searchResults` | `{ "results": User[] }` | Suchergebnisse anzeigen |
|
|
| `historyResults` | `{ "results": HistoryItem[] }` | Verlauf anzeigen |
|
|
| `inboxResults` | `{ "results": InboxItem[] }` | Inbox anzeigen |
|
|
| `unreadChats` | `{ "count": number }` | Badge aktualisieren |
|
|
| `commandResult` | `{ "lines": string[], "kind": string }` | Admin-/Slash-Command-Hinweise anzeigen |
|
|
| `commandTable` | `{ "title": string, "columns": string[], "rows": unknown[][] }` | Admin-/Statistik-Tabelle anzeigen |
|
|
| `userBlocked` | `{ "userName": string }` | Blockierstatus setzen |
|
|
| `userUnblocked` | `{ "userName": string }` | Blockierstatus entfernen |
|
|
| `error` | `{ "message": string }` | Snackbar/Dialog anzeigen |
|
|
|
|
## Android-Modulstruktur
|
|
|
|
Vorschlag fuer ein neues Modul oder separates Repo:
|
|
|
|
```text
|
|
android/
|
|
app/
|
|
src/main/
|
|
java/net/ypchat/app/
|
|
MainActivity.kt
|
|
YpChatApp.kt
|
|
core/
|
|
Config.kt
|
|
SessionCookieJar.kt
|
|
NetworkModule.kt
|
|
data/
|
|
api/
|
|
RestApi.kt
|
|
SocketClient.kt
|
|
model/
|
|
UserDto.kt
|
|
MessageDto.kt
|
|
ConversationDto.kt
|
|
repository/
|
|
ChatRepository.kt
|
|
FeedbackRepository.kt
|
|
ui/
|
|
login/
|
|
chat/
|
|
users/
|
|
search/
|
|
inbox/
|
|
history/
|
|
feedback/
|
|
common/
|
|
```
|
|
|
|
### Verantwortlichkeiten
|
|
|
|
- `RestApi`: Retrofit-Definitionen fuer `/api/*`
|
|
- `SocketClient`: kapselt Socket.IO-Verbindung, Event-Handler und Emits
|
|
- `ChatRepository`: verbindet REST, Socket und lokalen App-State
|
|
- `ChatViewModel`: bietet `StateFlow<ChatUiState>` fuer Compose
|
|
- `SessionCookieJar`: persistiert `connect.sid`, damit REST und Socket dieselbe Session nutzen
|
|
- `ImageUploader`: Photo Picker, Komprimierung falls noetig, Multipart Upload
|
|
|
|
## UI-Konzept
|
|
|
|
### Navigation
|
|
|
|
MVP-Screens:
|
|
|
|
- Login
|
|
- Online-Benutzer
|
|
- Suche
|
|
- Chat
|
|
- Inbox
|
|
- Verlauf
|
|
- Profil/Logout
|
|
|
|
Optional:
|
|
|
|
- Feedback
|
|
- Partner
|
|
- Regeln/Sicherheit/FAQ als native Info-Screens oder WebContent aus statischen Texten
|
|
|
|
### Mobile UX
|
|
|
|
Der Webclient ist desktop-/browsernah. Die App sollte mobiler denken:
|
|
|
|
- Startet in Login oder zuletzt aktiver Chat-Ansicht
|
|
- Bottom Navigation fuer `Online`, `Suche`, `Inbox`, `Verlauf`
|
|
- Chat-Screen mit fester Eingabezeile unten
|
|
- Online-Status und Flagge direkt in User-Zeilen
|
|
- Unread-Badge auf Inbox-Tab
|
|
- Bildauswahl ueber Android Photo Picker
|
|
- Fehlermeldungen als Snackbar, kritische Session-Fehler als Dialog
|
|
|
|
## Session- und Reconnect-Konzept
|
|
|
|
### Persistenz
|
|
|
|
Gespeichert werden lokal:
|
|
|
|
- `connect.sid` Cookie
|
|
- letzter bekannter Login-State fuer UI-Skeleton
|
|
- Logout-Marker, analog `singlechat_logged_out` im Web
|
|
- keine Chatnachrichten dauerhaft im MVP, weil Backend diese aktuell nur im Arbeitsspeicher haelt
|
|
|
|
### Reconnect
|
|
|
|
Empfohlener Ablauf:
|
|
|
|
1. App startet.
|
|
2. Wenn kein Logout-Marker vorhanden: `GET /api/session`.
|
|
3. Wenn Session `loggedIn`: Socket verbinden und `setSessionId` senden.
|
|
4. Wenn Socket reconnectet: erneut `GET /api/session`, dann `setSessionId`.
|
|
5. Bei `connect_error`: exponentielles Retry mit sichtbarem Offline-Banner.
|
|
6. Bei Logout: `POST /api/logout`, Socket disconnect, Cookie loeschen, Logout-Marker setzen.
|
|
|
|
### Inaktivitaet
|
|
|
|
Backend und Webclient verwenden 30 Minuten. Android sollte dieselbe Regel im UI spiegeln:
|
|
|
|
- Timer startet nach `loginSuccess` oder Session-Restore.
|
|
- Timer wird bei Senden, Empfangen, Suche und Conversation Requests zurueckgesetzt.
|
|
- Bei Ablauf: lokaler Logout und optional `POST /api/logout`.
|
|
|
|
## Bildversand
|
|
|
|
Ablauf:
|
|
|
|
1. Benutzer waehlt Bild per Photo Picker.
|
|
2. App prueft MIME-Type und Groesse.
|
|
3. Optional: Bild auf sinnvolle Chat-Groesse komprimieren, z.B. max. 1600 px Kantenlaenge.
|
|
4. `POST /api/upload-image` als Multipart `image`.
|
|
5. Backend antwortet mit:
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"code": "<code>",
|
|
"url": "/api/image/<code>"
|
|
}
|
|
```
|
|
|
|
6. App sendet Socket-Event `message` mit `isImage: true`, `message: code`, `imageUrl: url`.
|
|
7. Anzeige erfolgt ueber volle URL `https://www.ypchat.net/api/image/<code>`.
|
|
|
|
Hinweis: Der Backend-Endpunkt erlaubt 5 MB Upload und haelt Bilder temporaer fuer 6 Stunden.
|
|
|
|
## Backend-Anpassungen Vor Android-Release
|
|
|
|
Die App kann grundsaetzlich mit dem aktuellen Backend starten. Sinnvolle kleine Anpassungen wuerden die Mobile-Integration aber robuster machen:
|
|
|
|
- CORS ist fuer mobile Apps weniger kritisch, aber Socket.IO-Origin-Handling sollte getestet werden, weil native Clients oft keinen Browser-Origin senden.
|
|
- Session-Handling sollte fuer Android explizit dokumentiert werden: `GET /api/session` vor Socket-Verbindung.
|
|
- WebSocket ueber Apache sollte sauber funktionieren. Android kann Polling fallbacken, aber echte WebSocket-Verbindung ist fuer Akku und Latenz besser.
|
|
- `/api/upload-image` hat aktuell einen Fallback auf den zuletzt aktiven Client. Fuer Mobile waere sauberer: eindeutig ueber `req.sessionID` validieren und keine Aktivitaets-Heuristik verwenden.
|
|
- Nachrichten und Konversationen liegen aktuell im Arbeitsspeicher. Fuer eine App mit Reconnect/Background-Nutzung sollte mittelfristig Persistenz ergaenzt werden.
|
|
- Optional: API-Versionierung einfuehren, z.B. `/api/v1/session`, bevor App-Versionen langfristig im Umlauf sind.
|
|
|
|
## Datenschutz Und Store-Themen
|
|
|
|
Vor Veroeffentlichung im Play Store beachten:
|
|
|
|
- Datenschutzerklaerung direkt in App verlinken
|
|
- klare Alters-/Community-Regeln im Onboarding
|
|
- Hinweis, dass Chatnachrichten temporaer serverseitig verarbeitet werden
|
|
- Bild-Upload transparent erklaeren
|
|
- Melde-/Blockierfunktion prominent erreichbar machen
|
|
- Android `INTERNET` Permission erforderlich
|
|
- Keine Speicherpermission noetig, wenn Android Photo Picker genutzt wird
|
|
|
|
Wenn die App spaeter Push Notifications bekommt, braucht es FCM, Datenschutz-Ergaenzung und serverseitige Device-Token-Verwaltung.
|
|
|
|
## MVP-Backlog
|
|
|
|
### Phase 1: Android-Projekt
|
|
|
|
- Gradle/Kotlin/Compose-Projekt anlegen
|
|
- App-Theme und Basisnavigation erstellen
|
|
- `Config` fuer Dev/Prod-Basis-URL
|
|
- OkHttp, Retrofit und Socket.IO-Client einrichten
|
|
- Persistente CookieJar implementieren
|
|
|
|
### Phase 2: Session Und Login
|
|
|
|
- `GET /api/session`
|
|
- Socket-Verbindung mit `setSessionId`
|
|
- Login-Screen
|
|
- `login` Event
|
|
- `connected`, `loginSuccess`, `error`
|
|
- Logout mit `/api/logout`
|
|
|
|
### Phase 3: Chat-Kern
|
|
|
|
- Online-Userliste via `userList`
|
|
- Chat-Screen
|
|
- `requestConversation`
|
|
- Textnachrichten senden/empfangen
|
|
- `messageSent`, `unreadChats`
|
|
- Reconnect und Offline-Banner
|
|
|
|
### Phase 4: Suche, Inbox, Verlauf
|
|
|
|
- Suchformular und `userSearch`
|
|
- Suchergebnisse
|
|
- Inbox mit `requestOpenConversations`
|
|
- Verlauf mit `requestHistory`
|
|
- Blockieren/Entblockieren
|
|
|
|
### Phase 5: Bilder
|
|
|
|
- Android Photo Picker
|
|
- Multipart Upload zu `/api/upload-image`
|
|
- Bildnachricht via Socket senden
|
|
- Bildanzeige ueber `/api/image/:code`
|
|
- Fehlerbehandlung fuer abgelaufene Bilder
|
|
|
|
### Phase 6: Release-Haertung
|
|
|
|
- ProGuard/R8-Regeln fuer Socket.IO/OkHttp testen
|
|
- Crash-/Error-Logging entscheiden
|
|
- Play Store Icons, Screenshots, App Name
|
|
- Datenschutz-/Impressum-/Regeln-Screens
|
|
- Test auf Emulator, echtem Android-Geraet, schlechtem Netz und App-Background
|
|
|
|
## Risiken
|
|
|
|
| Risiko | Auswirkung | Gegenmassnahme |
|
|
|---|---|---|
|
|
| Socket.IO Android Client Version passt nicht zum Server | Verbindungsfehler | Version gegen Socket.IO Server 4.x testen, `allowEIO3` ist serverseitig aktiv |
|
|
| Session-Cookie wird nicht zwischen REST und Socket geteilt | Login/Upload funktionieren inkonsistent | gemeinsame OkHttp CookieJar und expliziter `setSessionId` Flow |
|
|
| Apache WebSocket-Proxy ist instabil | Reconnects/Latenz | WebSocket-Regeln pruefen, Polling als Fallback |
|
|
| Backend speichert Chat nur im RAM | Nachrichten nach Server-Restart weg | fuer MVP akzeptieren, spaeter DB-Persistenz |
|
|
| App im Hintergrund verliert Socket | Nachrichten kommen nur bei geoeffneter App | fuer MVP akzeptieren, spaeter Push Notifications |
|
|
| Bild-Upload-Session-Fallback ist unscharf | falsche Zuordnung theoretisch moeglich | Backend vor Release eindeutiger machen |
|
|
|
|
## Offene Entscheidungen
|
|
|
|
- Soll Android zuerst als separates Repo oder als `android/` Ordner in diesem Repo entstehen?
|
|
- Soll der MVP nur Chat enthalten oder auch Feedback/Partner/FAQ?
|
|
- Soll das bestehende Design exakt nachgebaut oder fuer Mobile bewusst neu interpretiert werden?
|
|
- Soll die erste Version ohne Push Notifications starten?
|
|
|
|
## Empfehlung Fuer Den Naechsten Schritt
|
|
|
|
Ich wuerde als naechstes ein Android-Projekt im Ordner `android/` scaffolden und zuerst nur den technischen Durchstich bauen:
|
|
|
|
1. `GET /api/session`
|
|
2. Socket.IO connect
|
|
3. `setSessionId`
|
|
4. Login
|
|
5. Empfang von `userList`
|
|
6. Senden und Empfangen einer Textnachricht
|
|
|
|
Wenn dieser Durchstich steht, ist der groesste technische Unsicherheitsblock geloest. Danach ist der Rest vor allem UI- und Zustandsarbeit.
|