This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user