mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Added AMSAT satellite status reports submission (#248)
Co-authored-by: atsunatsu <atsunatsu@users.noreply.github.com>
This commit is contained in:
1 parent
796056a582
commit
c2fc5688b9
15 files changed
+659
-27
No files matched your search
@@ -1,5 +1,7 @@
|
||||
package com.rtbishop.look4sat.core.data.repository
|
||||
|
||||
import com.rtbishop.look4sat.core.domain.model.AmSatReportSubmission
|
||||
import com.rtbishop.look4sat.core.domain.model.AmSatReportSubmitResult
|
||||
import com.rtbishop.look4sat.core.domain.model.SatDay
|
||||
import com.rtbishop.look4sat.core.domain.model.SatReport
|
||||
import com.rtbishop.look4sat.core.domain.model.SatSlot
|
||||
@@ -12,6 +14,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
@@ -49,6 +52,46 @@ class AmSatRepository(private val remoteSource: IRemoteSource) : IAmSatRepositor
|
||||
SatStatusPage(System.currentTimeMillis(), statuses, reportMap)
|
||||
}
|
||||
|
||||
override suspend fun submitReport(submission: AmSatReportSubmission): AmSatReportSubmitResult = withContext(Dispatchers.IO) {
|
||||
val payload = JSONObject().apply {
|
||||
put("name", submission.name)
|
||||
put("report", submission.report)
|
||||
put("callsign", submission.callsign)
|
||||
put("reported_at", isoUtcFormat.format(Date(submission.reportedAtUtcMillis)))
|
||||
if (submission.gridSquare.isNotBlank()) put("grid_square", submission.gridSquare)
|
||||
}
|
||||
val response = remoteSource.submitAmSatReport(payload.toString())
|
||||
?: return@withContext AmSatReportSubmitResult(success = false, message = "Network request failed")
|
||||
val (code, body) = response
|
||||
val errorMessage = parseSubmitError(body)
|
||||
return@withContext if (code in 200..299 && errorMessage.isBlank()) {
|
||||
AmSatReportSubmitResult(success = true, reportId = parseSubmitReportId(body))
|
||||
} else {
|
||||
AmSatReportSubmitResult(
|
||||
success = false,
|
||||
message = errorMessage.ifBlank { "HTTP $code" }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSubmitError(json: String): String {
|
||||
return try {
|
||||
JSONObject(json).optJSONObject("error")?.optString("message").orEmpty()
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSubmitReportId(json: String): String? {
|
||||
return try {
|
||||
val obj = JSONObject(json)
|
||||
obj.optJSONObject("data")?.optString("id")?.takeIf { it.isNotBlank() }
|
||||
?: obj.optString("id").takeIf { it.isNotBlank() }
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse catalog JSON to list of satellite names */
|
||||
private fun parseCatalog(json: String): List<String> {
|
||||
return try {
|
||||
|
||||
@@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.json.JSONObject
|
||||
import java.util.Locale
|
||||
|
||||
class SettingsRepo(
|
||||
private val locationManager: LocationManager,
|
||||
@@ -504,4 +505,16 @@ class SettingsRepo(
|
||||
}
|
||||
preferences.edit { putString(keySatelliteOffsets, updated) }
|
||||
}
|
||||
|
||||
//region # AMSAT status report settings
|
||||
private val keyAmSatCallsign = "amSatCallsign"
|
||||
|
||||
override fun getAmSatCallsign(): String {
|
||||
return preferences.getString(keyAmSatCallsign, "").orEmpty()
|
||||
}
|
||||
|
||||
override fun setAmSatCallsign(callsign: String) {
|
||||
preferences.edit { putString(keyAmSatCallsign, callsign.trim().uppercase(Locale.US)) }
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
@@ -23,8 +23,10 @@ import com.rtbishop.look4sat.core.domain.source.IRemoteSource
|
||||
import com.rtbishop.look4sat.core.domain.source.NetworkResult
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import java.io.InputStream
|
||||
|
||||
class RemoteSource(
|
||||
@@ -97,4 +99,21 @@ class RemoteSource(
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun submitAmSatReport(payloadJson: String): Pair<Int, String>? = withContext(dispatcher) {
|
||||
try {
|
||||
val body = payloadJson.toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||
val request = Request.Builder()
|
||||
.url("https://www.amsat.org/status/api/v1/reports.php")
|
||||
.header("User-Agent", "Look4Sat")
|
||||
.post(body)
|
||||
.build()
|
||||
httpClient.newCall(request).execute().use { response ->
|
||||
response.code to response.body.string()
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
println("RemoteSource submitAmSatReport exception: $exception")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,6 +128,8 @@ private class FakeRemoteSource : IRemoteSource {
|
||||
override suspend fun getAmSatCatalog(): String? = null
|
||||
|
||||
override suspend fun getAmSatReports(hours: Int, limit: Int): String? = null
|
||||
|
||||
override suspend fun submitAmSatReport(payloadJson: String): Pair<Int, String>? = null
|
||||
}
|
||||
|
||||
private class FakeLocalSource : ILocalSource {
|
||||
@@ -229,6 +231,10 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou
|
||||
override fun getSatelliteOffset(catnum: Int): String = ""
|
||||
|
||||
override fun setSatelliteOffset(catnum: Int, offset: String) = Unit
|
||||
|
||||
override fun getAmSatCallsign(): String = ""
|
||||
|
||||
override fun setAmSatCallsign(callsign: String) = Unit
|
||||
}
|
||||
|
||||
private fun defaultDataSourcesSettings(): DataSourcesSettings {
|
||||
|
||||
+4
@@ -175,6 +175,10 @@ class SelectionRepoTest {
|
||||
override fun getSatelliteOffset(catnum: Int): String = ""
|
||||
|
||||
override fun setSatelliteOffset(catnum: Int, offset: String) = Unit
|
||||
|
||||
override fun getAmSatCallsign(): String = ""
|
||||
|
||||
override fun setAmSatCallsign(callsign: String) = Unit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.rtbishop.look4sat.core.domain.model
|
||||
|
||||
/** One public AMSAT satellite status report submission. */
|
||||
data class AmSatReportSubmission(
|
||||
val name: String,
|
||||
val report: String,
|
||||
val callsign: String,
|
||||
val gridSquare: String,
|
||||
val reportedAtUtcMillis: Long
|
||||
)
|
||||
|
||||
/** Result of submitting an AMSAT satellite status report. */
|
||||
data class AmSatReportSubmitResult(
|
||||
val success: Boolean,
|
||||
val message: String = "",
|
||||
val reportId: String? = null
|
||||
)
|
||||
+5
@@ -1,9 +1,14 @@
|
||||
package com.rtbishop.look4sat.core.domain.repository
|
||||
|
||||
import com.rtbishop.look4sat.core.domain.model.AmSatReportSubmission
|
||||
import com.rtbishop.look4sat.core.domain.model.AmSatReportSubmitResult
|
||||
import com.rtbishop.look4sat.core.domain.model.SatStatusPage
|
||||
|
||||
/** AMSAT satellite status data source */
|
||||
interface IAmSatRepository {
|
||||
/** Fetch and parse the AMSAT status page; null on failure */
|
||||
suspend fun fetchStatus(): SatStatusPage?
|
||||
|
||||
/** Submit a public AMSAT satellite status report. */
|
||||
suspend fun submitReport(submission: AmSatReportSubmission): AmSatReportSubmitResult
|
||||
}
|
||||
+5
@@ -85,4 +85,9 @@ interface ISettingsRepo {
|
||||
fun getSatelliteOffset(catnum: Int): String
|
||||
fun setSatelliteOffset(catnum: Int, offset: String)
|
||||
//endregion
|
||||
|
||||
//region # AMSAT status report settings
|
||||
fun getAmSatCallsign(): String
|
||||
fun setAmSatCallsign(callsign: String)
|
||||
//endregion
|
||||
}
|
||||
@@ -32,4 +32,5 @@ interface IRemoteSource {
|
||||
suspend fun getNetworkStream(url: String): NetworkResult
|
||||
suspend fun getAmSatCatalog(): String?
|
||||
suspend fun getAmSatReports(hours: Int, limit: Int): String?
|
||||
suspend fun submitAmSatReport(payloadJson: String): Pair<Int, String>?
|
||||
}
|
||||
@@ -305,6 +305,7 @@ fun InfoDialog(
|
||||
title: String,
|
||||
onDismiss: () -> Unit,
|
||||
onAccept: () -> Unit,
|
||||
extraAction: (@Composable () -> Unit)? = null,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
DialogShell(onDismissRequest = onDismiss) { padding ->
|
||||
@@ -323,6 +324,10 @@ fun InfoDialog(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
extraAction?.let {
|
||||
it()
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
CardButton(onClick = onAccept, text = stringResource(R.string.btn_accept))
|
||||
}
|
||||
content()
|
||||
|
||||
@@ -30,6 +30,26 @@
|
||||
<string name="amsat_status_tlm_only">仅遥测</string>
|
||||
<string name="amsat_status_not_heard">未听到</string>
|
||||
<string name="amsat_status_crew_active">乘组活跃</string>
|
||||
<string name="amsat_upload">上传</string>
|
||||
<string name="amsat_upload_back">返回</string>
|
||||
<string name="amsat_upload_report">上传报告到 AMSAT</string>
|
||||
<string name="amsat_upload_status">状态</string>
|
||||
<string name="amsat_upload_callsign">呼号</string>
|
||||
<string name="amsat_upload_grid">网格</string>
|
||||
<string name="amsat_upload_time_now">时间:当前 UTC %s</string>
|
||||
<string name="amsat_upload_submit">提交到 AMSAT</string>
|
||||
<string name="amsat_upload_confirm">确认上传</string>
|
||||
<string name="amsat_upload_confirm_title">确认上传到 AMSAT</string>
|
||||
<string name="amsat_upload_confirm_message">这条公开报告将上传到 AMSAT。\n卫星:%1$s\n状态:%2$s\n呼号:%3$s\n网格:%4$s</string>
|
||||
<string name="amsat_upload_grid_none">未填写</string>
|
||||
<string name="amsat_upload_success">报告已上传</string>
|
||||
<string name="amsat_upload_failed">上传失败:%s</string>
|
||||
<string name="amsat_upload_callsign_required">请填写呼号</string>
|
||||
<string name="amsat_upload_grid_invalid">网格应为 4 或 6 位 Maidenhead 定位符</string>
|
||||
<string name="amsat_upload_active">活跃</string>
|
||||
<string name="amsat_upload_tlm">遥测/信标</string>
|
||||
<string name="amsat_upload_not_heard">未听到</string>
|
||||
<string name="amsat_upload_crew_active">ISS 语音</string>
|
||||
|
||||
<!-- Satellites screen -->
|
||||
<string name="sat_type_hint">模式:%s</string>
|
||||
|
||||
@@ -83,6 +83,26 @@
|
||||
<string name="amsat_status_tlm_only">Telemetry Only</string>
|
||||
<string name="amsat_status_not_heard">Not Heard</string>
|
||||
<string name="amsat_status_crew_active">Crew Active</string>
|
||||
<string name="amsat_upload">Upload</string>
|
||||
<string name="amsat_upload_back">Back</string>
|
||||
<string name="amsat_upload_report">Upload report to AMSAT</string>
|
||||
<string name="amsat_upload_status">Status</string>
|
||||
<string name="amsat_upload_callsign">Callsign</string>
|
||||
<string name="amsat_upload_grid">Grid</string>
|
||||
<string name="amsat_upload_time_now">Time: current UTC %s</string>
|
||||
<string name="amsat_upload_submit">Submit to AMSAT</string>
|
||||
<string name="amsat_upload_confirm">Upload</string>
|
||||
<string name="amsat_upload_confirm_title">Confirm AMSAT upload</string>
|
||||
<string name="amsat_upload_confirm_message">This public report will be uploaded to AMSAT.\nSatellite: %1$s\nStatus: %2$s\nCallsign: %3$s\nGrid: %4$s</string>
|
||||
<string name="amsat_upload_grid_none">Not provided</string>
|
||||
<string name="amsat_upload_success">Report uploaded</string>
|
||||
<string name="amsat_upload_failed">Upload failed: %s</string>
|
||||
<string name="amsat_upload_callsign_required">Callsign is required</string>
|
||||
<string name="amsat_upload_grid_invalid">Grid must be a 4 or 6 character Maidenhead locator</string>
|
||||
<string name="amsat_upload_active">Active</string>
|
||||
<string name="amsat_upload_tlm">TLM/Beacon</string>
|
||||
<string name="amsat_upload_not_heard">Not Heard</string>
|
||||
<string name="amsat_upload_crew_active">Crew Active</string>
|
||||
|
||||
<!-- Radar screen -->
|
||||
<string name="radar_back">Back</string>
|
||||
|
||||
Reference in new issue
Block a user