feat(Networking): enhance offline handling and localization for diary and members data
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
- Added `ACCESS_NETWORK_STATE` permission in AndroidManifest.xml to monitor network connectivity. - Introduced `NetworkConnectivityHolder` to manage network state and trigger data refresh when connectivity is restored. - Updated `DiaryManager` and `MembersManager` to support offline caching, allowing users to view previously loaded data when offline. - Enhanced localization by adding new keys for offline cache hints in both German and English, improving user experience during connectivity issues. - Updated UI components to display offline cache messages, ensuring users are informed when data is being served from cache.
This commit is contained in:
@@ -1341,6 +1341,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Keine Verbindung – zuletzt geladene Daten.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -3853,6 +3854,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Keine Verbindung – zuletzt geladene Daten.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -6366,6 +6368,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Keine Verbindung – zuletzt geladene Daten.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -8872,6 +8875,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "No members found",
|
||||
"mobile.noResults" to "No results",
|
||||
"mobile.noTimes" to "No times",
|
||||
"mobile.offlineCacheHint" to "No connection – showing last loaded data.",
|
||||
"mobile.participationTop" to "Top attendance",
|
||||
"mobile.refresh" to "Refresh",
|
||||
"mobile.requestedAccess" to "Requested",
|
||||
@@ -11378,6 +11382,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "No members found",
|
||||
"mobile.noResults" to "No results",
|
||||
"mobile.noTimes" to "No times",
|
||||
"mobile.offlineCacheHint" to "No connection – showing last loaded data.",
|
||||
"mobile.participationTop" to "Top attendance",
|
||||
"mobile.refresh" to "Refresh",
|
||||
"mobile.requestedAccess" to "Requested",
|
||||
@@ -13884,6 +13889,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "No members found",
|
||||
"mobile.noResults" to "No results",
|
||||
"mobile.noTimes" to "No times",
|
||||
"mobile.offlineCacheHint" to "No connection – showing last loaded data.",
|
||||
"mobile.participationTop" to "Top attendance",
|
||||
"mobile.refresh" to "Refresh",
|
||||
"mobile.requestedAccess" to "Requested",
|
||||
@@ -16390,6 +16396,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Sin conexión: se muestran los datos cargados por última vez.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -18896,6 +18903,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Walang koneksyon – ipinapakita ang huling na-load na datos.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -21402,6 +21410,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Pas de connexion – affichage des données du dernier chargement.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -23908,6 +23917,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Nessuna connessione – dati dell’ultimo caricamento.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -26414,6 +26424,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "ネットワークに接続されていません。最後に読み込んだデータを表示しています。",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -28920,6 +28931,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Brak połączenia – wyświetlane ostatnio wczytane dane.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -31426,6 +31438,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "ไม่มีการเชื่อมต่อ – แสดงข้อมูลที่โหลดล่าสุด",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -33932,6 +33945,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "Walang koneksyon – ipinapakita ang huling na-load na datos.",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
@@ -36438,6 +36452,7 @@ object MobileStrings {
|
||||
"mobile.noMembers" to "Keine Mitglieder gefunden",
|
||||
"mobile.noResults" to "Keine Treffer",
|
||||
"mobile.noTimes" to "Keine Zeiten",
|
||||
"mobile.offlineCacheHint" to "无网络连接,显示上次加载的数据。",
|
||||
"mobile.participationTop" to "Top Teilnahmen",
|
||||
"mobile.refresh" to "Aktualisieren",
|
||||
"mobile.requestedAccess" to "Angefragt",
|
||||
|
||||
@@ -224,12 +224,32 @@ class DiaryManager(
|
||||
}
|
||||
|
||||
suspend fun loadDates(clubId: Int) {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
val prev = _state.value
|
||||
val base = if (prev.lastLoadedClubId != null && prev.lastLoadedClubId != clubId) {
|
||||
prev.copy(dates = emptyList(), lastLoadedClubId = null, fromOfflineCache = false, error = null)
|
||||
} else {
|
||||
prev
|
||||
}
|
||||
_state.value = base.copy(isLoading = true, error = null)
|
||||
try {
|
||||
val dates = diaryApi.listDates(clubId)
|
||||
_state.value = _state.value.copy(isLoading = false, dates = dates)
|
||||
_state.value = base.copy(
|
||||
isLoading = false,
|
||||
dates = dates,
|
||||
lastLoadedClubId = clubId,
|
||||
fromOfflineCache = false,
|
||||
error = null,
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
_state.value = _state.value.copy(isLoading = false, error = t.toUserMessage("Fehler beim Laden des Tagebuchs"))
|
||||
if (base.lastLoadedClubId == clubId && base.dates.isNotEmpty()) {
|
||||
_state.value = base.copy(isLoading = false, fromOfflineCache = true, error = null)
|
||||
} else {
|
||||
_state.value = base.copy(
|
||||
isLoading = false,
|
||||
error = t.toUserMessage("Fehler beim Laden des Tagebuchs"),
|
||||
fromOfflineCache = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,9 @@ data class DiaryState(
|
||||
val dates: List<DiaryDate> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
/** Zuletzt erfolgreich geladener Verein (für Offline-Cache). */
|
||||
val lastLoadedClubId: Int? = null,
|
||||
/** `true`, wenn die angezeigte Tagebuch-Liste von einem fehlgeschlagenen Refresh stammt. */
|
||||
val fromOfflineCache: Boolean = false,
|
||||
)
|
||||
|
||||
|
||||
@@ -32,7 +32,13 @@ class MembersManager(
|
||||
val state: StateFlow<MembersState> = _state.asStateFlow()
|
||||
|
||||
suspend fun loadMembers(clubId: Int) {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
val prev = _state.value
|
||||
val base = if (prev.lastLoadedClubId != null && prev.lastLoadedClubId != clubId) {
|
||||
prev.copy(members = emptyList(), lastLoadedClubId = null, fromOfflineCache = false, error = null)
|
||||
} else {
|
||||
prev
|
||||
}
|
||||
_state.value = base.copy(isLoading = true, error = null)
|
||||
try {
|
||||
val members = membersApi.listMembers(clubId)
|
||||
val merged = runCatching { trainingStatsApi.getStats(clubId) }
|
||||
@@ -41,9 +47,23 @@ class MembersManager(
|
||||
onFailure = { members },
|
||||
)
|
||||
.sortedWith(compareBy<Member> { it.lastName.lowercase() }.thenBy { it.firstName.lowercase() })
|
||||
_state.value = _state.value.copy(members = merged, isLoading = false)
|
||||
_state.value = base.copy(
|
||||
members = merged,
|
||||
isLoading = false,
|
||||
lastLoadedClubId = clubId,
|
||||
fromOfflineCache = false,
|
||||
error = null,
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
_state.value = _state.value.copy(isLoading = false, error = t.toUserMessage("Mitglieder konnten nicht geladen werden"))
|
||||
if (base.lastLoadedClubId == clubId && base.members.isNotEmpty()) {
|
||||
_state.value = base.copy(isLoading = false, fromOfflineCache = true, error = null)
|
||||
} else {
|
||||
_state.value = base.copy(
|
||||
isLoading = false,
|
||||
error = t.toUserMessage("Mitglieder konnten nicht geladen werden"),
|
||||
fromOfflineCache = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,7 @@ data class MembersState(
|
||||
val members: List<Member> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val lastLoadedClubId: Int? = null,
|
||||
val fromOfflineCache: Boolean = false,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user