mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat(roaming): port QTH定位器 2.0 page and logic verbatim
The roaming page was repeatedly rebuilt by hand and the red marker still rendered at the wrong spot on the user's device. Per the user's explicit instruction the whole page is now a faithful, line-by-line port of the reference app (QTH定位器 2.0, com.us1pm.gridsquarelocator) with zero UI or logic changes. UI (res/layout/main.xml, byte-verified via aapt2 dump): - 25dp holo-blue GPS bar: "GPS" 14sp, green/red status dot 15dp (original mipmaps copied as drawables), centered date, right time, translucent-yellow "设置启用GPS" button that opens location settings - Latitude/longitude rows: 16sp black labels, right-aligned decimal values, DMS label format "纬度 22° 18' 50" N" exactly as reference - 43sp bold black locator, centered, with progress spinner + notice - 3x3 continuous grid: 60/200/60dp columns, middle row fixed 205dp, edge cells #0BACF1, center cell 200x200dp holo-blue (#01DDFF as shown on the user's device), center label 100x80dp 30sp bold white (textColorHighlight) centered, bottom "制作:US1PM 汉化:BA7LCE" - Red marker: original pnt.png (red square with white outline), 10x10dp, absolutely positioned by the reference lookup tables (lon 3rd pair a..x -> leftMargin -2..190dp, lat 3rd pair a..x -> topMargin 190..-2dp, screen-Y inverted) Logic (MainActivity.java showLocation/checkEnabled/onResume): - 8-char locator via the reference range-lookup tables - 3x3 neighbor grid via parseInt3 five-branch logic incl. all four corner-carry tables (00/09/99/90) - Live GPS + network updates 10s/10m while the page is shown, provider filtered to gps/network, checkEnabled three-state (green dot / red dot + settings button) exactly like the reference - Time = cached hour prefix + fix minutes; date "dd MMM yyyy" Dropped only the Play-store ad banner (conflicts with GPL project). Verified: 11 unit tests pass (locator, marker lookup, grid, DMS, time, all edge branches); feature + app modules compile. RoamingState.kt removed - state and math now live in RoamingScreen.kt.
This commit is contained in:
1 parent
88d1498eac
commit
0ecccc4746
6 files changed
+818
-330
No files matched your search
+694
-252
File diff suppressed because it is too large.
Load diff
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.feature.roaming
|
||||
|
||||
import com.rtbishop.look4sat.core.domain.predict.GeoPos
|
||||
import com.rtbishop.look4sat.core.domain.utility.positionToQth
|
||||
import com.rtbishop.look4sat.core.domain.utility.qthNeighbors
|
||||
import com.rtbishop.look4sat.core.domain.utility.qthToSquare
|
||||
|
||||
data class RoamingState(
|
||||
val latitude: Double = 0.0,
|
||||
val longitude: Double = 0.0,
|
||||
val qthLocator: String = "----",
|
||||
val gridSquares: List<String> = List(9) { "----" },
|
||||
/** Fractional position of the red marker inside the center cell, 0..1. */
|
||||
val markerX: Float = 0.5f,
|
||||
val markerY: Float = 0.5f,
|
||||
/** True when a fresh GPS fix is available (station position updated recently). */
|
||||
val gpsEnabled: Boolean = false
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
* Builds the state from a [GeoPos]. The 3x3 grid and the marker
|
||||
* position are derived from the 8-char locator exactly like the
|
||||
* QTH定位器 app: the marker is placed by the 3rd character pair
|
||||
* (e.g. the "ih" in "OL42ih45"), mapped to a 0..1 fraction.
|
||||
*/
|
||||
fun fromPosition(pos: GeoPos): RoamingState {
|
||||
val locator = positionToQth(pos.latitude, pos.longitude) ?: return RoamingState()
|
||||
val square = qthToSquare(locator)
|
||||
val squares = if (square != "----") qthNeighbors(square) else List(9) { "----" }
|
||||
// GPS is simply whatever the station position says — a real fix
|
||||
// (non-zero, with timestamp) counts as enabled.
|
||||
val hasFix = pos.timestamp > 0L && (pos.latitude != 0.0 || pos.longitude != 0.0)
|
||||
return RoamingState(
|
||||
latitude = pos.latitude,
|
||||
longitude = pos.longitude,
|
||||
qthLocator = locator,
|
||||
gridSquares = squares,
|
||||
markerX = markerFraction(locator, isLat = false),
|
||||
markerY = markerFraction(locator, isLat = true),
|
||||
gpsEnabled = hasFix
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fractional position of the fix inside the 4-char square, derived from
|
||||
* the 3rd character pair of an 8-char locator (e.g. the "ih" in
|
||||
* "OL42ih45"). Ported from the QTH定位器 app: it maps the 3rd-pair
|
||||
* letters (a..x) to a position inside the center cell instead of
|
||||
* recomputing from coordinates.
|
||||
* X: a = west edge .. x = east edge (left -> right). Y: a = south ..
|
||||
* x = north, inverted so the value grows downward on screen, matching
|
||||
* the reference app.
|
||||
*/
|
||||
private fun markerFraction(locator: String, isLat: Boolean): Float {
|
||||
if (locator.length < 6) return 0.5f
|
||||
val index = if (isLat) locator[5] else locator[4]
|
||||
val fraction = (index.lowercaseChar().code - 97).coerceIn(0, 23) / 24f
|
||||
return if (isLat) 1f - fraction else fraction
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 176 B |
Binary file not shown.
|
After Width: | Height: | Size: 172 B |
Binary file not shown.
|
After Width: | Height: | Size: 375 B |
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Look4Sat. Amateur radio satellite tracker and pass predictor.
|
||||
* Copyright (C) 2019-2026 Arty Bishop and contributors.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.rtbishop.look4sat.feature.roaming
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class RoamingStateTest {
|
||||
|
||||
// 用户实测坐标 (QTH定位器 2.0 成品截图): 22.314066 / 108.706575
|
||||
private fun stateOf(lat: Double, lon: Double) =
|
||||
roamingStateFromLocation(lat, lon, System.currentTimeMillis(), "21:")
|
||||
|
||||
@Test
|
||||
fun `user coordinates produce OL42ih45`() {
|
||||
val state = stateOf(22.314066, 108.706575)
|
||||
assertEquals("OL42ih45", state.loc)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `red marker from ih pair matches reference lookup`() {
|
||||
val state = stateOf(22.314066, 108.706575)
|
||||
// 成品查表: 经度第3对 'i' -> leftMargin 65dp, 纬度第3对 'h' -> topMargin 137dp
|
||||
assertEquals(65, state.markerLeft)
|
||||
assertEquals(137, state.markerTop)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `grid nine from OL42 matches reference`() {
|
||||
val state = stateOf(22.314066, 108.706575)
|
||||
val expected = listOf(
|
||||
"OL33", "OL43", "OL53",
|
||||
"OL32", "OL42", "OL52",
|
||||
"OL31", "OL41", "OL51"
|
||||
)
|
||||
assertEquals(expected, state.grids)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `latitude dms formatting matches reference`() {
|
||||
val state = stateOf(22.314066, 108.706575)
|
||||
assertEquals("纬度 22° 18' 50\" N", state.latLabel)
|
||||
assertEquals("22.314066° ", state.latValue)
|
||||
assertEquals("经度 108° 42' 23\" E", state.lonLabel)
|
||||
assertEquals("108.706575° ", state.lonValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `time is hour prefix plus fix minutes`() {
|
||||
// fixTime = 2026-08-03 21:12:13 UTC+8 -> minutes 12
|
||||
val fix = 1785820333000L // 2026-08-03 21:12:13 +0800
|
||||
val state = roamingStateFromLocation(22.314066, 108.706575, fix, "21:")
|
||||
assertEquals("21:12", state.time)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `west edge borrows lon letter`() {
|
||||
// 经度 0.5 纬度 22.314066: 数字对 02, 西边界 parseInt==0
|
||||
val state = stateOf(22.314066, 0.5)
|
||||
assertEquals(8, state.loc.length)
|
||||
// 左列经度 0->9, 经度字母 J-1=I: IL92
|
||||
assertEquals("IL92", state.grids[3])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `north edge carries lat letter`() {
|
||||
// 纬度 29.5 经度 108.706575: 数字对 49? -> 纬度数字 9 -> 北边界
|
||||
val state = stateOf(29.5, 108.706575)
|
||||
assertEquals(8, state.loc.length)
|
||||
// 上排纬度 +1 -> 0, 纬度字母 +1 (M)
|
||||
assertEquals("OM40", state.grids[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `east edge carries lon letter`() {
|
||||
// 经度 119.5 纬度 22.314066: 经度数字 9 -> 东边界
|
||||
val state = stateOf(22.314066, 119.5)
|
||||
assertEquals(8, state.loc.length)
|
||||
// 右上 经度 9->0, 经度字母 O+1=P, 纬度 2+1=3: PL03
|
||||
assertEquals("PL03", state.grids[2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `south edge borrows lat letter`() {
|
||||
// 纬度 0.5 经度 108.706575: 纬度数字 0 -> 南边界
|
||||
val state = stateOf(0.5, 108.706575)
|
||||
assertEquals(8, state.loc.length)
|
||||
// 下中 纬度 0->9, 纬度字母 J-1=I: OI49
|
||||
assertEquals("OI49", state.grids[7])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `boundary values wrap like reference`() {
|
||||
// (90, 180) -> RR99xx99 (成品区间查表行为)
|
||||
val state = stateOf(90.0, 180.0)
|
||||
assertEquals("RR99xx99", state.loc)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `gps state after fix matches showLocation`() {
|
||||
val state = stateOf(22.314066, 108.706575)
|
||||
assertEquals(true, state.gpsOn)
|
||||
assertEquals(false, state.gpsOff)
|
||||
assertEquals(false, state.showSettings)
|
||||
assertEquals(false, state.showProgress)
|
||||
assertEquals(false, state.showNotice)
|
||||
assertEquals(true, state.showDate)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user