Compare commits
2
Commits
v4.5.2
...
ultra-function
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ef8f05dc4 | ||
|
|
f7402a3531 |
No files matched your search
@@ -97,6 +97,7 @@ import com.rtbishop.look4sat.feature.mutual.MutualViewModel
|
|||||||
import com.rtbishop.look4sat.feature.passes.PassesDestination
|
import com.rtbishop.look4sat.feature.passes.PassesDestination
|
||||||
import com.rtbishop.look4sat.feature.radar.RadarDestination
|
import com.rtbishop.look4sat.feature.radar.RadarDestination
|
||||||
import com.rtbishop.look4sat.feature.radar.WavelogLogScreen
|
import com.rtbishop.look4sat.feature.radar.WavelogLogScreen
|
||||||
|
import com.rtbishop.look4sat.feature.status.SatStatusScreen
|
||||||
import com.rtbishop.look4sat.feature.roaming.RoamingScreen
|
import com.rtbishop.look4sat.feature.roaming.RoamingScreen
|
||||||
import com.rtbishop.look4sat.feature.satellites.SatellitesDestination
|
import com.rtbishop.look4sat.feature.satellites.SatellitesDestination
|
||||||
import com.rtbishop.look4sat.feature.settings.SettingsDestination
|
import com.rtbishop.look4sat.feature.settings.SettingsDestination
|
||||||
@@ -148,7 +149,7 @@ fun MainScreen(navigateToRadar: () -> Unit = {}) {
|
|||||||
val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle()
|
val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle()
|
||||||
val otherSettings by container.settingsRepo.otherSettings.collectAsStateWithLifecycle()
|
val otherSettings by container.settingsRepo.otherSettings.collectAsStateWithLifecycle()
|
||||||
// UI 设置: 按 screenOrder 排序(空 = 默认顺序), 再按 hiddenScreens 过滤(设置页固定保留)
|
// UI 设置: 按 screenOrder 排序(空 = 默认顺序), 再按 hiddenScreens 过滤(设置页固定保留)
|
||||||
val allNavItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Mutual, Screen.Roaming, Screen.CwDecode, Screen.WavelogLog, Screen.Map, Screen.Settings)
|
val allNavItems = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Mutual, Screen.Roaming, Screen.CwDecode, Screen.WavelogLog, Screen.AmSat, Screen.Map, Screen.Settings)
|
||||||
.sortedBy { screen ->
|
.sortedBy { screen ->
|
||||||
// 未知(新页面如 CwDecode 不在旧持久化顺序里): 用默认顺序位置(漫游↔地图), 再兜底最后
|
// 未知(新页面如 CwDecode 不在旧持久化顺序里): 用默认顺序位置(漫游↔地图), 再兜底最后
|
||||||
val idx = otherSettings.screenOrder.indexOf(screen.screenId)
|
val idx = otherSettings.screenOrder.indexOf(screen.screenId)
|
||||||
@@ -162,6 +163,7 @@ fun MainScreen(navigateToRadar: () -> Unit = {}) {
|
|||||||
// 老用户迁移: 已持久化的 subMenuOrder 不含新页面 WavelogLog → 追加到子菜单尾部
|
// 老用户迁移: 已持久化的 subMenuOrder 不含新页面 WavelogLog → 追加到子菜单尾部
|
||||||
val subOrder = (otherSettings.subMenuOrder.ifEmpty { com.rtbishop.look4sat.core.presentation.defaultSubMenuOrder })
|
val subOrder = (otherSettings.subMenuOrder.ifEmpty { com.rtbishop.look4sat.core.presentation.defaultSubMenuOrder })
|
||||||
.let { list -> if ("WavelogLog" in list) list else list + "WavelogLog" }
|
.let { list -> if ("WavelogLog" in list) list else list + "WavelogLog" }
|
||||||
|
.let { list -> if ("AMSAT" in list) list else list + "AMSAT" }
|
||||||
val mainNavItems = remember(allNavItems, subOrder) {
|
val mainNavItems = remember(allNavItems, subOrder) {
|
||||||
allNavItems.filter { it.screenId !in subOrder }.take(5)
|
allNavItems.filter { it.screenId !in subOrder }.take(5)
|
||||||
}
|
}
|
||||||
@@ -193,6 +195,7 @@ fun MainScreen(navigateToRadar: () -> Unit = {}) {
|
|||||||
is Screen.Mutual -> screen is Screen.Mutual
|
is Screen.Mutual -> screen is Screen.Mutual
|
||||||
is Screen.CwDecode -> screen is Screen.CwDecode
|
is Screen.CwDecode -> screen is Screen.CwDecode
|
||||||
is Screen.WavelogLog -> screen is Screen.WavelogLog
|
is Screen.WavelogLog -> screen is Screen.WavelogLog
|
||||||
|
is Screen.AmSat -> screen is Screen.AmSat
|
||||||
is Screen.Map -> screen is Screen.Map
|
is Screen.Map -> screen is Screen.Map
|
||||||
is Screen.Settings -> screen is Screen.Settings
|
is Screen.Settings -> screen is Screen.Settings
|
||||||
else -> false
|
else -> false
|
||||||
@@ -283,6 +286,9 @@ fun MainScreen(navigateToRadar: () -> Unit = {}) {
|
|||||||
entry<Screen.CwDecode> {
|
entry<Screen.CwDecode> {
|
||||||
CwDecodeScreen()
|
CwDecodeScreen()
|
||||||
}
|
}
|
||||||
|
entry<Screen.AmSat> {
|
||||||
|
SatStatusScreen(container = container)
|
||||||
|
}
|
||||||
entry<Screen.WavelogLog> {
|
entry<Screen.WavelogLog> {
|
||||||
WavelogLogScreen(queue = container.wavelogQueue)
|
WavelogLogScreen(queue = container.wavelogQueue)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -39,6 +39,7 @@ internal class ApplicationPlugin : Plugin<Project> {
|
|||||||
implementation(project(":feature:roaming"))
|
implementation(project(":feature:roaming"))
|
||||||
implementation(project(":feature:satellites"))
|
implementation(project(":feature:satellites"))
|
||||||
implementation(project(":feature:settings"))
|
implementation(project(":feature:settings"))
|
||||||
|
implementation(project(":feature:status"))
|
||||||
implementation(libs.androidx.core.splashscreen)
|
implementation(libs.androidx.core.splashscreen)
|
||||||
implementation(libs.compose.material3.adaptive)
|
implementation(libs.compose.material3.adaptive)
|
||||||
implementation(libs.compose.navigation3)
|
implementation(libs.compose.navigation3)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.rtbishop.look4sat.core.data.framework.Ft817Controller
|
|||||||
import com.rtbishop.look4sat.core.data.framework.Ic705Controller
|
import com.rtbishop.look4sat.core.data.framework.Ic705Controller
|
||||||
import com.rtbishop.look4sat.core.data.framework.NetworkReporter
|
import com.rtbishop.look4sat.core.data.framework.NetworkReporter
|
||||||
import com.rtbishop.look4sat.core.data.framework.RadioTrackingService
|
import com.rtbishop.look4sat.core.data.framework.RadioTrackingService
|
||||||
|
import com.rtbishop.look4sat.core.data.repository.AmSatRepository
|
||||||
import com.rtbishop.look4sat.core.data.repository.DatabaseRepo
|
import com.rtbishop.look4sat.core.data.repository.DatabaseRepo
|
||||||
import com.rtbishop.look4sat.core.data.repository.SatelliteRepo
|
import com.rtbishop.look4sat.core.data.repository.SatelliteRepo
|
||||||
import com.rtbishop.look4sat.core.data.repository.SelectionRepo
|
import com.rtbishop.look4sat.core.data.repository.SelectionRepo
|
||||||
@@ -73,12 +74,14 @@ import okhttp3.OkHttpClient
|
|||||||
class MainContainer(private val context: Context) : IMainContainer {
|
class MainContainer(private val context: Context) : IMainContainer {
|
||||||
|
|
||||||
private val localSource = provideLocalSource()
|
private val localSource = provideLocalSource()
|
||||||
|
private val remoteSource by lazy { provideRemoteSource() }
|
||||||
private val mainHandler = CoroutineExceptionHandler { _, error -> println("MainHandler: $error") }
|
private val mainHandler = CoroutineExceptionHandler { _, error -> println("MainHandler: $error") }
|
||||||
override val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + mainHandler)
|
override val appScope = CoroutineScope(SupervisorJob() + Dispatchers.Default + mainHandler)
|
||||||
override val settingsRepo = provideSettingsRepo()
|
override val settingsRepo = provideSettingsRepo()
|
||||||
override val selectionRepo = provideSelectionRepo()
|
override val selectionRepo = provideSelectionRepo()
|
||||||
override val satelliteRepo = provideSatelliteRepo()
|
override val satelliteRepo = provideSatelliteRepo()
|
||||||
override val databaseRepo = provideDatabaseRepo()
|
override val databaseRepo = provideDatabaseRepo()
|
||||||
|
override val amSatRepo = AmSatRepository(remoteSource)
|
||||||
override val radioTrackingService: IRadioTrackingService by lazy {
|
override val radioTrackingService: IRadioTrackingService by lazy {
|
||||||
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
val manager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
RadioTrackingService(appScope, manager, satelliteRepo, settingsRepo)
|
RadioTrackingService(appScope, manager, satelliteRepo, settingsRepo)
|
||||||
@@ -162,7 +165,6 @@ class MainContainer(private val context: Context) : IMainContainer {
|
|||||||
private fun provideDatabaseRepo(): IDatabaseRepo {
|
private fun provideDatabaseRepo(): IDatabaseRepo {
|
||||||
val dbDispatcher = Dispatchers.Default
|
val dbDispatcher = Dispatchers.Default
|
||||||
val dataParser = DataParser(dbDispatcher)
|
val dataParser = DataParser(dbDispatcher)
|
||||||
val remoteSource = provideRemoteSource()
|
|
||||||
return DatabaseRepo(dbDispatcher, dataParser, localSource, remoteSource, settingsRepo)
|
return DatabaseRepo(dbDispatcher, dataParser, localSource, remoteSource, settingsRepo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.rtbishop.look4sat.core.data.repository
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.core.data.source.AmSatParser
|
||||||
|
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 */
|
||||||
|
class AmSatRepository(private val remoteSource: IRemoteSource) : IAmSatRepository {
|
||||||
|
|
||||||
|
override suspend fun fetchStatus(): SatStatusPage? {
|
||||||
|
val html = remoteSource.getStatusHtml() ?: return null
|
||||||
|
if (html.isBlank()) return null
|
||||||
|
return try {
|
||||||
|
AmSatParser.parse(html, System.currentTimeMillis())
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
println("AmSatRepository parse exception: $exception")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.rtbishop.look4sat.core.data.source
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.SatDay
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.SatReport
|
||||||
|
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 java.util.regex.Pattern
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AMSAT 卫星状态页解析器(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')
|
||||||
|
*
|
||||||
|
* 状态色: #648fff=活跃, #ffb000=仅遥测, #dc267f=未听到, #fe6100=矛盾
|
||||||
|
*/
|
||||||
|
object AmSatParser {
|
||||||
|
|
||||||
|
private val STATUS_COLORS = mapOf(
|
||||||
|
"#648fff" to 0xFF648FFF.toLong(),
|
||||||
|
"#ffb000" to 0xFFFFB000.toLong(),
|
||||||
|
"#dc267f" to 0xFFDC267F.toLong(),
|
||||||
|
"#fe6100" to 0xFFFE6100.toLong()
|
||||||
|
)
|
||||||
|
private val GRAY = 0xFFC0C0C0.toLong()
|
||||||
|
|
||||||
|
private val rowRe = Pattern.compile("<tr>(.*?)</tr>", Pattern.DOTALL)
|
||||||
|
private val cellRe = Pattern.compile("(<t[dh][^>]*>.*?</t[dh]>)", Pattern.DOTALL)
|
||||||
|
private val bgRe = Pattern.compile("bgcolor=\"?(#[0-9a-fA-F]{6}|C0C0C0)\"?")
|
||||||
|
private val linkRe = Pattern.compile("docTips\\.show\\('(a\\d+)'\\)")
|
||||||
|
private val tipRe = Pattern.compile(
|
||||||
|
"tips\\.(a\\d+)\\s*=\\s*new\\s+Array\\(\\s*\\d+,\\s*\\d+,\\s*\\d+,\\s*'([^']*)'"
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 解析完整页面 */
|
||||||
|
fun parse(html: String, fetchedAtUtcMs: Long): SatStatusPage {
|
||||||
|
val reports = parseReports(html)
|
||||||
|
val statuses = parseStatusTable(html, reports)
|
||||||
|
return SatStatusPage(fetchedAtUtcMs, statuses, reports)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提取全部报告(tips.* JS 数组) */
|
||||||
|
fun parseReports(html: String): Map<String, SatReport> {
|
||||||
|
val map = mutableMapOf<String, SatReport>()
|
||||||
|
val m = tipRe.matcher(html)
|
||||||
|
while (m.find()) {
|
||||||
|
val id = m.group(1)
|
||||||
|
val parts = m.group(2).split("<br>")
|
||||||
|
map[id] = SatReport(
|
||||||
|
id = id,
|
||||||
|
statusText = parts.getOrElse(0) { "" }.trim(),
|
||||||
|
call = parts.getOrElse(1) { "" }.trim(),
|
||||||
|
grid = parts.getOrElse(2) { "" }.trim(),
|
||||||
|
dateUtc = parts.getOrElse(3) { "" }.trim(),
|
||||||
|
timeUtc = parts.getOrElse(4) { "" }.trim()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析状态表(48 行卫星) */
|
||||||
|
fun parseStatusTable(html: String, reports: Map<String, SatReport>): List<SatStatus> {
|
||||||
|
val result = mutableListOf<SatStatus>()
|
||||||
|
val tables = extractTables(html)
|
||||||
|
for (table in tables) {
|
||||||
|
val rows = rowRe.matcher(table)
|
||||||
|
val parsed = mutableListOf<SatStatus>()
|
||||||
|
var rowIndex = 0
|
||||||
|
var dayHeaders: List<String> = emptyList()
|
||||||
|
while (rows.find()) {
|
||||||
|
val rowHtml = rows.group(1)
|
||||||
|
val cells = cellRe.matcher(rowHtml)
|
||||||
|
val cellList = mutableListOf<String>()
|
||||||
|
while (cells.find()) cellList.add(cells.group(1))
|
||||||
|
if (rowIndex == 0) {
|
||||||
|
// 表头: Name + 6 天(每个 colspan=12)
|
||||||
|
dayHeaders = cellList.drop(1).take(6).map { stripHtml(it) }
|
||||||
|
rowIndex++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (cellList.size < 7) { rowIndex++; continue }
|
||||||
|
val name = stripHtml(cellList[0])
|
||||||
|
if (name.isBlank()) { rowIndex++; continue }
|
||||||
|
val days = mutableListOf<SatDay>()
|
||||||
|
for (d in 0 until 6) {
|
||||||
|
val start = 1 + d * 12
|
||||||
|
val end = start + 12
|
||||||
|
val slots = (start until end).mapNotNull { i ->
|
||||||
|
cellList.getOrNull(i)?.let { cell ->
|
||||||
|
val bg = bgRe.matcher(cell)
|
||||||
|
val color = if (bg.find()) {
|
||||||
|
STATUS_COLORS[bg.group(1).lowercase()] ?: GRAY
|
||||||
|
} else GRAY
|
||||||
|
val link = linkRe.matcher(cell)
|
||||||
|
val ids = mutableListOf<String>()
|
||||||
|
while (link.find()) ids.add(link.group(1))
|
||||||
|
val hasReport = ids.isNotEmpty()
|
||||||
|
val count = if (hasReport) {
|
||||||
|
stripHtml(cell).trim().toIntOrNull() ?: ids.size
|
||||||
|
} else 0
|
||||||
|
SatSlot(
|
||||||
|
statusColor = if (hasReport) color else GRAY,
|
||||||
|
count = count,
|
||||||
|
reportIds = ids
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
days.add(SatDay(dayHeaders.getOrElse(d) { "" }, slots))
|
||||||
|
}
|
||||||
|
parsed.add(SatStatus(name, days))
|
||||||
|
rowIndex++
|
||||||
|
}
|
||||||
|
if (parsed.isNotEmpty()) {
|
||||||
|
result.addAll(parsed)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractTables(html: String): List<String> {
|
||||||
|
val result = mutableListOf<String>()
|
||||||
|
val m = Pattern.compile("<table.*?</table>", Pattern.DOTALL).matcher(html)
|
||||||
|
while (m.find()) result.add(m.group())
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stripHtml(s: String): String =
|
||||||
|
s.replace(Regex("<[^>]+>"), "").trim()
|
||||||
|
}
|
||||||
@@ -43,6 +43,22 @@ class RemoteSource(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getStatusHtml(): String? = withContext(dispatcher) {
|
||||||
|
try {
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("https://amsat.org/status/")
|
||||||
|
.header("User-Agent", "Mozilla/5.0 (Linux; Android 13) Look4Sat/4.5")
|
||||||
|
.build()
|
||||||
|
httpClient.newCall(request).execute().use { response ->
|
||||||
|
if (!response.isSuccessful) return@use null
|
||||||
|
response.body?.string()
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
println("RemoteSource amsat status exception: $exception")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getNetworkStream(url: String): InputStream? = withContext(dispatcher) {
|
override suspend fun getNetworkStream(url: String): InputStream? = withContext(dispatcher) {
|
||||||
try {
|
try {
|
||||||
val networkRequest = Request.Builder().url(url).build()
|
val networkRequest = Request.Builder().url(url).build()
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.rtbishop.look4sat.core.domain.model
|
||||||
|
|
||||||
|
/** 单条卫星状态报告(AMSAT 网站 tooltip 数据) */
|
||||||
|
data class SatReport(
|
||||||
|
val id: String, // 报告 ID(a885153)
|
||||||
|
val statusText: String, // Heard / Telemetry Only / Not Heard ...
|
||||||
|
val call: String, // 呼号
|
||||||
|
val grid: String, // 网格坐标(可为空)
|
||||||
|
val dateUtc: String, // 2026-08-04
|
||||||
|
val timeUtc: String // 2:46-:59 UTC
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 单个 2 小时槽的状态 */
|
||||||
|
data class SatSlot(
|
||||||
|
val statusColor: Long, // ARGB 状态色(-1 = 无报告)
|
||||||
|
val count: Int, // 报告数量(0 = 无)
|
||||||
|
val reportIds: List<String> = emptyList() // 该槽报告 ID 列表
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 卫星一天的状态(12 个 2 小时槽) */
|
||||||
|
data class SatDay(
|
||||||
|
val dateLabel: String, // "Aug 4"
|
||||||
|
val slots: List<SatSlot> // 12 槽(00-02 ... 22-24)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 单个卫星 6 天状态 */
|
||||||
|
data class SatStatus(
|
||||||
|
val name: String, // "AO-123_[FM]"
|
||||||
|
val days: List<SatDay> // 6 天(新→旧)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 页面整体解析结果 */
|
||||||
|
data class SatStatusPage(
|
||||||
|
val fetchedAtUtcMs: Long,
|
||||||
|
val statuses: List<SatStatus>,
|
||||||
|
val reports: Map<String, SatReport> // id → 报告
|
||||||
|
)
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package com.rtbishop.look4sat.core.domain.repository
|
||||||
|
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.SatStatusPage
|
||||||
|
|
||||||
|
/** AMSAT 卫星状态数据源 */
|
||||||
|
interface IAmSatRepository {
|
||||||
|
/** 抓取并解析 AMSAT 状态页; 失败返回 null */
|
||||||
|
suspend fun fetchStatus(): SatStatusPage?
|
||||||
|
}
|
||||||
+1
@@ -30,6 +30,7 @@ interface IMainContainer {
|
|||||||
val selectionRepo: ISelectionRepo
|
val selectionRepo: ISelectionRepo
|
||||||
val satelliteRepo: ISatelliteRepo
|
val satelliteRepo: ISatelliteRepo
|
||||||
val databaseRepo: IDatabaseRepo
|
val databaseRepo: IDatabaseRepo
|
||||||
|
val amSatRepo: IAmSatRepository
|
||||||
val radioTrackingService: IRadioTrackingService
|
val radioTrackingService: IRadioTrackingService
|
||||||
val mutualPassData: StateFlow<MutualPassData>
|
val mutualPassData: StateFlow<MutualPassData>
|
||||||
fun setMutualPassData(data: MutualPassData)
|
fun setMutualPassData(data: MutualPassData)
|
||||||
|
|||||||
@@ -22,4 +22,7 @@ import java.io.InputStream
|
|||||||
interface IRemoteSource {
|
interface IRemoteSource {
|
||||||
suspend fun getFileStream(uri: String): InputStream?
|
suspend fun getFileStream(uri: String): InputStream?
|
||||||
suspend fun getNetworkStream(url: String): InputStream?
|
suspend fun getNetworkStream(url: String): InputStream?
|
||||||
|
|
||||||
|
/** 抓取 AMSAT 状态页 HTML(带 UA, 返回 null = 失败) */
|
||||||
|
suspend fun getStatusHtml(): String?
|
||||||
}
|
}
|
||||||
+4
-1
@@ -47,6 +47,9 @@ sealed class Screen(val iconResId: Int, val titleResId: Int, val screenId: Strin
|
|||||||
@Serializable
|
@Serializable
|
||||||
data object WavelogLog : Screen(R.drawable.ic_log, R.string.nav_log, "WavelogLog")
|
data object WavelogLog : Screen(R.drawable.ic_log, R.string.nav_log, "WavelogLog")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object AmSat : Screen(R.drawable.ic_satellites, R.string.nav_amsat, "AMSAT")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Settings : Screen(R.drawable.ic_settings, R.string.nav_prefs, "Settings")
|
data object Settings : Screen(R.drawable.ic_settings, R.string.nav_prefs, "Settings")
|
||||||
}
|
}
|
||||||
@@ -55,7 +58,7 @@ sealed class Screen(val iconResId: Int, val titleResId: Int, val screenId: Strin
|
|||||||
val defaultScreenOrder = listOf("Satellites", "Passes", "Radar", "Map", "Settings")
|
val defaultScreenOrder = listOf("Satellites", "Passes", "Radar", "Map", "Settings")
|
||||||
|
|
||||||
// UI 设置: 默认更多菜单顺序(进「更多」的页面)
|
// UI 设置: 默认更多菜单顺序(进「更多」的页面)
|
||||||
val defaultSubMenuOrder = listOf("Mutual", "Roaming", "CwDecode", "WavelogLog")
|
val defaultSubMenuOrder = listOf("Mutual", "Roaming", "CwDecode", "WavelogLog", "AMSAT")
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object RadarDestination : NavKey
|
data object RadarDestination : NavKey
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Look4Sat Pro</string>
|
<string name="app_name" translatable="false">Look4Sat Pro</string>
|
||||||
<string name="btn_accept">Terima</string>
|
<string name="btn_accept">Terima</string>
|
||||||
<string name="btn_cancel">Batal</string>
|
<string name="btn_cancel">Batal</string>
|
||||||
<string name="empty_list_message">Tidak ada data untuk ditampilkan</string>
|
<string name="empty_list_message">Tidak ada data untuk ditampilkan</string>
|
||||||
@@ -14,6 +14,18 @@
|
|||||||
<string name="nav_prefs">Pengaturan</string>
|
<string name="nav_prefs">Pengaturan</string>
|
||||||
<string name="nav_more">Lainnya</string>
|
<string name="nav_more">Lainnya</string>
|
||||||
<string name="nav_log">Log</string>
|
<string name="nav_log">Log</string>
|
||||||
|
<string name="nav_amsat">AMSAT</string>
|
||||||
|
<string name="amsat_title">Status Satelit AMSAT</string>
|
||||||
|
<string name="amsat_updated">Diperbarui:</string>
|
||||||
|
<string name="amsat_active">Aktif</string>
|
||||||
|
<string name="amsat_tlm">TLM/beacon</string>
|
||||||
|
<string name="amsat_not_heard">Tidak terdengar</string>
|
||||||
|
<string name="amsat_conflict">Konflik</string>
|
||||||
|
<string name="amsat_name">Name</string>
|
||||||
|
<string name="amsat_load_failed">Gagal memuat - periksa jaringan dan coba lagi</string>
|
||||||
|
<string name="amsat_no_reports">Tidak ada laporan untuk periode ini</string>
|
||||||
|
<string name="amsat_close">Tutup</string>
|
||||||
|
|
||||||
<string name="sat_type_hint">Jenis: %s</string>
|
<string name="sat_type_hint">Jenis: %s</string>
|
||||||
<string name="sat_type_title">Pilih jenis satelit</string>
|
<string name="sat_type_title">Pilih jenis satelit</string>
|
||||||
<string name="sat_search_hint">Id - Nama</string>
|
<string name="sat_search_hint">Id - Nama</string>
|
||||||
@@ -33,13 +45,13 @@
|
|||||||
<string name="pass_filter_aos_time">Jendela AOS</string>
|
<string name="pass_filter_aos_time">Jendela AOS</string>
|
||||||
<string name="pass_filter_invert_time">Balik jendela AOS</string>
|
<string name="pass_filter_invert_time">Balik jendela AOS</string>
|
||||||
<string name="pass_filter_deep_space">DeepSpace (periode >225 mnt)</string>
|
<string name="pass_filter_deep_space">DeepSpace (periode >225 mnt)</string>
|
||||||
<string name="pass_time_placeholder"> -- : -- : -- </string>
|
<string name="pass_time_placeholder" translatable="false"> -- : -- : -- </string>
|
||||||
<string name="pass_modes_title">Pilih jenis modulasi</string>
|
<string name="pass_modes_title">Pilih jenis modulasi</string>
|
||||||
<string name="pass_satId">%05d</string>
|
<string name="pass_satId" translatable="false">%05d</string>
|
||||||
<string name="pass_elevation">Elevasi: %.1f°</string>
|
<string name="pass_elevation">Elevasi: %.1f°</string>
|
||||||
<string name="pass_deep_space">DeepSpace</string>
|
<string name="pass_deep_space" translatable="false">DeepSpace</string>
|
||||||
<string name="pass_altitude">Ketinggian: %d km</string>
|
<string name="pass_altitude">Ketinggian: %d km</string>
|
||||||
<string name="pass_aosLos">%03d° - %03d°</string>
|
<string name="pass_aosLos" translatable="false">%03d° - %03d°</string>
|
||||||
<string name="pass_empty_list_message">
|
<string name="pass_empty_list_message">
|
||||||
Pastikan filter Anda benar dan Anda sudah memilih satelit</string>
|
Pastikan filter Anda benar dan Anda sudah memilih satelit</string>
|
||||||
<string name="pass_error_message">Tidak ada lintasan satelit mendatang untuk ditampilkan</string>
|
<string name="pass_error_message">Tidak ada lintasan satelit mendatang untuk ditampilkan</string>
|
||||||
@@ -47,26 +59,30 @@
|
|||||||
<string name="pass_welcome_message">
|
<string name="pass_welcome_message">
|
||||||
Pastikan Anda mengatur posisi melalui GPS, Lat/Lon atau QTH di Pengaturan.
|
Pastikan Anda mengatur posisi melalui GPS, Lat/Lon atau QTH di Pengaturan.
|
||||||
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
||||||
<string name="pass_whatsnew_title">Apa yang baru di Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">Apa yang baru di Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* WaveLog kini mendukung API v1 dan v2 (terdeteksi otomatis); dialog kesalahan dengan tombol salin
|
* BARU: Halaman status satelit AMSAT - tabel status langsung (warna resmi), jumlah laporan, laporan detail per satelit/hari
|
||||||
* Frekuensi log cocok dengan panel Doppler; transponder linier menampilkan rentang penuh</string>
|
* Log dikelompokkan per lintasan satelit (sesi = satelit + waktu AOS), garis pemisah antar sesi
|
||||||
|
* GPS kini hanya menampilkan sukses saat posisi benar-benar didapat (timeout 15 dtk, cek izin)
|
||||||
|
* Pesan kegagalan jelas saat database satelit gagal diunduh
|
||||||
|
* Frekuensi log mengikuti panel Doppler secara real-time; visual hapus geser dipulihkan
|
||||||
|
* Perbaikan bahasa Indonesia (values-id)</string>
|
||||||
<string name="radar_back">Kembali</string>
|
<string name="radar_back">Kembali</string>
|
||||||
<string name="radar_notify">Beri tahu</string>
|
<string name="radar_notify">Beri tahu</string>
|
||||||
<string name="radar_az_text">Azimuth</string>
|
<string name="radar_az_text">Azimuth</string>
|
||||||
<string name="radar_az_value">%.1f°</string>
|
<string name="radar_az_value" translatable="false">%.1f°</string>
|
||||||
<string name="radar_el_text">Elevasi</string>
|
<string name="radar_el_text">Elevasi</string>
|
||||||
<string name="radar_el_value">%.1f°</string>
|
<string name="radar_el_value" translatable="false">%.1f°</string>
|
||||||
<string name="radar_alt_text">Ketinggian</string>
|
<string name="radar_alt_text">Ketinggian</string>
|
||||||
<string name="radar_alt_value">%.0f km</string>
|
<string name="radar_alt_value" translatable="false">%.0f km</string>
|
||||||
<string name="radar_dist_text">Jarak</string>
|
<string name="radar_dist_text">Jarak</string>
|
||||||
<string name="radar_dist_value">%.0f km</string>
|
<string name="radar_dist_value" translatable="false">%.0f km</string>
|
||||||
<string name="radar_eclipsed">Gerhana</string>
|
<string name="radar_eclipsed">Gerhana</string>
|
||||||
<string name="radar_downlink">Downlink</string>
|
<string name="radar_downlink">Downlink</string>
|
||||||
<string name="radar_uplink">Uplink</string>
|
<string name="radar_uplink">Uplink</string>
|
||||||
<string name="radar_link_low">%.4f</string>
|
<string name="radar_link_low" translatable="false">%.4f</string>
|
||||||
<string name="radar_link_lowHigh">%.4f - %.4f</string>
|
<string name="radar_link_lowHigh" translatable="false">%.4f - %.4f</string>
|
||||||
<string name="radar_no_link">- - . - -</string>
|
<string name="radar_no_link" translatable="false">- - . - -</string>
|
||||||
<string name="radar_no_data">Satelit ini tidak memiliki transceiver</string>
|
<string name="radar_no_data">Satelit ini tidak memiliki transceiver</string>
|
||||||
<string name="radar_add">Tambah</string>
|
<string name="radar_add">Tambah</string>
|
||||||
<string name="radar_data">Transceiver satelit:</string>
|
<string name="radar_data">Transceiver satelit:</string>
|
||||||
@@ -86,32 +102,32 @@
|
|||||||
<string name="radar_cw_reset">Bersihkan</string>
|
<string name="radar_cw_reset">Bersihkan</string>
|
||||||
<string name="map_prev">Sebelumnya</string>
|
<string name="map_prev">Sebelumnya</string>
|
||||||
<string name="map_next">Berikutnya</string>
|
<string name="map_next">Berikutnya</string>
|
||||||
<string name="map_copyright">© Kontributor OpenStreetMap</string>
|
<string name="map_copyright" translatable="false">© Kontributor OpenStreetMap</string>
|
||||||
<string name="map_azimuth">Azimuth: %.1f°</string>
|
<string name="map_azimuth">Azimuth: %.1f°</string>
|
||||||
<string name="map_elevation">Elevasi: %.1f°</string>
|
<string name="map_elevation">Elevasi: %.1f°</string>
|
||||||
<string name="map_altitude">Ketinggian: %.0f km</string>
|
<string name="map_altitude">Ketinggian: %.0f km</string>
|
||||||
<string name="map_distance">Jarak: %.0f km</string>
|
<string name="map_distance">Jarak: %.0f km</string>
|
||||||
<string name="map_latitude">Lintang: %.1f°</string>
|
<string name="map_latitude">Lintang: %.1f°</string>
|
||||||
<string name="map_longitude">Bujur: %.1f°</string>
|
<string name="map_longitude">Bujur: %.1f°</string>
|
||||||
<string name="map_qth">QTH: %s</string>
|
<string name="map_qth" translatable="false">QTH: %s</string>
|
||||||
<string name="map_phase">Fase: %.1f°</string>
|
<string name="map_phase">Fase: %.1f°</string>
|
||||||
<string name="map_eclipsed">Gerhana</string>
|
<string name="map_eclipsed">Gerhana</string>
|
||||||
<string name="map_period">Periode: %.0f mnt</string>
|
<string name="map_period">Periode: %.0f mnt</string>
|
||||||
<string name="map_velocity">Kecepatan: %.2f km/d</string>
|
<string name="map_velocity">Kecepatan: %.2f km/d</string>
|
||||||
<string name="map_visibility">Visibilitas: %s</string>
|
<string name="map_visibility">Visibilitas: %s</string>
|
||||||
<string name="map_visible">Terlihat</string>
|
<string name="map_visible">Terlihat</string>
|
||||||
<string name="prefs_app_title">Look4Sat Pro v%s</string>
|
<string name="prefs_app_title" translatable="false">Look4Sat Pro v%s</string>
|
||||||
<string name="prefs_app_url">https://play.google.com/store/apps/details?id=com.rtbishop.look4sat</string>
|
<string name="prefs_app_url" translatable="false">https://play.google.com/store/apps/details?id=com.rtbishop.look4sat</string>
|
||||||
<string name="prefs_donate_title">Donasi</string>
|
<string name="prefs_donate_title">Donasi</string>
|
||||||
<string name="prefs_donate_url">https://ko-fi.com/rt_bishop</string>
|
<string name="prefs_donate_url" translatable="false">https://ko-fi.com/rt_bishop</string>
|
||||||
<string name="prefs_fdroid_title">F-Droid</string>
|
<string name="prefs_fdroid_title" translatable="false">F-Droid</string>
|
||||||
<string name="prefs_fdroid_url">https://f-droid.org/en/packages/com.rtbishop.look4sat/</string>
|
<string name="prefs_fdroid_url" translatable="false">https://f-droid.org/en/packages/com.rtbishop.look4sat/</string>
|
||||||
<string name="prefs_github_title">GitHub</string>
|
<string name="prefs_github_title" translatable="false">GitHub</string>
|
||||||
<string name="prefs_github_url">https://github.com/rt-bishop/Look4Sat/</string>
|
<string name="prefs_github_url" translatable="false">https://github.com/rt-bishop/Look4Sat/</string>
|
||||||
<string name="prefs_license_title">GPLv3</string>
|
<string name="prefs_license_title" translatable="false">GPLv3</string>
|
||||||
<string name="prefs_license_url">https://www.gnu.org/licenses/gpl-3.0.html</string>
|
<string name="prefs_license_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.html</string>
|
||||||
<string name="prefs_privacy_title">Kebijakan Privasi</string>
|
<string name="prefs_privacy_title">Kebijakan Privasi</string>
|
||||||
<string name="prefs_privacy_url">https://sites.google.com/view/look4sat-privacy-policy/home</string>
|
<string name="prefs_privacy_url" translatable="false">https://sites.google.com/view/look4sat-privacy-policy/home</string>
|
||||||
<string name="prefs_updated_title">Diperbarui: %s</string>
|
<string name="prefs_updated_title">Diperbarui: %s</string>
|
||||||
<string name="prefs_updated_time">d MMM yyyy - HH:mm:ss</string>
|
<string name="prefs_updated_time">d MMM yyyy - HH:mm:ss</string>
|
||||||
<string name="prefs_loc_title">Posisi stasiun</string>
|
<string name="prefs_loc_title">Posisi stasiun</string>
|
||||||
@@ -141,7 +157,7 @@
|
|||||||
<string name="prefs_data_sources_title">Sumber data kustom</string>
|
<string name="prefs_data_sources_title">Sumber data kustom</string>
|
||||||
<string name="prefs_data_sources_tle_switch">URL TLE Kustom</string>
|
<string name="prefs_data_sources_tle_switch">URL TLE Kustom</string>
|
||||||
<string name="prefs_data_sources_transceivers_switch">URL transceiver kustom</string>
|
<string name="prefs_data_sources_transceivers_switch">URL transceiver kustom</string>
|
||||||
<string name="prefs_data_sources_url_title">URL (HTTPS)</string>
|
<string name="prefs_data_sources_url_title" translatable="false">URL (HTTPS)</string>
|
||||||
<string name="prefs_data_reset_url">Setel ulang ke URL bawaan</string>
|
<string name="prefs_data_reset_url">Setel ulang ke URL bawaan</string>
|
||||||
<string name="prefs_data_output_title">Keluaran data</string>
|
<string name="prefs_data_output_title">Keluaran data</string>
|
||||||
<string name="prefs_net_output">Jaringan</string>
|
<string name="prefs_net_output">Jaringan</string>
|
||||||
@@ -222,7 +238,7 @@
|
|||||||
<string name="prefs_highlight_mid">Sedang %1$d–%2$d°</string>
|
<string name="prefs_highlight_mid">Sedang %1$d–%2$d°</string>
|
||||||
<string name="prefs_highlight_high">Tinggi > %1$d°</string>
|
<string name="prefs_highlight_high">Tinggi > %1$d°</string>
|
||||||
<string name="prefs_outro_title">BG7NTA & penulis asli mengucapkan terima kasih</string>
|
<string name="prefs_outro_title">BG7NTA & penulis asli mengucapkan terima kasih</string>
|
||||||
<string name="prefs_outro_thanks">
|
<string name="prefs_outro_thanks" translatable="false">
|
||||||
* Pengguna dan kontributor Look4Sat!
|
* Pengguna dan kontributor Look4Sat!
|
||||||
\n* David A. B. Johnson (predict4java)
|
\n* David A. B. Johnson (predict4java)
|
||||||
\n* Dave Moten (predict4java)
|
\n* Dave Moten (predict4java)
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name" translatable="false">Look4Sat Pro</string>
|
||||||
|
<string name="btn_accept">Terima</string>
|
||||||
|
<string name="btn_cancel">Batal</string>
|
||||||
|
<string name="empty_list_message">Tidak ada data untuk ditampilkan</string>
|
||||||
|
<string name="nav_sat">Satelit</string>
|
||||||
|
<string name="nav_pass">Lintasan</string>
|
||||||
|
<string name="nav_radar">Radar</string>
|
||||||
|
<string name="nav_mutual">Pasangan</string>
|
||||||
|
<string name="nav_roaming">Jelajah</string>
|
||||||
|
<string name="nav_cw">Dekoder CW</string>
|
||||||
|
<string name="nav_map">Peta</string>
|
||||||
|
<string name="nav_prefs">Pengaturan</string>
|
||||||
|
<string name="nav_more">Lainnya</string>
|
||||||
|
<string name="nav_log">Log</string>
|
||||||
|
<string name="nav_amsat">AMSAT</string>
|
||||||
|
<string name="amsat_title">Status Satelit AMSAT</string>
|
||||||
|
<string name="amsat_updated">Diperbarui:</string>
|
||||||
|
<string name="amsat_active">Aktif</string>
|
||||||
|
<string name="amsat_tlm">TLM/beacon</string>
|
||||||
|
<string name="amsat_not_heard">Tidak terdengar</string>
|
||||||
|
<string name="amsat_conflict">Konflik</string>
|
||||||
|
<string name="amsat_name">Name</string>
|
||||||
|
<string name="amsat_load_failed">Gagal memuat - periksa jaringan dan coba lagi</string>
|
||||||
|
<string name="amsat_no_reports">Tidak ada laporan untuk periode ini</string>
|
||||||
|
<string name="amsat_close">Tutup</string>
|
||||||
|
<string name="sat_type_hint">Jenis: %s</string>
|
||||||
|
<string name="sat_type_title">Pilih jenis satelit</string>
|
||||||
|
<string name="sat_search_hint">Id - Nama</string>
|
||||||
|
<string name="sat_search_clear">Bersihkan</string>
|
||||||
|
<string name="sat_clear_all">Bersihkan semua</string>
|
||||||
|
<string name="sat_select_all">Pilih semua</string>
|
||||||
|
<string name="sat_empty_list_message">Pastikan kata kunci pencarian Anda benar dan DB sudah diperbarui</string>
|
||||||
|
<string name="sat_warning_title">Peringatan\!</string>
|
||||||
|
<string name="sat_warning_message">
|
||||||
|
Ada lebih dari 9000 satelit yang terdaftar di aplikasi ini.
|
||||||
|
Tidak masuk akal untuk melacak semuanya sekaligus.
|
||||||
|
\n\nSelalu coba persempit daftar hanya ke satelit yang
|
||||||
|
Anda minati melalui pencarian dan pemilih jenis.</string>
|
||||||
|
<string name="pass_filter_title">Filter lintasan</string>
|
||||||
|
<string name="pass_filter_elev">Elevasi minimal</string>
|
||||||
|
<string name="pass_filter_hours">Waktu ke depan</string>
|
||||||
|
<string name="pass_filter_aos_time">Jendela AOS</string>
|
||||||
|
<string name="pass_filter_invert_time">Balik jendela AOS</string>
|
||||||
|
<string name="pass_filter_deep_space">DeepSpace (periode >225 mnt)</string>
|
||||||
|
<string name="pass_time_placeholder" translatable="false"> -- : -- : -- </string>
|
||||||
|
<string name="pass_modes_title">Pilih jenis modulasi</string>
|
||||||
|
<string name="pass_satId" translatable="false">%05d</string>
|
||||||
|
<string name="pass_elevation">Elevasi: %.1f°</string>
|
||||||
|
<string name="pass_deep_space" translatable="false">DeepSpace</string>
|
||||||
|
<string name="pass_altitude">Ketinggian: %d km</string>
|
||||||
|
<string name="pass_aosLos" translatable="false">%03d° - %03d°</string>
|
||||||
|
<string name="pass_empty_list_message">
|
||||||
|
Pastikan filter Anda benar dan Anda sudah memilih satelit</string>
|
||||||
|
<string name="pass_error_message">Tidak ada lintasan satelit mendatang untuk ditampilkan</string>
|
||||||
|
<string name="pass_welcome_title">Selamat datang di Look4Sat\!</string>
|
||||||
|
<string name="pass_welcome_message">
|
||||||
|
Pastikan Anda mengatur posisi melalui GPS, Lat/Lon atau QTH di Pengaturan.
|
||||||
|
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
||||||
|
<string name="pass_whatsnew_title" translatable="false">Apa yang baru di Look4Sat</string>
|
||||||
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
|
* BARU: Halaman status satelit AMSAT - tabel status langsung (warna resmi), jumlah laporan, laporan detail per satelit/hari
|
||||||
|
* Log dikelompokkan per lintasan satelit (sesi = satelit + waktu AOS), garis pemisah antar sesi
|
||||||
|
* GPS kini hanya menampilkan sukses saat posisi benar-benar didapat (timeout 15 dtk, cek izin)
|
||||||
|
* Pesan kegagalan jelas saat database satelit gagal diunduh
|
||||||
|
* Frekuensi log mengikuti panel Doppler secara real-time; visual hapus geser dipulihkan
|
||||||
|
* Perbaikan bahasa Indonesia (values-id)</string>
|
||||||
|
<string name="radar_back">Kembali</string>
|
||||||
|
<string name="radar_notify">Beri tahu</string>
|
||||||
|
<string name="radar_az_text">Azimuth</string>
|
||||||
|
<string name="radar_az_value" translatable="false">%.1f°</string>
|
||||||
|
<string name="radar_el_text">Elevasi</string>
|
||||||
|
<string name="radar_el_value" translatable="false">%.1f°</string>
|
||||||
|
<string name="radar_alt_text">Ketinggian</string>
|
||||||
|
<string name="radar_alt_value" translatable="false">%.0f km</string>
|
||||||
|
<string name="radar_dist_text">Jarak</string>
|
||||||
|
<string name="radar_dist_value" translatable="false">%.0f km</string>
|
||||||
|
<string name="radar_eclipsed">Gerhana</string>
|
||||||
|
<string name="radar_downlink">Downlink</string>
|
||||||
|
<string name="radar_uplink">Uplink</string>
|
||||||
|
<string name="radar_link_low" translatable="false">%.4f</string>
|
||||||
|
<string name="radar_link_lowHigh" translatable="false">%.4f - %.4f</string>
|
||||||
|
<string name="radar_no_link" translatable="false">- - . - -</string>
|
||||||
|
<string name="radar_no_data">Satelit ini tidak memiliki transceiver</string>
|
||||||
|
<string name="radar_add">Tambah</string>
|
||||||
|
<string name="radar_data">Transceiver satelit:</string>
|
||||||
|
<string name="radar_inverted">Terbalik: %s</string>
|
||||||
|
<string name="radar_mode">Mode: %s</string>
|
||||||
|
<string name="radar_string_no">Tidak</string>
|
||||||
|
<string name="radar_string_yes">Ya</string>
|
||||||
|
<string name="radar_visible">Terlihat</string>
|
||||||
|
<string name="radar_doppler_calc">Kalkulator Frekuensi Doppler</string>
|
||||||
|
<string name="radar_doppler_tx_hint">Masukkan frekuensi TX (MHz)</string>
|
||||||
|
<string name="radar_doppler_rx_hint">Masukkan frekuensi RX (MHz)</string>
|
||||||
|
<string name="radar_doppler_offset_hint">Offset (kHz)</string>
|
||||||
|
<string name="radar_doppler_info">Untuk transponder linier, ketik satu frekuensi untuk melihat frekuensi lainnya</string>
|
||||||
|
<string name="radar_cw_decoder">Dekoder CW</string>
|
||||||
|
<string name="radar_cw_start">Mulai</string>
|
||||||
|
<string name="radar_cw_stop">Berhenti</string>
|
||||||
|
<string name="radar_cw_reset">Bersihkan</string>
|
||||||
|
<string name="map_prev">Sebelumnya</string>
|
||||||
|
<string name="map_next">Berikutnya</string>
|
||||||
|
<string name="map_copyright" translatable="false">© Kontributor OpenStreetMap</string>
|
||||||
|
<string name="map_azimuth">Azimuth: %.1f°</string>
|
||||||
|
<string name="map_elevation">Elevasi: %.1f°</string>
|
||||||
|
<string name="map_altitude">Ketinggian: %.0f km</string>
|
||||||
|
<string name="map_distance">Jarak: %.0f km</string>
|
||||||
|
<string name="map_latitude">Lintang: %.1f°</string>
|
||||||
|
<string name="map_longitude">Bujur: %.1f°</string>
|
||||||
|
<string name="map_qth" translatable="false">QTH: %s</string>
|
||||||
|
<string name="map_phase">Fase: %.1f°</string>
|
||||||
|
<string name="map_eclipsed">Gerhana</string>
|
||||||
|
<string name="map_period">Periode: %.0f mnt</string>
|
||||||
|
<string name="map_velocity">Kecepatan: %.2f km/d</string>
|
||||||
|
<string name="map_visibility">Visibilitas: %s</string>
|
||||||
|
<string name="map_visible">Terlihat</string>
|
||||||
|
<string name="prefs_app_title" translatable="false">Look4Sat Pro v%s</string>
|
||||||
|
<string name="prefs_app_url" translatable="false">https://play.google.com/store/apps/details?id=com.rtbishop.look4sat</string>
|
||||||
|
<string name="prefs_donate_title">Donasi</string>
|
||||||
|
<string name="prefs_donate_url" translatable="false">https://ko-fi.com/rt_bishop</string>
|
||||||
|
<string name="prefs_fdroid_title" translatable="false">F-Droid</string>
|
||||||
|
<string name="prefs_fdroid_url" translatable="false">https://f-droid.org/en/packages/com.rtbishop.look4sat/</string>
|
||||||
|
<string name="prefs_github_title" translatable="false">GitHub</string>
|
||||||
|
<string name="prefs_github_url" translatable="false">https://github.com/rt-bishop/Look4Sat/</string>
|
||||||
|
<string name="prefs_license_title" translatable="false">GPLv3</string>
|
||||||
|
<string name="prefs_license_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.html</string>
|
||||||
|
<string name="prefs_privacy_title">Kebijakan Privasi</string>
|
||||||
|
<string name="prefs_privacy_url" translatable="false">https://sites.google.com/view/look4sat-privacy-policy/home</string>
|
||||||
|
<string name="prefs_updated_title">Diperbarui: %s</string>
|
||||||
|
<string name="prefs_updated_time">d MMM yyyy - HH:mm:ss</string>
|
||||||
|
<string name="prefs_loc_title">Posisi stasiun</string>
|
||||||
|
<string name="prefs_loc_gps_title">GPS</string>
|
||||||
|
<string name="prefs_loc_gps_error">Tidak dapat mengambil lokasi - periksa izin dan sinyal GPS/jaringan</string>
|
||||||
|
<string name="prefs_loc_input_title">Input</string>
|
||||||
|
<string name="prefs_loc_input_error">Lokasi yang dimasukkan tidak valid</string>
|
||||||
|
<string name="prefs_loc_qth_title">QTH</string>
|
||||||
|
<string name="prefs_loc_qth_error">Lokator QTH tidak valid</string>
|
||||||
|
<string name="prefs_loc_success">Posisi berhasil diperbarui</string>
|
||||||
|
<string name="prefs_station_title">Pengaturan posisi stasiun</string>
|
||||||
|
<string name="prefs_station_lat_text">Atur garis lintang stasiun Anda</string>
|
||||||
|
<string name="prefs_station_lon_text">Atur garis bujur stasiun Anda</string>
|
||||||
|
<string name="prefs_locator_title">Pengaturan lokator QTH</string>
|
||||||
|
<string name="prefs_locator_text">Atur posisi stasiun menggunakan lokator</string>
|
||||||
|
<string name="prefs_data_title">Data satelit</string>
|
||||||
|
<string name="prefs_data_entries">Satelit: %s</string>
|
||||||
|
<string name="prefs_data_radios">Transceiver: %s</string>
|
||||||
|
<string name="prefs_data_update">Perbarui</string>
|
||||||
|
<string name="prefs_data_import">URL Kustom</string>
|
||||||
|
<string name="prefs_data_clear">Bersihkan</string>
|
||||||
|
<string name="prefs_data_clear_success">Data berhasil dibersihkan</string>
|
||||||
|
<string name="prefs_data_update_success">Pembaruan berhasil diselesaikan</string>
|
||||||
|
<string name="prefs_data_update_failed">Pembaruan gagal - periksa jaringan Anda</string>
|
||||||
|
<string name="prefs_data_import_satellites_error">Tidak ada satelit yang diimpor. Pilih file TLE/3LE (.txt) atau OMM (.csv) yang valid.</string>
|
||||||
|
<string name="prefs_data_import_transceivers_error">Tidak ada transceiver yang diimpor. Pilih file SatNOGS (.json) yang valid.</string>
|
||||||
|
<string name="prefs_data_sources_title">Sumber data kustom</string>
|
||||||
|
<string name="prefs_data_sources_tle_switch">URL TLE Kustom</string>
|
||||||
|
<string name="prefs_data_sources_transceivers_switch">URL transceiver kustom</string>
|
||||||
|
<string name="prefs_data_sources_url_title" translatable="false">URL (HTTPS)</string>
|
||||||
|
<string name="prefs_data_reset_url">Setel ulang ke URL bawaan</string>
|
||||||
|
<string name="prefs_data_output_title">Keluaran data</string>
|
||||||
|
<string name="prefs_net_output">Jaringan</string>
|
||||||
|
<string name="prefs_bt_output">Bluetooth</string>
|
||||||
|
<string name="prefs_cat_output">CAT</string>
|
||||||
|
<string name="nav_radiocontrol">Kontrol Radio</string>
|
||||||
|
<string name="rc_settings_title">Kontrol Radio CAT</string>
|
||||||
|
<string name="rc_radio_model">Model Radio</string>
|
||||||
|
<string name="rc_tx_device_hint">Alamat BT Radio TX</string>
|
||||||
|
<string name="rc_rx_device_hint">Alamat BT Radio RX</string>
|
||||||
|
<string name="rc_tx_name_hint">Nama Radio TX</string>
|
||||||
|
<string name="rc_rx_name_hint">Nama Radio RX</string>
|
||||||
|
<string name="rc_enable_switch">Aktifkan Kontrol CAT</string>
|
||||||
|
<string name="prefs_net_title">Keluaran data jaringan</string>
|
||||||
|
<string name="prefs_net_rotator_switch">Aktifkan keluaran rotator</string>
|
||||||
|
<string name="prefs_net_rotator_address_hint">IP:Port</string>
|
||||||
|
<string name="prefs_net_rotator_format_hint">Format</string>
|
||||||
|
<string name="prefs_net_frequency_switch">Aktifkan keluaran frekuensi</string>
|
||||||
|
<string name="prefs_net_frequency_address_hint">IP:Port</string>
|
||||||
|
<string name="prefs_net_frequency_format_hint">Format</string>
|
||||||
|
<string name="prefs_bt_title">Keluaran data Bluetooth</string>
|
||||||
|
<string name="prefs_bt_rotator_switch">Aktifkan keluaran rotator</string>
|
||||||
|
<string name="prefs_bt_rotator_device_hint">Id perangkat</string>
|
||||||
|
<string name="prefs_bt_rotator_output_hint">Format data</string>
|
||||||
|
<string name="prefs_bt_frequency_switch">Aktifkan keluaran frekuensi</string>
|
||||||
|
<string name="prefs_bt_frequency_device_hint">Id perangkat</string>
|
||||||
|
<string name="prefs_bt_frequency_output_hint">Format data</string>
|
||||||
|
<string name="prefs_bt_perm_error">Periksa izin bluetooth Anda</string>
|
||||||
|
<string name="prefs_net_perm_error">Periksa izin jaringan Anda</string>
|
||||||
|
<string name="tracking_status">Melacak: %s</string>
|
||||||
|
<string name="loc_lat_text">Lintang: %s°</string>
|
||||||
|
<string name="loc_lon_text">Bujur: %s°</string>
|
||||||
|
<string name="loc_qth_text">QTH: %s</string>
|
||||||
|
<string name="connect_radio">Hubungkan</string>
|
||||||
|
<string name="disconnect_radio">Putuskan</string>
|
||||||
|
<string name="track_radio">Lacak</string>
|
||||||
|
<string name="stop_radio">Berhenti</string>
|
||||||
|
<string name="prefs_wavelog_title">Server log WaveLog</string>
|
||||||
|
<string name="prefs_wavelog_url_hint">Alamat server</string>
|
||||||
|
<string name="prefs_wavelog_key_hint">Kunci API</string>
|
||||||
|
<string name="prefs_wavelog_station_hint">ID stasiun</string>
|
||||||
|
<string name="prefs_wavelog_auto">Unggah otomatis</string>
|
||||||
|
<string name="prefs_wavelog_test">Uji koneksi</string>
|
||||||
|
<string name="prefs_wavelog_upload">Unggah sekarang</string>
|
||||||
|
<string name="wavelog_title">Log</string>
|
||||||
|
<string name="wavelog_select_transponder">Pilih transponder</string>
|
||||||
|
<string name="wavelog_call_hint">Tanda panggil</string>
|
||||||
|
<string name="wavelog_mode_hint">Mode</string>
|
||||||
|
<string name="wavelog_saved">Tersimpan ke log lokal</string>
|
||||||
|
<string name="wavelog_need_config">Konfigurasikan WaveLog di Pengaturan terlebih dahulu</string>
|
||||||
|
<string name="wavelog_empty">Belum ada entri log</string>
|
||||||
|
<string name="wavelog_grid_mismatch">Grid QTH (%1$s) tidak cocok dengan grid stasiun (%2$s). Tetap unggah?</string>
|
||||||
|
<string name="wavelog_ignore">Abaikan dan unggah</string>
|
||||||
|
<string name="wavelog_cancel">Batal</string>
|
||||||
|
<string name="wavelog_copy">Salin</string>
|
||||||
|
<string name="wavelog_delete_undo">Urungkan</string>
|
||||||
|
<string name="wavelog_col_time">Waktu</string>
|
||||||
|
<string name="wavelog_col_freq">Frek.</string>
|
||||||
|
<string name="wavelog_col_sat">Satelit</string>
|
||||||
|
<string name="wavelog_col_call">Panggilan</string>
|
||||||
|
<string name="wavelog_col_uploaded">Terkirim</string>
|
||||||
|
<string name="wavelog_ungrouped">Tidak dikelompokkan</string>
|
||||||
|
<string name="prefs_other_title">Pengaturan lainnya</string>
|
||||||
|
<string name="prefs_ui_title">Pengaturan UI</string>
|
||||||
|
<string name="prefs_ui_order_title">Urutan halaman</string>
|
||||||
|
<string name="prefs_ui_main_title">Menu utama (bilah bawah)</string>
|
||||||
|
<string name="prefs_ui_sub_title">Menu lainnya</string>
|
||||||
|
<string name="prefs_ui_move_out">Pindah ke menu lainnya</string>
|
||||||
|
<string name="prefs_ui_move_back">Kembalikan ke menu utama</string>
|
||||||
|
<string name="prefs_ui_order_locked">Tetap (tidak dapat dipindahkan)</string>
|
||||||
|
<string name="prefs_ui_order_reset">Setel ulang urutan</string>
|
||||||
|
<string name="prefs_other_switch_utc">Tampilkan waktu lintasan dalam UTC</string>
|
||||||
|
<string name="prefs_other_switch_update">Aktifkan pembaruan data otomatis</string>
|
||||||
|
<string name="prefs_other_switch_sweep">Aktifkan animasi sapuan radar</string>
|
||||||
|
<string name="prefs_other_switch_sensors">Gunakan sensor untuk memutar tampilan radar</string>
|
||||||
|
<string name="prefs_other_switch_night_mode">Aktifkan filter mode malam merah</string>
|
||||||
|
<string name="prefs_highlight_title">Sorotan elevasi</string>
|
||||||
|
<string name="prefs_highlight_low">Rendah < %1$d°</string>
|
||||||
|
<string name="prefs_highlight_mid">Sedang %1$d–%2$d°</string>
|
||||||
|
<string name="prefs_highlight_high">Tinggi > %1$d°</string>
|
||||||
|
<string name="prefs_outro_title">BG7NTA & penulis asli mengucapkan terima kasih</string>
|
||||||
|
<string name="prefs_outro_thanks" translatable="false">
|
||||||
|
* Pengguna dan kontributor Look4Sat!
|
||||||
|
\n* David A. B. Johnson (predict4java)
|
||||||
|
\n* Dave Moten (predict4java)
|
||||||
|
\n* Alexandru Csete (Gpredict)
|
||||||
|
\n* Dr T.S. Kelso (Celestrak)
|
||||||
|
\n* Libre Space Foundation (SatNOGS)
|
||||||
|
\n* xdsopl dan kontributor Robot36!
|
||||||
|
\n* BA7OPF (fitur pencocokan lintasan)
|
||||||
|
\n* BG7NTA</string>
|
||||||
|
<string name="prefs_outro_license">Aplikasi ini hadir tanpa jaminan</string>
|
||||||
|
</resources>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Look4Sat Pro</string>
|
<string name="app_name" translatable="false">Look4Sat Pro</string>
|
||||||
<string name="btn_accept">Terima</string>
|
<string name="btn_accept">Terima</string>
|
||||||
<string name="btn_cancel">Batal</string>
|
<string name="btn_cancel">Batal</string>
|
||||||
<string name="empty_list_message">Tidak ada data untuk ditampilkan</string>
|
<string name="empty_list_message">Tidak ada data untuk ditampilkan</string>
|
||||||
@@ -14,6 +14,17 @@
|
|||||||
<string name="nav_prefs">Pengaturan</string>
|
<string name="nav_prefs">Pengaturan</string>
|
||||||
<string name="nav_more">Lainnya</string>
|
<string name="nav_more">Lainnya</string>
|
||||||
<string name="nav_log">Log</string>
|
<string name="nav_log">Log</string>
|
||||||
|
<string name="nav_amsat">AMSAT</string>
|
||||||
|
<string name="amsat_title">Status Satelit AMSAT</string>
|
||||||
|
<string name="amsat_updated">Diperbarui:</string>
|
||||||
|
<string name="amsat_active">Aktif</string>
|
||||||
|
<string name="amsat_tlm">TLM/beacon</string>
|
||||||
|
<string name="amsat_not_heard">Tidak terdengar</string>
|
||||||
|
<string name="amsat_conflict">Konflik</string>
|
||||||
|
<string name="amsat_name">Name</string>
|
||||||
|
<string name="amsat_load_failed">Gagal memuat - periksa jaringan dan coba lagi</string>
|
||||||
|
<string name="amsat_no_reports">Tidak ada laporan untuk periode ini</string>
|
||||||
|
<string name="amsat_close">Tutup</string>
|
||||||
<string name="sat_type_hint">Jenis: %s</string>
|
<string name="sat_type_hint">Jenis: %s</string>
|
||||||
<string name="sat_type_title">Pilih jenis satelit</string>
|
<string name="sat_type_title">Pilih jenis satelit</string>
|
||||||
<string name="sat_search_hint">Id - Nama</string>
|
<string name="sat_search_hint">Id - Nama</string>
|
||||||
@@ -33,13 +44,13 @@
|
|||||||
<string name="pass_filter_aos_time">Jendela AOS</string>
|
<string name="pass_filter_aos_time">Jendela AOS</string>
|
||||||
<string name="pass_filter_invert_time">Balik jendela AOS</string>
|
<string name="pass_filter_invert_time">Balik jendela AOS</string>
|
||||||
<string name="pass_filter_deep_space">DeepSpace (periode >225 mnt)</string>
|
<string name="pass_filter_deep_space">DeepSpace (periode >225 mnt)</string>
|
||||||
<string name="pass_time_placeholder"> -- : -- : -- </string>
|
<string name="pass_time_placeholder" translatable="false"> -- : -- : -- </string>
|
||||||
<string name="pass_modes_title">Pilih jenis modulasi</string>
|
<string name="pass_modes_title">Pilih jenis modulasi</string>
|
||||||
<string name="pass_satId">%05d</string>
|
<string name="pass_satId" translatable="false">%05d</string>
|
||||||
<string name="pass_elevation">Elevasi: %.1f°</string>
|
<string name="pass_elevation">Elevasi: %.1f°</string>
|
||||||
<string name="pass_deep_space">DeepSpace</string>
|
<string name="pass_deep_space" translatable="false">DeepSpace</string>
|
||||||
<string name="pass_altitude">Ketinggian: %d km</string>
|
<string name="pass_altitude">Ketinggian: %d km</string>
|
||||||
<string name="pass_aosLos">%03d° - %03d°</string>
|
<string name="pass_aosLos" translatable="false">%03d° - %03d°</string>
|
||||||
<string name="pass_empty_list_message">
|
<string name="pass_empty_list_message">
|
||||||
Pastikan filter Anda benar dan Anda sudah memilih satelit</string>
|
Pastikan filter Anda benar dan Anda sudah memilih satelit</string>
|
||||||
<string name="pass_error_message">Tidak ada lintasan satelit mendatang untuk ditampilkan</string>
|
<string name="pass_error_message">Tidak ada lintasan satelit mendatang untuk ditampilkan</string>
|
||||||
@@ -47,26 +58,30 @@
|
|||||||
<string name="pass_welcome_message">
|
<string name="pass_welcome_message">
|
||||||
Pastikan Anda mengatur posisi melalui GPS, Lat/Lon atau QTH di Pengaturan.
|
Pastikan Anda mengatur posisi melalui GPS, Lat/Lon atau QTH di Pengaturan.
|
||||||
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
\n\nPerbarui database setidaknya seminggu sekali untuk prediksi yang akurat.</string>
|
||||||
<string name="pass_whatsnew_title">Apa yang baru di Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">Apa yang baru di Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* WaveLog kini mendukung API v1 dan v2 (terdeteksi otomatis); dialog kesalahan dengan tombol salin
|
* BARU: Halaman status satelit AMSAT - tabel status langsung (warna resmi), jumlah laporan, laporan detail per satelit/hari
|
||||||
* Frekuensi log cocok dengan panel Doppler; transponder linier menampilkan rentang penuh</string>
|
* Log dikelompokkan per lintasan satelit (sesi = satelit + waktu AOS), garis pemisah antar sesi
|
||||||
|
* GPS kini hanya menampilkan sukses saat posisi benar-benar didapat (timeout 15 dtk, cek izin)
|
||||||
|
* Pesan kegagalan jelas saat database satelit gagal diunduh
|
||||||
|
* Frekuensi log mengikuti panel Doppler secara real-time; visual hapus geser dipulihkan
|
||||||
|
* Perbaikan bahasa Indonesia (values-id)</string>
|
||||||
<string name="radar_back">Kembali</string>
|
<string name="radar_back">Kembali</string>
|
||||||
<string name="radar_notify">Beri tahu</string>
|
<string name="radar_notify">Beri tahu</string>
|
||||||
<string name="radar_az_text">Azimuth</string>
|
<string name="radar_az_text">Azimuth</string>
|
||||||
<string name="radar_az_value">%.1f°</string>
|
<string name="radar_az_value" translatable="false">%.1f°</string>
|
||||||
<string name="radar_el_text">Elevasi</string>
|
<string name="radar_el_text">Elevasi</string>
|
||||||
<string name="radar_el_value">%.1f°</string>
|
<string name="radar_el_value" translatable="false">%.1f°</string>
|
||||||
<string name="radar_alt_text">Ketinggian</string>
|
<string name="radar_alt_text">Ketinggian</string>
|
||||||
<string name="radar_alt_value">%.0f km</string>
|
<string name="radar_alt_value" translatable="false">%.0f km</string>
|
||||||
<string name="radar_dist_text">Jarak</string>
|
<string name="radar_dist_text">Jarak</string>
|
||||||
<string name="radar_dist_value">%.0f km</string>
|
<string name="radar_dist_value" translatable="false">%.0f km</string>
|
||||||
<string name="radar_eclipsed">Gerhana</string>
|
<string name="radar_eclipsed">Gerhana</string>
|
||||||
<string name="radar_downlink">Downlink</string>
|
<string name="radar_downlink">Downlink</string>
|
||||||
<string name="radar_uplink">Uplink</string>
|
<string name="radar_uplink">Uplink</string>
|
||||||
<string name="radar_link_low">%.4f</string>
|
<string name="radar_link_low" translatable="false">%.4f</string>
|
||||||
<string name="radar_link_lowHigh">%.4f - %.4f</string>
|
<string name="radar_link_lowHigh" translatable="false">%.4f - %.4f</string>
|
||||||
<string name="radar_no_link">- - . - -</string>
|
<string name="radar_no_link" translatable="false">- - . - -</string>
|
||||||
<string name="radar_no_data">Satelit ini tidak memiliki transceiver</string>
|
<string name="radar_no_data">Satelit ini tidak memiliki transceiver</string>
|
||||||
<string name="radar_add">Tambah</string>
|
<string name="radar_add">Tambah</string>
|
||||||
<string name="radar_data">Transceiver satelit:</string>
|
<string name="radar_data">Transceiver satelit:</string>
|
||||||
@@ -86,32 +101,32 @@
|
|||||||
<string name="radar_cw_reset">Bersihkan</string>
|
<string name="radar_cw_reset">Bersihkan</string>
|
||||||
<string name="map_prev">Sebelumnya</string>
|
<string name="map_prev">Sebelumnya</string>
|
||||||
<string name="map_next">Berikutnya</string>
|
<string name="map_next">Berikutnya</string>
|
||||||
<string name="map_copyright">© Kontributor OpenStreetMap</string>
|
<string name="map_copyright" translatable="false">© Kontributor OpenStreetMap</string>
|
||||||
<string name="map_azimuth">Azimuth: %.1f°</string>
|
<string name="map_azimuth">Azimuth: %.1f°</string>
|
||||||
<string name="map_elevation">Elevasi: %.1f°</string>
|
<string name="map_elevation">Elevasi: %.1f°</string>
|
||||||
<string name="map_altitude">Ketinggian: %.0f km</string>
|
<string name="map_altitude">Ketinggian: %.0f km</string>
|
||||||
<string name="map_distance">Jarak: %.0f km</string>
|
<string name="map_distance">Jarak: %.0f km</string>
|
||||||
<string name="map_latitude">Lintang: %.1f°</string>
|
<string name="map_latitude">Lintang: %.1f°</string>
|
||||||
<string name="map_longitude">Bujur: %.1f°</string>
|
<string name="map_longitude">Bujur: %.1f°</string>
|
||||||
<string name="map_qth">QTH: %s</string>
|
<string name="map_qth" translatable="false">QTH: %s</string>
|
||||||
<string name="map_phase">Fase: %.1f°</string>
|
<string name="map_phase">Fase: %.1f°</string>
|
||||||
<string name="map_eclipsed">Gerhana</string>
|
<string name="map_eclipsed">Gerhana</string>
|
||||||
<string name="map_period">Periode: %.0f mnt</string>
|
<string name="map_period">Periode: %.0f mnt</string>
|
||||||
<string name="map_velocity">Kecepatan: %.2f km/d</string>
|
<string name="map_velocity">Kecepatan: %.2f km/d</string>
|
||||||
<string name="map_visibility">Visibilitas: %s</string>
|
<string name="map_visibility">Visibilitas: %s</string>
|
||||||
<string name="map_visible">Terlihat</string>
|
<string name="map_visible">Terlihat</string>
|
||||||
<string name="prefs_app_title">Look4Sat Pro v%s</string>
|
<string name="prefs_app_title" translatable="false">Look4Sat Pro v%s</string>
|
||||||
<string name="prefs_app_url">https://play.google.com/store/apps/details?id=com.rtbishop.look4sat</string>
|
<string name="prefs_app_url" translatable="false">https://play.google.com/store/apps/details?id=com.rtbishop.look4sat</string>
|
||||||
<string name="prefs_donate_title">Donasi</string>
|
<string name="prefs_donate_title">Donasi</string>
|
||||||
<string name="prefs_donate_url">https://ko-fi.com/rt_bishop</string>
|
<string name="prefs_donate_url" translatable="false">https://ko-fi.com/rt_bishop</string>
|
||||||
<string name="prefs_fdroid_title">F-Droid</string>
|
<string name="prefs_fdroid_title" translatable="false">F-Droid</string>
|
||||||
<string name="prefs_fdroid_url">https://f-droid.org/en/packages/com.rtbishop.look4sat/</string>
|
<string name="prefs_fdroid_url" translatable="false">https://f-droid.org/en/packages/com.rtbishop.look4sat/</string>
|
||||||
<string name="prefs_github_title">GitHub</string>
|
<string name="prefs_github_title" translatable="false">GitHub</string>
|
||||||
<string name="prefs_github_url">https://github.com/rt-bishop/Look4Sat/</string>
|
<string name="prefs_github_url" translatable="false">https://github.com/rt-bishop/Look4Sat/</string>
|
||||||
<string name="prefs_license_title">GPLv3</string>
|
<string name="prefs_license_title" translatable="false">GPLv3</string>
|
||||||
<string name="prefs_license_url">https://www.gnu.org/licenses/gpl-3.0.html</string>
|
<string name="prefs_license_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.html</string>
|
||||||
<string name="prefs_privacy_title">Kebijakan Privasi</string>
|
<string name="prefs_privacy_title">Kebijakan Privasi</string>
|
||||||
<string name="prefs_privacy_url">https://sites.google.com/view/look4sat-privacy-policy/home</string>
|
<string name="prefs_privacy_url" translatable="false">https://sites.google.com/view/look4sat-privacy-policy/home</string>
|
||||||
<string name="prefs_updated_title">Diperbarui: %s</string>
|
<string name="prefs_updated_title">Diperbarui: %s</string>
|
||||||
<string name="prefs_updated_time">d MMM yyyy - HH:mm:ss</string>
|
<string name="prefs_updated_time">d MMM yyyy - HH:mm:ss</string>
|
||||||
<string name="prefs_loc_title">Posisi stasiun</string>
|
<string name="prefs_loc_title">Posisi stasiun</string>
|
||||||
@@ -141,7 +156,7 @@
|
|||||||
<string name="prefs_data_sources_title">Sumber data kustom</string>
|
<string name="prefs_data_sources_title">Sumber data kustom</string>
|
||||||
<string name="prefs_data_sources_tle_switch">URL TLE Kustom</string>
|
<string name="prefs_data_sources_tle_switch">URL TLE Kustom</string>
|
||||||
<string name="prefs_data_sources_transceivers_switch">URL transceiver kustom</string>
|
<string name="prefs_data_sources_transceivers_switch">URL transceiver kustom</string>
|
||||||
<string name="prefs_data_sources_url_title">URL (HTTPS)</string>
|
<string name="prefs_data_sources_url_title" translatable="false">URL (HTTPS)</string>
|
||||||
<string name="prefs_data_reset_url">Setel ulang ke URL bawaan</string>
|
<string name="prefs_data_reset_url">Setel ulang ke URL bawaan</string>
|
||||||
<string name="prefs_data_output_title">Keluaran data</string>
|
<string name="prefs_data_output_title">Keluaran data</string>
|
||||||
<string name="prefs_net_output">Jaringan</string>
|
<string name="prefs_net_output">Jaringan</string>
|
||||||
@@ -223,7 +238,7 @@
|
|||||||
<string name="prefs_highlight_mid">Sedang %1$d–%2$d°</string>
|
<string name="prefs_highlight_mid">Sedang %1$d–%2$d°</string>
|
||||||
<string name="prefs_highlight_high">Tinggi > %1$d°</string>
|
<string name="prefs_highlight_high">Tinggi > %1$d°</string>
|
||||||
<string name="prefs_outro_title">BG7NTA & penulis asli mengucapkan terima kasih</string>
|
<string name="prefs_outro_title">BG7NTA & penulis asli mengucapkan terima kasih</string>
|
||||||
<string name="prefs_outro_thanks">
|
<string name="prefs_outro_thanks" translatable="false">
|
||||||
* Pengguna dan kontributor Look4Sat!
|
* Pengguna dan kontributor Look4Sat!
|
||||||
\n* David A. B. Johnson (predict4java)
|
\n* David A. B. Johnson (predict4java)
|
||||||
\n* Dave Moten (predict4java)
|
\n* Dave Moten (predict4java)
|
||||||
|
|||||||
@@ -15,6 +15,17 @@
|
|||||||
<string name="nav_prefs">Ayarlar</string>
|
<string name="nav_prefs">Ayarlar</string>
|
||||||
<string name="nav_more">Daha Fazla</string>
|
<string name="nav_more">Daha Fazla</string>
|
||||||
<string name="nav_log">Günlük</string>
|
<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_updated">Güncellendi:</string>
|
||||||
|
<string name="amsat_active">Aktif</string>
|
||||||
|
<string name="amsat_tlm">TLM/işaret</string>
|
||||||
|
<string name="amsat_not_heard">Duyulmadı</string>
|
||||||
|
<string name="amsat_conflict">Çelişkili</string>
|
||||||
|
<string name="amsat_name">Name</string>
|
||||||
|
<string name="amsat_load_failed">Yükleme başarısız - ağı kontrol edip tekrar deneyin</string>
|
||||||
|
<string name="amsat_no_reports">Bu dönem için rapor yok</string>
|
||||||
|
<string name="amsat_close">Kapat</string>
|
||||||
|
|
||||||
<!-- Satellites screen -->
|
<!-- Satellites screen -->
|
||||||
<string name="sat_type_hint">Uydu türü: %s</string>
|
<string name="sat_type_hint">Uydu türü: %s</string>
|
||||||
@@ -54,8 +65,12 @@
|
|||||||
\n\nDoğru tahminler alabilmek için veritabanını en az haftada bir güncelleyin.</string>
|
\n\nDoğru tahminler alabilmek için veritabanını en az haftada bir güncelleyin.</string>
|
||||||
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* WaveLog artık hem API v1 hem v2 destekliyor (otomatik algılama); kopyalama butonlu hata penceresi
|
* YENİ: AMSAT uydu durum sayfası - canlı durum tablosu (resmi renkler), rapor sayıları, uydu/gün bazında detaylı raporlar
|
||||||
* Günlük frekansları Doppler paneliyle eşleşiyor; doğrusal transponderler tam aralığı gösteriyor</string>
|
* Günlükler uydu geçişine göre gruplanır (oturum = uydu + AOS zamanı), oturumlar arasında ayırıcı çizgi
|
||||||
|
* GPS artık yalnızca gerçek konum alındığında başarı gösterir (15 sn zaman aşımı, izin kontrolü)
|
||||||
|
* Uydu veritabanı indirilemediğinde net hata mesajı gösterilir
|
||||||
|
* Günlük frekansları Doppler panelini gerçek zamanlı takip eder; kaydırarak silme görselleri düzeltildi
|
||||||
|
* Endonezya dili düzeltmesi (values-id)</string>
|
||||||
|
|
||||||
<!-- Radar screen -->
|
<!-- Radar screen -->
|
||||||
<string name="radar_back">Geri</string>
|
<string name="radar_back">Geri</string>
|
||||||
|
|||||||
@@ -16,6 +16,17 @@
|
|||||||
<string name="nav_prefs">设置</string>
|
<string name="nav_prefs">设置</string>
|
||||||
<string name="nav_more">更多</string>
|
<string name="nav_more">更多</string>
|
||||||
<string name="nav_log">日志</string>
|
<string name="nav_log">日志</string>
|
||||||
|
<string name="nav_amsat">AMSAT</string>
|
||||||
|
<string name="amsat_title">AMSAT 卫星状态</string>
|
||||||
|
<string name="amsat_updated">更新:</string>
|
||||||
|
<string name="amsat_active">活跃</string>
|
||||||
|
<string name="amsat_tlm">仅遥测</string>
|
||||||
|
<string name="amsat_not_heard">未听到</string>
|
||||||
|
<string name="amsat_conflict">矛盾</string>
|
||||||
|
<string name="amsat_name">Name</string>
|
||||||
|
<string name="amsat_load_failed">加载失败,请检查网络后重试</string>
|
||||||
|
<string name="amsat_no_reports">该时段无报告</string>
|
||||||
|
<string name="amsat_close">关闭</string>
|
||||||
|
|
||||||
<!-- Satellites screen -->
|
<!-- Satellites screen -->
|
||||||
<string name="sat_type_hint">类型:%s</string>
|
<string name="sat_type_hint">类型:%s</string>
|
||||||
@@ -44,8 +55,12 @@
|
|||||||
<string name="pass_welcome_message">请务必在设置中通过GPS、经纬度或QTH定位您的位置\n建议至少每周更新一次数据库,以确保预测结果的准确性</string>
|
<string name="pass_welcome_message">请务必在设置中通过GPS、经纬度或QTH定位您的位置\n建议至少每周更新一次数据库,以确保预测结果的准确性</string>
|
||||||
<string name="pass_whatsnew_title">Look4Sat Pro 更新内容</string>
|
<string name="pass_whatsnew_title">Look4Sat Pro 更新内容</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* WaveLog 同时支持 API v1 和 v2(自动识别); 错误弹窗带一键复制
|
* 新增: AMSAT 卫星状态页 - 实时状态表格(官网配色)、报告数量、逐卫星/逐日详细报告
|
||||||
* 日志频率与多普勒面板一致; 线性转发器显示完整频段区间</string>
|
* 日志按场次分组(场次 = 卫星 + AOS 时间), 组间横线分隔
|
||||||
|
* GPS 真正定位成功才会提示成功(15 秒超时, 权限检查)
|
||||||
|
* 卫星数据库下载失败时有明确失败提示
|
||||||
|
* 日志频率实时跟随多普勒面板; 左滑删除视觉恢复
|
||||||
|
* 印尼语识别修复(values-id)</string>
|
||||||
|
|
||||||
<!-- Radar screen -->
|
<!-- Radar screen -->
|
||||||
<string name="radar_back">后退</string>
|
<string name="radar_back">后退</string>
|
||||||
|
|||||||
@@ -17,6 +17,17 @@
|
|||||||
<string name="nav_prefs">Settings</string>
|
<string name="nav_prefs">Settings</string>
|
||||||
<string name="nav_more">More</string>
|
<string name="nav_more">More</string>
|
||||||
<string name="nav_log">Log</string>
|
<string name="nav_log">Log</string>
|
||||||
|
<string name="nav_amsat">AMSAT</string>
|
||||||
|
<string name="amsat_title">AMSAT Satellite Status</string>
|
||||||
|
<string name="amsat_updated">Updated:</string>
|
||||||
|
<string name="amsat_active">Active</string>
|
||||||
|
<string name="amsat_tlm">TLM/Beacon</string>
|
||||||
|
<string name="amsat_not_heard">Not Heard</string>
|
||||||
|
<string name="amsat_conflict">Conflicting</string>
|
||||||
|
<string name="amsat_name">Name</string>
|
||||||
|
<string name="amsat_load_failed">Load failed - check network and retry</string>
|
||||||
|
<string name="amsat_no_reports">No reports for this period</string>
|
||||||
|
<string name="amsat_close">Close</string>
|
||||||
|
|
||||||
<!-- Satellites screen -->
|
<!-- Satellites screen -->
|
||||||
<string name="sat_type_hint">Type: %s</string>
|
<string name="sat_type_hint">Type: %s</string>
|
||||||
@@ -56,8 +67,12 @@
|
|||||||
\n\nPlease update the database at least weekly to get accurate predictions.</string>
|
\n\nPlease update the database at least weekly to get accurate predictions.</string>
|
||||||
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
|
||||||
<string name="pass_whatsnew_message" translatable="false">
|
<string name="pass_whatsnew_message" translatable="false">
|
||||||
* WaveLog now supports both API v1 and v2 (auto-detected); error dialog with copy button
|
* NEW: AMSAT satellite status page - live status grid (official colors), report counts, detailed reports per satellite/day
|
||||||
* Log frequencies match the Doppler panel; linear transponders show the full range</string>
|
* Log entries grouped by satellite pass (session ID = satellite + AOS time), divider lines between sessions
|
||||||
|
* GPS now only reports success when a real fix is acquired (15s timeout, permission check)
|
||||||
|
* Data update shows a clear failure message when the satellite database cannot be downloaded
|
||||||
|
* Log frequencies follow the Doppler panel in real time; swipe-to-delete visuals restored
|
||||||
|
* Indonesian locale fix (values-id)</string>
|
||||||
|
|
||||||
<!-- Radar screen -->
|
<!-- Radar screen -->
|
||||||
<string name="radar_back">Back</string>
|
<string name="radar_back">Back</string>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_500">#ff6200ee</color>
|
||||||
|
<integer name="default_font_size">18</integer>
|
||||||
|
<string name="idle">Diam</string>
|
||||||
|
<string name="waterfall_display">Tampilan waterfall</string>
|
||||||
|
<string name="frequency_scale">Skala frekuensi</string>
|
||||||
|
<string name="decoded_text">Teks terdekode</string>
|
||||||
|
<string name="clear">Bersihkan</string>
|
||||||
|
<string name="pause">Jeda Dekode</string>
|
||||||
|
<string name="save_text">Simpan Teks</string>
|
||||||
|
<string name="record_signals">Rekam Sinyal</string>
|
||||||
|
<string name="settings">Pengaturan</string>
|
||||||
|
<string name="help">Bantuan Daring</string>
|
||||||
|
<string name="rate">Nilai Aplikasi Ini</string>
|
||||||
|
<string name="premium">Dapatkan Versi Premium</string>
|
||||||
|
<string name="tap_back_again_to_close">Ketuk Kembali lagi untuk menutup aplikasi</string>
|
||||||
|
<string name="message_type">Tipe Pesan</string>
|
||||||
|
<string name="cw_mic_permission">Izin mikrofon diperlukan untuk dekode CW</string>
|
||||||
|
<string name="cw_grant_permission">Beri izin</string>
|
||||||
|
<string name="cw_open_settings">Buka pengaturan aplikasi</string>
|
||||||
|
</resources>
|
||||||
@@ -40,6 +40,7 @@ import androidx.compose.material3.ExposedDropdownMenuBox
|
|||||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
import androidx.compose.material3.MenuAnchorType
|
import androidx.compose.material3.MenuAnchorType
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -179,7 +180,19 @@ fun LogTab(
|
|||||||
modifier = Modifier.padding(vertical = 8.dp)
|
modifier = Modifier.padding(vertical = 8.dp)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
entries.forEach { entry ->
|
// 按场次分组(sessionId = 卫星名-AOS 时间戳), 组间粗线分隔
|
||||||
|
entries.groupBy { it.sessionId.ifBlank { "un" } }.forEach { (sessionId, groupEntries) ->
|
||||||
|
// 组标题 + 分隔线
|
||||||
|
HorizontalDivider(thickness = 2.dp, color = MaterialTheme.colorScheme.primary)
|
||||||
|
Text(
|
||||||
|
text = if (sessionId == "un") stringResource(id = R.string.wavelog_ungrouped)
|
||||||
|
else sessionId.substringBeforeLast('-'),
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp)
|
||||||
|
)
|
||||||
|
groupEntries.forEach { entry ->
|
||||||
SwipeDeleteRow(
|
SwipeDeleteRow(
|
||||||
onDelete = {
|
onDelete = {
|
||||||
queue.remove(entry.id)
|
queue.remove(entry.id)
|
||||||
@@ -216,6 +229,7 @@ fun LogTab(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 转发器展开输入区: 实时频率 + 呼号(回车存本地) + 模式修正 */
|
/** 转发器展开输入区: 实时频率 + 呼号(回车存本地) + 模式修正 */
|
||||||
|
|||||||
+1
@@ -705,6 +705,7 @@ private fun UiSettingsCard(
|
|||||||
R.string.nav_roaming to "Roaming",
|
R.string.nav_roaming to "Roaming",
|
||||||
R.string.nav_cw to "CwDecode",
|
R.string.nav_cw to "CwDecode",
|
||||||
R.string.nav_log to "WavelogLog",
|
R.string.nav_log to "WavelogLog",
|
||||||
|
R.string.nav_amsat to "AMSAT",
|
||||||
R.string.nav_map to "Map",
|
R.string.nav_map to "Map",
|
||||||
R.string.nav_prefs to "Settings"
|
R.string.nav_prefs to "Settings"
|
||||||
) // name 必须与 Screen.screenId 一致 (R8 安全)
|
) // name 必须与 Screen.screenId 一致 (R8 安全)
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import com.android.build.api.dsl.LibraryExtension
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.convention.featurePlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.rtbishop.look4sat.feature.status"
|
||||||
|
compileOptions {
|
||||||
|
encoding = "UTF-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":core:domain"))
|
||||||
|
implementation(project(":core:presentation"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,343 @@
|
|||||||
|
package com.rtbishop.look4sat.feature.status
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.animateFloat
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
|
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.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.SatDay
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.SatReport
|
||||||
|
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 java.util.Calendar
|
||||||
|
|
||||||
|
// ========== 官网状态色(amsat.org/status 原色) ==========
|
||||||
|
val ActiveBlue = Color(0xFF648FFF)
|
||||||
|
val TlmOrange = Color(0xFFFFB000)
|
||||||
|
val NotHeardPink = Color(0xFFDC267F)
|
||||||
|
val ConflictDeepOrange = Color(0xFFFE6100)
|
||||||
|
val NoReportGray = Color(0xFFC0C0C0)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SatStatusScreen(container: IMainContainer) {
|
||||||
|
val viewModel: SatStatusViewModel = viewModel(
|
||||||
|
factory = viewModelFactory {
|
||||||
|
initializer { SatStatusViewModel(container) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
var selectedDay by remember { mutableStateOf<Pair<SatStatus, SatDay>?>(null) }
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp)) {
|
||||||
|
// 顶部: 图例 + 刷新
|
||||||
|
StatusHeader(
|
||||||
|
fetchedAtUtcMs = uiState.fetchedAtUtcMs,
|
||||||
|
isRefreshing = uiState.isRefreshing,
|
||||||
|
onRefresh = { viewModel.refresh() }
|
||||||
|
)
|
||||||
|
LegendRow()
|
||||||
|
|
||||||
|
when {
|
||||||
|
uiState.isLoading -> {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uiState.error != null && uiState.statuses.isEmpty() -> {
|
||||||
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.amsat_load_failed),
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// 表头
|
||||||
|
HeaderRow(statuses = uiState.statuses)
|
||||||
|
HorizontalDivider(thickness = 1.dp)
|
||||||
|
// 行
|
||||||
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
|
items(uiState.statuses, key = { it.name }) { status ->
|
||||||
|
StatusRow(
|
||||||
|
status = status,
|
||||||
|
onClickDay = { day -> selectedDay = status to day }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedDay?.let { (status, day) ->
|
||||||
|
ReportDialog(
|
||||||
|
statusName = status.name,
|
||||||
|
day = day,
|
||||||
|
reports = uiState.reports,
|
||||||
|
onDismiss = { selectedDay = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 顶部: 更新时间 + 刷新按钮(加载中旋转动画) */
|
||||||
|
@Composable
|
||||||
|
private fun StatusHeader(
|
||||||
|
fetchedAtUtcMs: Long,
|
||||||
|
isRefreshing: Boolean,
|
||||||
|
onRefresh: () -> Unit
|
||||||
|
) {
|
||||||
|
val infiniteTransition = rememberInfiniteTransition(label = "refresh")
|
||||||
|
val angle by infiniteTransition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = 360f,
|
||||||
|
animationSpec = infiniteRepeatable(tween(800), RepeatMode.Restart),
|
||||||
|
label = "angle"
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (fetchedAtUtcMs > 0) stringResource(id = R.string.amsat_updated) + " " + formatFetchedAt(fetchedAtUtcMs) else stringResource(id = R.string.amsat_title),
|
||||||
|
fontSize = 13.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(34.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.clickable(onClick = onRefresh),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图例(官网四色) */
|
||||||
|
@Composable
|
||||||
|
private fun LegendRow() {
|
||||||
|
val legend = listOf(
|
||||||
|
stringResource(id = R.string.amsat_active) to ActiveBlue,
|
||||||
|
stringResource(id = R.string.amsat_tlm) to TlmOrange,
|
||||||
|
stringResource(id = R.string.amsat_not_heard) to NotHeardPink,
|
||||||
|
stringResource(id = R.string.amsat_conflict) to ConflictDeepOrange
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
legend.forEach { (label, color) ->
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(10.dp)
|
||||||
|
.clip(RoundedCornerShape(2.dp))
|
||||||
|
.background(color)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(3.dp))
|
||||||
|
Text(text = label, fontSize = 10.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 表头: 卫星名 + 6 天日期 */
|
||||||
|
@Composable
|
||||||
|
private fun HeaderRow(statuses: List<SatStatus>) {
|
||||||
|
val dates = statuses.firstOrNull()?.days?.map { it.dateLabel } ?: emptyList()
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.amsat_name),
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.weight(1.4f).padding(start = 4.dp)
|
||||||
|
)
|
||||||
|
dates.forEach { date ->
|
||||||
|
Text(
|
||||||
|
text = date,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 卫星行: 名称 + 6 天色块(官网色 + 报告数) */
|
||||||
|
@Composable
|
||||||
|
private fun StatusRow(status: SatStatus, onClickDay: (SatDay) -> Unit) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 3.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = status.name,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.weight(1.4f).padding(start = 4.dp)
|
||||||
|
)
|
||||||
|
status.days.forEach { day ->
|
||||||
|
val slot = day.slots.firstOrNull { it.statusColor != NoReportGray.value.toInt().toLong() } ?: day.slots.first()
|
||||||
|
DayCell(
|
||||||
|
slot = slot,
|
||||||
|
modifier = Modifier.weight(1f).padding(horizontal = 1.dp),
|
||||||
|
onClick = { onClickDay(day) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单天色块: 取当天 12 槽中最新的有报告状态; 无报告则灰 */
|
||||||
|
@Composable
|
||||||
|
private fun DayCell(slot: SatSlot, modifier: Modifier, onClick: () -> Unit) {
|
||||||
|
val color = Color(slot.statusColor)
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.height(26.dp)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.background(color)
|
||||||
|
.clickable(onClick = onClick),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (slot.count > 0) {
|
||||||
|
Text(
|
||||||
|
text = slot.count.toString(),
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 报告详情弹窗(3 级: 呼号/日期/时间/网格) */
|
||||||
|
@Composable
|
||||||
|
private fun ReportDialog(
|
||||||
|
statusName: String,
|
||||||
|
day: SatDay,
|
||||||
|
reports: Map<String, SatReport>,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
val dayReports = day.slots.flatMap { it.reportIds }
|
||||||
|
.mapNotNull { reports[it] }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text(text = "$statusName · ${day.dateLabel}") },
|
||||||
|
text = {
|
||||||
|
if (dayReports.isEmpty()) {
|
||||||
|
Text(stringResource(id = R.string.amsat_no_reports))
|
||||||
|
} else {
|
||||||
|
LazyColumn(modifier = Modifier.height(320.dp)) {
|
||||||
|
items(dayReports) { report ->
|
||||||
|
Column(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(8.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(statusColorOf(report.statusText))
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
Text(
|
||||||
|
text = report.statusText,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = "${report.call} ${report.dateUtc} ${report.timeUtc}" +
|
||||||
|
if (report.grid.isNotBlank() && report.grid != "-") " ${report.grid}" else "",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
HorizontalDivider(thickness = 0.5.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) { Text(stringResource(id = R.string.amsat_close)) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun statusColorOf(statusText: String): Color = when {
|
||||||
|
statusText.contains("Heard", ignoreCase = true) && !statusText.contains("Not", ignoreCase = true) -> ActiveBlue
|
||||||
|
statusText.contains("Telemetry", ignoreCase = true) || statusText.contains("Beacon", ignoreCase = true) -> TlmOrange
|
||||||
|
statusText.contains("Not Heard", ignoreCase = true) -> NotHeardPink
|
||||||
|
else -> ConflictDeepOrange
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatFetchedAt(utcMs: Long): String {
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
cal.timeInMillis = utcMs
|
||||||
|
return "%02d-%02d %02d:%02d".format(
|
||||||
|
cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH),
|
||||||
|
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)
|
||||||
|
)
|
||||||
|
}
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package com.rtbishop.look4sat.feature.status
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.SatReport
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.SatStatus
|
||||||
|
import com.rtbishop.look4sat.core.domain.repository.IMainContainer
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
data class SatStatusUiState(
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val isRefreshing: Boolean = false,
|
||||||
|
val statuses: List<SatStatus> = emptyList(),
|
||||||
|
val reports: Map<String, SatReport> = emptyMap(),
|
||||||
|
val fetchedAtUtcMs: Long = 0L,
|
||||||
|
val error: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
class SatStatusViewModel(
|
||||||
|
private val container: IMainContainer
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow(SatStatusUiState(isLoading = true))
|
||||||
|
val uiState: StateFlow<SatStatusUiState> = _uiState
|
||||||
|
|
||||||
|
init {
|
||||||
|
fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetch() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
_uiState.update { it.copy(isLoading = true, error = null) }
|
||||||
|
try {
|
||||||
|
val page = container.amSatRepo.fetchStatus()
|
||||||
|
if (page != null && page.statuses.isNotEmpty()) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
isRefreshing = false,
|
||||||
|
statuses = page.statuses,
|
||||||
|
reports = page.reports,
|
||||||
|
fetchedAtUtcMs = page.fetchedAtUtcMs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(isLoading = false, isRefreshing = false, error = "fetch_failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(isLoading = false, isRefreshing = false, error = exception.message ?: "fetch_failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
if (_uiState.value.isRefreshing) return
|
||||||
|
_uiState.update { it.copy(isRefreshing = true, error = null) }
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val page = container.amSatRepo.fetchStatus()
|
||||||
|
if (page != null && page.statuses.isNotEmpty()) {
|
||||||
|
_uiState.update {
|
||||||
|
it.copy(
|
||||||
|
isRefreshing = false,
|
||||||
|
statuses = page.statuses,
|
||||||
|
reports = page.reports,
|
||||||
|
fetchedAtUtcMs = page.fetchedAtUtcMs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_uiState.update { it.copy(isRefreshing = false, error = "fetch_failed") }
|
||||||
|
}
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
_uiState.update { it.copy(isRefreshing = false, error = exception.message ?: "fetch_failed") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[versions]
|
[versions]
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionCode = "452"
|
appVersionCode = "453"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
appVersionName = "4.5.2"
|
appVersionName = "4.5.3"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
compileSdk = "37"
|
compileSdk = "37"
|
||||||
#noinspection UnusedVersionCatalogEntry
|
#noinspection UnusedVersionCatalogEntry
|
||||||
|
|||||||
+2
-1
@@ -28,5 +28,6 @@ include(
|
|||||||
":feature:radar",
|
":feature:radar",
|
||||||
":feature:roaming",
|
":feature:roaming",
|
||||||
":feature:satellites",
|
":feature:satellites",
|
||||||
":feature:settings"
|
":feature:settings",
|
||||||
|
":feature:status"
|
||||||
)
|
)
|
||||||
Reference in new issue
Block a user