diff --git a/android/README.md b/android/README.md index 87affaa..7fe8906 100755 --- a/android/README.md +++ b/android/README.md @@ -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. diff --git a/android/app/src/main/java/de/ypchat/android/MainActivity.kt b/android/app/src/main/java/de/ypchat/android/MainActivity.kt index 172d2f6..d2f7a67 100755 --- a/android/app/src/main/java/de/ypchat/android/MainActivity.kt +++ b/android/app/src/main/java/de/ypchat/android/MainActivity.kt @@ -17,4 +17,9 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { YpChatRoot(viewModel) } } + + override fun onResume() { + super.onResume() + viewModel.reconnectIfNeeded() + } } diff --git a/android/app/src/main/java/de/ypchat/android/core/ProfileStore.kt b/android/app/src/main/java/de/ypchat/android/core/ProfileStore.kt index 7a3dd84..ad9e263 100755 --- a/android/app/src/main/java/de/ypchat/android/core/ProfileStore.kt +++ b/android/app/src/main/java/de/ypchat/android/core/ProfileStore.kt @@ -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" } } diff --git a/android/app/src/main/java/de/ypchat/android/data/repository/ChatRepository.kt b/android/app/src/main/java/de/ypchat/android/data/repository/ChatRepository.kt index 289b852..0268dc6 100755 --- a/android/app/src/main/java/de/ypchat/android/data/repository/ChatRepository.kt +++ b/android/app/src/main/java/de/ypchat/android/data/repository/ChatRepository.kt @@ -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, diff --git a/android/app/src/main/java/de/ypchat/android/ui/ChatViewModel.kt b/android/app/src/main/java/de/ypchat/android/ui/ChatViewModel.kt index 1c75af0..0e30733 100755 --- a/android/app/src/main/java/de/ypchat/android/ui/ChatViewModel.kt +++ b/android/app/src/main/java/de/ypchat/android/ui/ChatViewModel.kt @@ -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)