chore(i18n): translate all code comments to English
All Chinese comments (//, /* */, KDoc) across core/app/feature/build- logic translated to English (550 lines, 73 files after FT8 rollback). Code logic untouched - comment text only. Verified: all modules compileDebugKotlin BUILD SUCCESSFUL.
This commit is contained in:
1 parent
19926c58ac
commit
c9cab8457d
65 files changed
+443
-443
No files matched your search
@@ -9,8 +9,8 @@ import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
|
||||
/**
|
||||
* APRS-IS TCP 客户端(从 APRSdroid TcpUploader.scala 逆向移植)。
|
||||
* 纯文本协议:登录一行 + 每行一个包;断线 30 秒重连。
|
||||
* APRS-IS TCP client (reverse-ported from APRSdroid TcpUploader.scala).
|
||||
* Plain-text protocol: one login line + one packet per line; 30 s reconnect after drop.
|
||||
*/
|
||||
class AprsIsClient(
|
||||
private val host: String,
|
||||
@@ -30,7 +30,7 @@ class AprsIsClient(
|
||||
val isConnected: Boolean
|
||||
get() = synchronized(lock) { socket?.isConnected == true && !socket!!.isClosed }
|
||||
|
||||
/** 建立连接 + 登录(同步阻塞,调用方放后台线程) */
|
||||
/** Connect + login (synchronous/blocking; call from a background thread) */
|
||||
@Throws(Exception::class)
|
||||
fun connect() {
|
||||
disconnect()
|
||||
@@ -43,10 +43,10 @@ class AprsIsClient(
|
||||
writer = PrintWriter(OutputStreamWriter(s.getOutputStream(), Charsets.ISO_8859_1), true)
|
||||
reader = BufferedReader(InputStreamReader(s.getInputStream(), Charsets.ISO_8859_1), 256)
|
||||
}
|
||||
// 登录行
|
||||
// Login line
|
||||
val login = AprsPacket.formatLogin(callsign, ssid, passcode, version) + filter
|
||||
writer?.println(login)
|
||||
// 读登录响应(aprsc 返回 # logresp ... verified/unverified)
|
||||
// Read the login response (aprsc replies # logresp ... verified/unverified)
|
||||
runCatching {
|
||||
s.soTimeout = 8000
|
||||
val resp = reader?.readLine()
|
||||
@@ -54,14 +54,14 @@ class AprsIsClient(
|
||||
resp.contains("unverified", ignoreCase = true))) {
|
||||
throw IllegalArgumentException(resp.trim())
|
||||
}
|
||||
// 恢复超时
|
||||
// Restore timeout
|
||||
s.soTimeout = timeoutSec * 1000
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送一个 APRS 包(一行)并尝试读服务器确认。
|
||||
* 返回 null=发送失败;Pair(ok, detail)=发送结果(服务器错误原文在 detail)
|
||||
* Sends one APRS packet (one line) and tries to read the server ack.
|
||||
* Returns null=failed to send; Pair(ok, detail)=result (server error text lives in detail)
|
||||
*/
|
||||
fun sendPacket(packetLine: String): Pair<Boolean, String>? {
|
||||
synchronized(lock) {
|
||||
@@ -69,7 +69,7 @@ class AprsIsClient(
|
||||
w.println(packetLine)
|
||||
if (w.checkError()) return Pair(false, "write failed")
|
||||
}
|
||||
// 尝试读服务器响应(短超时 3s;APRS-IS 对格式错误会回错误行)
|
||||
// Try to read the server response (short 3 s timeout; APRS-IS replies with an error line on bad format)
|
||||
return runCatching {
|
||||
val s = socket ?: return@runCatching Pair(true, "OK")
|
||||
val oldTimeout = s.soTimeout
|
||||
@@ -88,7 +88,7 @@ class AprsIsClient(
|
||||
}.getOrElse { Pair(true, "OK") }
|
||||
}
|
||||
|
||||
/** 读一行(服务器响应,超时抛异常) */
|
||||
/** Read one line (server response; throws on timeout) */
|
||||
fun readLine(): String? {
|
||||
return reader?.readLine()
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/** APRS 连接状态 */
|
||||
/** APRS connection state */
|
||||
enum class AprsState { Idle, Connecting, Connected, Disconnected, Error }
|
||||
|
||||
/** APRS 配置(SharedPreferences 持久化,填入即保存) */
|
||||
/** APRS config (persisted in SharedPreferences, saved as filled) */
|
||||
data class AprsConfig(
|
||||
val enabled: Boolean = false,
|
||||
val server: String = "euro.aprs2.net",
|
||||
@@ -29,7 +29,7 @@ data class AprsConfig(
|
||||
val includeAltitude: Boolean = true
|
||||
)
|
||||
|
||||
/** APRS 上报结果 */
|
||||
/** APRS report result */
|
||||
data class AprsReport(
|
||||
val timestamp: Long,
|
||||
val packet: String,
|
||||
@@ -37,7 +37,7 @@ data class AprsReport(
|
||||
val detail: String
|
||||
)
|
||||
|
||||
/** 上报调度器(周期上报 + 手动触发),连接管理放在前台服务 */
|
||||
/** Report scheduler (periodic + manual trigger); connection management lives in the foreground service */
|
||||
class AprsReporter(
|
||||
private val configProvider: () -> AprsConfig,
|
||||
private val positionProvider: () -> Pair<Double, Double>? = { null },
|
||||
@@ -51,7 +51,7 @@ class AprsReporter(
|
||||
|
||||
val isRunning: Boolean get() = job?.isActive == true
|
||||
|
||||
/** 启动周期上报(前台服务调用) */
|
||||
/** Start periodic reporting (called by the foreground service) */
|
||||
fun start() {
|
||||
stop()
|
||||
val cfg = configProvider()
|
||||
@@ -75,7 +75,7 @@ class AprsReporter(
|
||||
onState(AprsState.Idle)
|
||||
}
|
||||
|
||||
/** 手动触发一次(立即上报,不等待周期) */
|
||||
/** Trigger one report manually (immediately, without waiting for the cycle) */
|
||||
fun reportNow() {
|
||||
manualJob?.cancel()
|
||||
manualJob = scope.launch { reportOnce() }
|
||||
@@ -112,7 +112,7 @@ class AprsReporter(
|
||||
}
|
||||
}
|
||||
|
||||
/** 构造位置包:BG7NTA-5>APRS:=DDMM.MMN/DDDMM.MME<状态文本 */
|
||||
/** Build position packet: BG7NTA-5>APRS:=DDMM.MMN/DDDMM.MME<status text */
|
||||
private fun buildPositionPacket(cfg: AprsConfig, lat: Double? = null, lon: Double? = null): String {
|
||||
val source = AprsPacket.formatCallSsid(cfg.callsign, cfg.ssid)
|
||||
val pos = AprsPosition(
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.rtbishop.look4sat.core.data.aprs
|
||||
import android.content.Context
|
||||
|
||||
/**
|
||||
* APRS 配置存取 + 服务 action 常量(被 feature/settings 和 app 服务共用,
|
||||
* 避免 feature 反向依赖 app)。
|
||||
* APRS config storage + service action constants (shared by feature/settings and the app service,
|
||||
* so feature never depends on app).
|
||||
*/
|
||||
object AprsStore {
|
||||
|
||||
@@ -28,7 +28,7 @@ object AprsStore {
|
||||
private const val KEY_LAST_OK = "last_report_ok"
|
||||
private const val KEY_LAST_DETAIL = "last_report_detail"
|
||||
|
||||
/** 读取配置(填入即保存,无需每次打开重填) */
|
||||
/** Read config (saved as filled; no need to re-enter each time) */
|
||||
fun loadConfig(context: Context): AprsConfig {
|
||||
val p = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
return AprsConfig(
|
||||
@@ -45,7 +45,7 @@ object AprsStore {
|
||||
)
|
||||
}
|
||||
|
||||
/** 上次上报结果(供设置卡片显示) */
|
||||
/** Last report result (shown on the settings card) */
|
||||
data class LastReport(val time: Long = 0L, val ok: Boolean = false, val detail: String = "")
|
||||
|
||||
fun loadLastReport(context: Context): LastReport {
|
||||
@@ -65,7 +65,7 @@ object AprsStore {
|
||||
.apply()
|
||||
}
|
||||
|
||||
/** 保存配置 */
|
||||
/** Save config */
|
||||
fun saveConfig(context: Context, cfg: AprsConfig) {
|
||||
context.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit()
|
||||
.putBoolean(KEY_ENABLED, cfg.enabled)
|
||||
|
||||
@@ -103,7 +103,7 @@ class MainContainer(private val context: Context) : IMainContainer {
|
||||
|
||||
override fun provideSaveImage(): ISaveImage = SaveImage(context)
|
||||
|
||||
// WaveLog 日志(4.5.2): 本地队列 + 上传器(共享实例)
|
||||
// WaveLog logging (4.5.2): local queue + uploader (shared instance)
|
||||
override val wavelogQueue: WavelogQueue by lazy {
|
||||
val prefs = context.getSharedPreferences("wavelog", Context.MODE_PRIVATE)
|
||||
WavelogQueue(object : IWavelogQueueStore {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import com.rtbishop.look4sat.core.domain.model.SatStatusPage
|
||||
import com.rtbishop.look4sat.core.domain.repository.IAmSatRepository
|
||||
import com.rtbishop.look4sat.core.domain.source.IRemoteSource
|
||||
|
||||
/** AMSAT 状态仓库: 抓 HTML → 解析 → SatStatusPage */
|
||||
/** AMSAT status repository: fetch HTML -> parse -> SatStatusPage */
|
||||
class AmSatRepository(private val remoteSource: IRemoteSource) : IAmSatRepository {
|
||||
|
||||
override suspend fun fetchStatus(): SatStatusPage? {
|
||||
|
||||
@@ -69,7 +69,7 @@ class DatabaseRepo(
|
||||
val dataSourcesSettings = settingsRepo.dataSourcesSettings.value
|
||||
val tleUrls = buildMap {
|
||||
putAll(Sources.satelliteDataUrls)
|
||||
// 开关开且 URL 非空 -> All 用自定义 URL;否则用默认 URL(在线更新默认源)
|
||||
// Switch on + non-empty URL -> All uses the custom URL; otherwise the default URL (online-update default source)
|
||||
put("All", if (dataSourcesSettings.useCustomTLE && dataSourcesSettings.tleUrl.isNotBlank())
|
||||
dataSourcesSettings.tleUrl else Sources.defaultTleUrl)
|
||||
}.filterValues { it.isNotBlank() }
|
||||
@@ -81,7 +81,7 @@ class DatabaseRepo(
|
||||
// launch all network requests concurrently
|
||||
val tleJobs = tleUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } }
|
||||
val radioJobs = radioUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } }
|
||||
// 统计成功源数: 0 成功视为更新失败(不刷时间戳, 抛异常让 UI 提示)
|
||||
// Count successful sources: zero successes = update failed (timestamp untouched, exception surfaced in the UI)
|
||||
val tleResults = tleJobs.awaitAll()
|
||||
val radioResults = radioJobs.awaitAll()
|
||||
val successCount = tleResults.count { it.second != null } + radioResults.count { it.second != null }
|
||||
|
||||
@@ -187,9 +187,9 @@ class SettingsRepo(
|
||||
return true
|
||||
}
|
||||
|
||||
/** GPS 定位: 一次性 getCurrentLocation(GPS 优先, 15 秒超时), 拿到位置才返回 true */
|
||||
/** GPS fix: one-shot getCurrentLocation (GPS first, 15 s timeout); returns true only with a fix */
|
||||
override suspend fun setStationPosition(): Boolean {
|
||||
// 权限前置: 无定位权限直接失败(不吞异常)
|
||||
// Permission gate: fail fast without location permission (no swallowed exceptions)
|
||||
if (androidx.core.content.ContextCompat.checkSelfPermission(
|
||||
context, android.Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) != android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
@@ -202,7 +202,7 @@ class SettingsRepo(
|
||||
val signal = android.os.CancellationSignal()
|
||||
val handler = android.os.Handler(android.os.Looper.getMainLooper())
|
||||
val executor = java.util.concurrent.Executor { handler.post(it) }
|
||||
// 15 秒超时
|
||||
// 15 s timeout
|
||||
val timeout = handler.postDelayed({
|
||||
signal.cancel()
|
||||
if (cont.isActive) cont.resume(false) { }
|
||||
@@ -432,8 +432,8 @@ class SettingsRepo(
|
||||
}
|
||||
|
||||
private fun getDataSourcesSettings(): DataSourcesSettings {
|
||||
// 4.4.8 修复: 旧版 example.com 占位 URL 视为未配置 -> 替换为真实默认 URL 且开关强制关闭,
|
||||
// 否则在线更新的 All/SatNOGS 源会指向错误地址导致更新失败
|
||||
// 4.4.8 fix: legacy example.com placeholder URLs count as unconfigured -> replaced with the real default URL and the switch forced off,
|
||||
// otherwise the online All/SatNOGS sources would point at the wrong address and fail to update
|
||||
val storedTleUrl = preferences.getString(keyTleUrl, Sources.defaultTleUrl) ?: Sources.defaultTleUrl
|
||||
val storedTxUrl = preferences.getString(keyTransceiversUrl, Sources.defaultTransceiversUrl) ?: Sources.defaultTransceiversUrl
|
||||
val tleUrl = if (storedTleUrl == "https://example.com/tle.txt") Sources.defaultTleUrl else storedTleUrl
|
||||
|
||||
@@ -8,17 +8,17 @@ import com.rtbishop.look4sat.core.domain.model.SatStatusPage
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* AMSAT 卫星状态页解析器(https://amsat.org/status/)
|
||||
* AMSAT satellite status page parser (https://amsat.org/status/)
|
||||
*
|
||||
* 页面结构(静态 HTML, 2026-08 实测):
|
||||
* - 状态表: <table> 内 48 行, 表头 = Name + 6 天(每个 colspan=12)
|
||||
* 数据行 73 格: [0]=卫星名, [1..72] = 6 天 × 12 个 2 小时槽(新→旧)
|
||||
* 每格 <td width=9 bgcolor="颜色">数字</td>, 有报告时数字带
|
||||
* docTips.show('id') 链接
|
||||
* - 报告详情在页面内嵌 JS:
|
||||
* tips.a885153 = new Array(5,5,120,'状态<br>呼号<br>网格<br>日期<br>时间段 UTC')
|
||||
* Page structure (static HTML, verified 2026-08):
|
||||
* - Status table: 48 rows in <table>, header = Name + 6 days (each colspan=12)
|
||||
* Data row has 73 cells: [0]=satellite name, [1..72] = 6 days x 12 two-hour slots (new->old)
|
||||
* Each cell: <td width=9 bgcolor="color">count</td>; when reports exist the count carries a
|
||||
* docTips.show('id') link
|
||||
* - Report details live in the page's inline JS:
|
||||
* tips.a885153 = new Array(5,5,120,'status<br>callsign<br>grid<br>date<br>time span UTC')
|
||||
*
|
||||
* 状态色: #648fff=活跃, #ffb000=仅遥测, #dc267f=未听到, #fe6100=矛盾
|
||||
* Status colors: #648fff=active, #ffb000=telemetry only, #dc267f=not heard, #fe6100=conflicting
|
||||
*/
|
||||
object AmSatParser {
|
||||
|
||||
@@ -38,14 +38,14 @@ object AmSatParser {
|
||||
"tips\\.(a\\d+)\\s*=\\s*new\\s+Array\\(\\s*\\d+,\\s*\\d+,\\s*\\d+,\\s*'([^']*)'"
|
||||
)
|
||||
|
||||
/** 解析完整页面 */
|
||||
/** Parse the full page */
|
||||
fun parse(html: String, fetchedAtUtcMs: Long): SatStatusPage {
|
||||
val reports = parseReports(html)
|
||||
val statuses = parseStatusTable(html, reports)
|
||||
return SatStatusPage(fetchedAtUtcMs, statuses, reports)
|
||||
}
|
||||
|
||||
/** 提取全部报告(tips.* JS 数组) */
|
||||
/** Extract all reports (tips.* JS arrays) */
|
||||
fun parseReports(html: String): Map<String, SatReport> {
|
||||
val map = mutableMapOf<String, SatReport>()
|
||||
val m = tipRe.matcher(html)
|
||||
@@ -64,7 +64,7 @@ object AmSatParser {
|
||||
return map
|
||||
}
|
||||
|
||||
/** 解析状态表(48 行卫星) */
|
||||
/** Parse the status table (48 satellite rows) */
|
||||
fun parseStatusTable(html: String, reports: Map<String, SatReport>): List<SatStatus> {
|
||||
val result = mutableListOf<SatStatus>()
|
||||
val tables = extractTables(html)
|
||||
@@ -79,7 +79,7 @@ object AmSatParser {
|
||||
val cellList = mutableListOf<String>()
|
||||
while (cells.find()) cellList.add(cells.group(1))
|
||||
if (rowIndex == 0) {
|
||||
// 表头: Name + 6 天(每个 colspan=12)
|
||||
// Header: Name + 6 days (each colspan=12)
|
||||
dayHeaders = cellList.drop(1).take(6).map { stripHtml(it) }
|
||||
rowIndex++
|
||||
continue
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
/* LotwSatellitesRepo.kt — LoTW 卫星列表运行时刷新(4.5.5)。
|
||||
* 下载 ARRL 官方 config.tq6(gzip XML), 解析 <satellite name="...">,
|
||||
* 更新 LotwSatellites 动态表 + SharedPreferences 持久化(重启不丢)。
|
||||
* 触发: 设置页 WaveLog 区「更新卫星列表」按钮(用户拍板: 手动触发)。
|
||||
/* LotwSatellitesRepo.kt - runtime refresh of the LoTW satellite list (4.5.5).
|
||||
* Downloads ARRL's official config.tq6 (gzip XML), parses <satellite name="...">,
|
||||
* updates the LotwSatellites dynamic set + persists to SharedPreferences (survives restarts).
|
||||
* Trigger: the "Update sats" button in the settings WaveLog section (user decided: manual).
|
||||
*/
|
||||
package com.rtbishop.look4sat.core.data.wavelog
|
||||
|
||||
@@ -20,7 +20,7 @@ class LotwSatellitesRepo(private val context: Context) : ILotwSatellitesRepo {
|
||||
|
||||
private val prefs = context.getSharedPreferences("lotw_satellites", Context.MODE_PRIVATE)
|
||||
|
||||
/** 从 SharedPreferences 恢复上次下载的表(进程启动时调用) */
|
||||
/** Restore the last downloaded list from SharedPreferences (call at process start) */
|
||||
override fun restore() {
|
||||
val saved = prefs.getString(KEY_NAMES, null)
|
||||
if (!saved.isNullOrBlank()) {
|
||||
@@ -28,7 +28,7 @@ class LotwSatellitesRepo(private val context: Context) : ILotwSatellitesRepo {
|
||||
}
|
||||
}
|
||||
|
||||
/** 下载 config.tq6 → 解析 → 更新内存 + 持久化。返回结果供 UI 提示 */
|
||||
/** Download config.tq6 -> parse -> update memory + persist. Result is surfaced in the UI */
|
||||
override suspend fun refresh(): ILotwSatellitesRepo.RefreshResult = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val url = URL("https://lotw.arrl.org/lotw/config.tq6")
|
||||
@@ -39,7 +39,7 @@ class LotwSatellitesRepo(private val context: Context) : ILotwSatellitesRepo {
|
||||
val raw = conn.getInputStream()
|
||||
val reader = BufferedReader(InputStreamReader(GZIPInputStream(raw), Charsets.UTF_8))
|
||||
val text = reader.use { it.readText() }
|
||||
// 解析 <satellite name="XXX" ...> — 格式固定(ARRL 官方 XML)
|
||||
// Parse <satellite name="XXX" ...> - fixed format (official ARRL XML)
|
||||
val names = Regex("""<satellite name="([^"]+)""").findAll(text)
|
||||
.map { it.groupValues[1].uppercase() }
|
||||
.toSet()
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ class DatabaseRepoTest {
|
||||
repository.updateFromRemote()
|
||||
|
||||
assertTrue(localSource.insertedEntries.any { it.catnum == 25544 })
|
||||
// 新语义: 开关开 + URL 非空 -> All 源用自定义 URL, 数据归入 All 类型
|
||||
// New semantics: switch on + non-empty URL -> the All source uses the custom URL, data lands in the All type
|
||||
assertEquals(listOf(25544), settingsRepo.satelliteTypeIdsByType["All"])
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user