Android app
All checks were successful
Deploy SingleChat / deploy (push) Successful in 26s

This commit is contained in:
Torsten Schulz (local)
2026-08-10 15:03:35 +02:00
parent bfe28a25fa
commit 86e90c351f
5 changed files with 64 additions and 7 deletions

View File

@@ -46,7 +46,7 @@ ypchat.baseUrl=http://10.0.2.2:3300
ypchat.baseUrl=http://192.168.178.42:3300
```
## Implementierter Durchstich
## Implementierte Funktionen
- `GET /api/session`
- Socket.IO Connect mit WebSocket zuerst und Polling-Fallback
@@ -61,11 +61,37 @@ ypchat.baseUrl=http://192.168.178.42:3300
- `blockUser`
- `POST /api/logout`
- Laenderliste im Login ueber `/api/countries`
- Upload-Client fuer `/api/upload-image` im Repository vorbereitet
- Android Photo Picker und Kameraaufnahme
- Bildupload ueber `/api/upload-image` mit 5-MB-Grenze
- lokale Logout-Markierung: Nach einem expliziten Logout wird eine noch
vorhandene Server-Session nicht automatisch wiederhergestellt
- Socket.IO-Reconnect mit erneutem Setzen der Express-Session; beim
Zurueckkehren in die App wird eine fehlende Verbindung erneut aufgebaut
- Videochat mit Kamera- und Mikrofonfreigabe
- Feedback, Partner, FAQ, Regeln, Sicherheit, Datenschutz und Impressum
## Noch Offen
## Build-Status
- Android Photo Picker an `uploadImage()` anschliessen
- lokale Logout-Markierung analog Web-Frontend speichern
- Reconnect-Feinschliff nach App-Background testen
- Play-Store-Texte, Datenschutz, Impressum und App-Icons finalisieren
Der Debug- und der minifizierte Release-Build werden erfolgreich erzeugt:
```text
./gradlew :app:assembleDebug
./gradlew :app:assembleRelease
```
## Release-Abnahme vor Google Play
Die folgenden Punkte sind keine fehlenden App-Funktionen, sondern muessen vor
dem Veroeffentlichen mit realen Konten und Geraeten abgenommen werden:
1. Login, Chat, Bildversand, Blockieren und Logout auf mindestens zwei echten
Android-Geraeten pruefen.
2. Die App waehrend eines Chats in den Hintergrund legen, wieder oeffnen und
den Socket-Reconnect pruefen.
3. Einen Videoanruf zwischen zwei echten Geraeten inklusive Kamera-, Mikrofon-
und Berechtigungsdialog pruefen.
4. Die Angaben in `DATA_SAFETY_DRAFT.md` mit dem tatsaechlichen
Server-/Loeschprozess abgleichen und die Datenschutzerklaerung in der Play
Console hinterlegen.
5. Store-Name, Kategorie und die vorhandenen Store-Grafiken final pruefen;
die Texte und Checklisten liegen in den weiteren Dateien dieses Ordners.

View File

@@ -17,4 +17,9 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent { YpChatRoot(viewModel) }
}
override fun onResume() {
super.onResume()
viewModel.reconnectIfNeeded()
}
}

View File

@@ -32,10 +32,25 @@ class ProfileStore(context: Context) {
preferences.edit().clear().apply()
}
/**
* Prevents an existing server session from silently logging a user back in
* after they explicitly chose to log out on this device.
*/
fun markLocallyLoggedOut() {
preferences.edit().putBoolean(KEY_LOGGED_OUT, true).apply()
}
fun clearLocalLogoutMarker() {
preferences.edit().remove(KEY_LOGGED_OUT).apply()
}
fun wasLocallyLoggedOut(): Boolean = preferences.getBoolean(KEY_LOGGED_OUT, false)
private companion object {
const val KEY_NICKNAME = "nickname"
const val KEY_GENDER = "gender"
const val KEY_AGE = "age"
const val KEY_COUNTRY = "country"
const val KEY_LOGGED_OUT = "logged_out"
}
}

View File

@@ -59,6 +59,7 @@ class ChatRepository(
_state.value = _state.value.copy(savedProfile = profileStore.read())
loadCountries()
loadFeedbackAdminStatus()
if (profileStore.wasLocallyLoggedOut()) return
runCatching { restApi.session() }
.onSuccess { session ->
_state.value = _state.value.copy(
@@ -99,6 +100,7 @@ class ChatRepository(
}
suspend fun login(userName: String, gender: String, age: Int, country: String) {
profileStore.clearLocalLogoutMarker()
profileStore.write(SavedProfile(userName, gender, age, country))
val session = runCatching { restApi.session() }.getOrNull()
val sessionId = session?.sessionId ?: _state.value.expressSessionId
@@ -111,6 +113,7 @@ class ChatRepository(
}
suspend fun logout() {
profileStore.markLocallyLoggedOut()
runCatching { restApi.logout() }
socketClient.disconnect()
cookieJar.clear()
@@ -123,6 +126,12 @@ class ChatRepository(
expressSessionId?.let { socketClient.setSessionId(it) }
}
fun reconnectIfNeeded() {
if (_state.value.isLoggedIn && !socketClient.isConnected) {
connectSocket()
}
}
fun openConversation(userName: String) {
_state.value = _state.value.copy(
currentConversation = userName,

View File

@@ -37,6 +37,8 @@ class ChatViewModel(private val repository: ChatRepository) : ViewModel() {
viewModelScope.launch { repository.logout() }
}
fun reconnectIfNeeded() = repository.reconnectIfNeeded()
fun openConversation(userName: String) = repository.openConversation(userName)
fun closeConversation() = repository.closeConversation()
fun sendMessage(text: String) = repository.sendMessage(text)