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 ypchat.baseUrl=http://192.168.178.42:3300
``` ```
## Implementierter Durchstich ## Implementierte Funktionen
- `GET /api/session` - `GET /api/session`
- Socket.IO Connect mit WebSocket zuerst und Polling-Fallback - Socket.IO Connect mit WebSocket zuerst und Polling-Fallback
@@ -61,11 +61,37 @@ ypchat.baseUrl=http://192.168.178.42:3300
- `blockUser` - `blockUser`
- `POST /api/logout` - `POST /api/logout`
- Laenderliste im Login ueber `/api/countries` - 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 Der Debug- und der minifizierte Release-Build werden erfolgreich erzeugt:
- lokale Logout-Markierung analog Web-Frontend speichern
- Reconnect-Feinschliff nach App-Background testen ```text
- Play-Store-Texte, Datenschutz, Impressum und App-Icons finalisieren ./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) super.onCreate(savedInstanceState)
setContent { YpChatRoot(viewModel) } setContent { YpChatRoot(viewModel) }
} }
override fun onResume() {
super.onResume()
viewModel.reconnectIfNeeded()
}
} }

View File

@@ -32,10 +32,25 @@ class ProfileStore(context: Context) {
preferences.edit().clear().apply() 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 { private companion object {
const val KEY_NICKNAME = "nickname" const val KEY_NICKNAME = "nickname"
const val KEY_GENDER = "gender" const val KEY_GENDER = "gender"
const val KEY_AGE = "age" const val KEY_AGE = "age"
const val KEY_COUNTRY = "country" 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()) _state.value = _state.value.copy(savedProfile = profileStore.read())
loadCountries() loadCountries()
loadFeedbackAdminStatus() loadFeedbackAdminStatus()
if (profileStore.wasLocallyLoggedOut()) return
runCatching { restApi.session() } runCatching { restApi.session() }
.onSuccess { session -> .onSuccess { session ->
_state.value = _state.value.copy( _state.value = _state.value.copy(
@@ -99,6 +100,7 @@ class ChatRepository(
} }
suspend fun login(userName: String, gender: String, age: Int, country: String) { suspend fun login(userName: String, gender: String, age: Int, country: String) {
profileStore.clearLocalLogoutMarker()
profileStore.write(SavedProfile(userName, gender, age, country)) profileStore.write(SavedProfile(userName, gender, age, country))
val session = runCatching { restApi.session() }.getOrNull() val session = runCatching { restApi.session() }.getOrNull()
val sessionId = session?.sessionId ?: _state.value.expressSessionId val sessionId = session?.sessionId ?: _state.value.expressSessionId
@@ -111,6 +113,7 @@ class ChatRepository(
} }
suspend fun logout() { suspend fun logout() {
profileStore.markLocallyLoggedOut()
runCatching { restApi.logout() } runCatching { restApi.logout() }
socketClient.disconnect() socketClient.disconnect()
cookieJar.clear() cookieJar.clear()
@@ -123,6 +126,12 @@ class ChatRepository(
expressSessionId?.let { socketClient.setSessionId(it) } expressSessionId?.let { socketClient.setSessionId(it) }
} }
fun reconnectIfNeeded() {
if (_state.value.isLoggedIn && !socketClient.isConnected) {
connectSocket()
}
}
fun openConversation(userName: String) { fun openConversation(userName: String) {
_state.value = _state.value.copy( _state.value = _state.value.copy(
currentConversation = userName, currentConversation = userName,

View File

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