fix(status): AMSAT page load failure, refresh spinner, retry button

- P0: fetchStatus() now runs on Dispatchers.IO - the previous
  synchronous URLConnection on the main thread threw
  NetworkOnMainThreadException and showed "load failed" on every open
- P1: refresh button rotates a vector icon (ic_refresh) instead of the
  "↻" text glyph, whose off-center font metrics made the spinner
  orbit around a shifted pivot
- P2: error state gains a Retry button (4 locales); amsat_refresh
  string added (5 locales)
- versionCode 456 (bump for reinstalling over 455), versionName stays 4.5.5
This commit is contained in:
mckero committed 2026-08-05 08:50:01 +00:00
1 parent 6221516893
commit f789a15338
9 files changed
+47 -10

No files matched your search

@@ -8,20 +8,22 @@ import com.rtbishop.look4sat.core.domain.model.SatSlot
import com.rtbishop.look4sat.core.domain.model.SatStatus
import com.rtbishop.look4sat.core.domain.model.SatStatusPage
import com.rtbishop.look4sat.core.domain.repository.IAmSatRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Calendar
import java.util.TimeZone
/** AMSAT status repository: official API v1 -> SatStatusPage (replaces the HTML parser). */
class AmSatRepository(private val apiClient: AmSatApiClient) : IAmSatRepository {
override suspend fun fetchStatus(): SatStatusPage? {
override suspend fun fetchStatus(): SatStatusPage? = withContext(Dispatchers.IO) {
val nowSec = System.currentTimeMillis() / 1000
val names = apiClient.fetchCatalog()
val reports = apiClient.fetchAllReports(hours = 168)
if (names.isEmpty() && reports.isEmpty()) return null
if (names.isEmpty() && reports.isEmpty()) return@withContext null
val statuses = buildStatuses(names, reports, nowSec)
val reportMap = reports.associate { it.id to toSatReport(it) }
return SatStatusPage(System.currentTimeMillis(), statuses, reportMap)
SatStatusPage(System.currentTimeMillis(), statuses, reportMap)
}
/** Build one SatStatus (6 days x 12 slots) per catalog satellite, slotting reports by age. */
@@ -46,6 +46,8 @@
<string name="nav_log">Log</string>
<string name="nav_amsat">AMSAT</string>
<string name="amsat_title">Status Satelit AMSAT</string>
<string name="amsat_refresh">Segarkan</string>
<string name="amsat_retry">Coba lagi</string>
<string name="amsat_updated">Diperbarui:</string>
<string name="amsat_active">Aktif</string>
<string name="amsat_tlm">TLM/beacon</string>
@@ -46,6 +46,8 @@
<string name="nav_log">Log</string>
<string name="nav_amsat">AMSAT</string>
<string name="amsat_title">Status Satelit AMSAT</string>
<string name="amsat_refresh">Segarkan</string>
<string name="amsat_retry">Coba lagi</string>
<string name="amsat_updated">Diperbarui:</string>
<string name="amsat_active">Aktif</string>
<string name="amsat_tlm">TLM/beacon</string>
@@ -47,6 +47,8 @@
<string name="nav_log">Günlük</string>
<string name="nav_amsat">AMSAT</string>
<string name="amsat_title">AMSAT Uydu Durumu</string>
<string name="amsat_refresh">Yenile</string>
<string name="amsat_retry">Tekrar dene</string>
<string name="amsat_updated">Güncellendi:</string>
<string name="amsat_active">Aktif</string>
<string name="amsat_tlm">TLM/işaret</string>
@@ -48,6 +48,8 @@
<string name="nav_log">日志</string>
<string name="nav_amsat">AMSAT</string>
<string name="amsat_title">AMSAT 卫星状态</string>
<string name="amsat_refresh">刷新</string>
<string name="amsat_retry">重试</string>
<string name="amsat_updated">更新:</string>
<string name="amsat_active">活跃</string>
<string name="amsat_tlm">仅遥测</string>
@@ -49,6 +49,8 @@
<string name="nav_log">Log</string>
<string name="nav_amsat">AMSAT</string>
<string name="amsat_title">AMSAT Satellite Status</string>
<string name="amsat_refresh">Refresh</string>
<string name="amsat_retry">Retry</string>
<string name="amsat_updated">Updated:</string>
<string name="amsat_active">Active</string>
<string name="amsat_tlm">TLM/Beacon</string>
@@ -7,6 +7,7 @@ import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -20,6 +21,8 @@ import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -56,6 +59,7 @@ import com.rtbishop.look4sat.core.domain.model.SatSlot
import com.rtbishop.look4sat.core.domain.model.SatStatus
import com.rtbishop.look4sat.core.domain.repository.IMainContainer
import com.rtbishop.look4sat.core.presentation.R
import com.rtbishop.look4sat.feature.status.R as StatusR
import java.util.Calendar
// ========== Official status colors (amsat.org/status originals) ==========
@@ -97,11 +101,18 @@ fun SatStatusScreen(container: IMainContainer) {
}
}
uiState.error != null && uiState.statuses.isEmpty() -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.amsat_load_failed),
color = MaterialTheme.colorScheme.error
)
TextButton(onClick = { viewModel.refresh() }) {
Text(text = stringResource(id = R.string.amsat_retry))
}
}
}
else -> {
@@ -169,11 +180,13 @@ private fun StatusHeader(
if (isRefreshing) {
CircularProgressIndicator(modifier = Modifier.size(18.dp), strokeWidth = 2.dp)
} else {
Text(
text = "↻",
fontSize = 20.sp,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.rotate(angle)
Image(
painter = painterResource(id = StatusR.drawable.ic_refresh),
contentDescription = stringResource(id = R.string.amsat_refresh),
modifier = Modifier
.size(18.dp)
.rotate(angle),
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary)
)
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Standard Material refresh icon (24dp viewport), centered geometry -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
</vector>
+1 -1
View File
@@ -1,6 +1,6 @@
[versions]
#noinspection UnusedVersionCatalogEntry
appVersionCode = "455"
appVersionCode = "456"
#noinspection UnusedVersionCatalogEntry
appVersionName = "4.5.5"
#noinspection UnusedVersionCatalogEntry