mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Merge pull request #6 from Iu-yang1/feature/tianditu-autotle
feat(map): add TianDiTu switching and AutoTLE sources
This commit is contained in:
13 files changed
+371
-25
No files matched your search
@@ -25,6 +25,7 @@ import androidx.core.location.LocationListenerCompat
|
|||||||
import androidx.core.location.LocationManagerCompat
|
import androidx.core.location.LocationManagerCompat
|
||||||
import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings
|
import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings
|
||||||
import com.rtbishop.look4sat.core.domain.model.DatabaseState
|
import com.rtbishop.look4sat.core.domain.model.DatabaseState
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.MapSource
|
||||||
import com.rtbishop.look4sat.core.domain.model.OtherSettings
|
import com.rtbishop.look4sat.core.domain.model.OtherSettings
|
||||||
import com.rtbishop.look4sat.core.domain.model.PassesSettings
|
import com.rtbishop.look4sat.core.domain.model.PassesSettings
|
||||||
import com.rtbishop.look4sat.core.domain.model.RCSettings
|
import com.rtbishop.look4sat.core.domain.model.RCSettings
|
||||||
@@ -98,6 +99,8 @@ class SettingsRepo(
|
|||||||
private val keySstvMode = "sstvMode"
|
private val keySstvMode = "sstvMode"
|
||||||
private val keyLowElevation = "lowElevation"
|
private val keyLowElevation = "lowElevation"
|
||||||
private val keyHighElevation = "highElevation"
|
private val keyHighElevation = "highElevation"
|
||||||
|
private val keyMapSource = "mapSource"
|
||||||
|
private val keyTiandituKey = "tiandituKey"
|
||||||
private val keyUseCustomTle = "useCustomTle"
|
private val keyUseCustomTle = "useCustomTle"
|
||||||
private val keyUseCustomTransceivers = "useCustomTransceivers"
|
private val keyUseCustomTransceivers = "useCustomTransceivers"
|
||||||
private val keyTleUrl = "tleUrl"
|
private val keyTleUrl = "tleUrl"
|
||||||
@@ -107,6 +110,7 @@ class SettingsRepo(
|
|||||||
private val keySatelliteEnabled = "satelliteEnabled"
|
private val keySatelliteEnabled = "satelliteEnabled"
|
||||||
private val keyTransceiversEnabled = "transceiversEnabled"
|
private val keyTransceiversEnabled = "transceiversEnabled"
|
||||||
private val keySatnogsTleSourceMigration = "satnogsTleSourceMigration"
|
private val keySatnogsTleSourceMigration = "satnogsTleSourceMigration"
|
||||||
|
private val keyAutoTleSourceMigration = "autoTleSourceMigration"
|
||||||
private val separatorComma = ","
|
private val separatorComma = ","
|
||||||
private val separatorUrl = "\n"
|
private val separatorUrl = "\n"
|
||||||
private val legacyCelestrakSatnogsUrl =
|
private val legacyCelestrakSatnogsUrl =
|
||||||
@@ -597,7 +601,12 @@ class SettingsRepo(
|
|||||||
|
|
||||||
override fun updateOtherSettings(transform: (OtherSettings) -> OtherSettings) {
|
override fun updateOtherSettings(transform: (OtherSettings) -> OtherSettings) {
|
||||||
_otherSettings.update { current ->
|
_otherSettings.update { current ->
|
||||||
val new = transform(current)
|
val new = transform(current).let {
|
||||||
|
it.copy(
|
||||||
|
mapSource = MapSource.normalize(it.mapSource),
|
||||||
|
tiandituKey = it.tiandituKey.trim()
|
||||||
|
)
|
||||||
|
}
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
putBoolean(keyStateOfAutoUpdate, new.stateOfAutoUpdate)
|
putBoolean(keyStateOfAutoUpdate, new.stateOfAutoUpdate)
|
||||||
putBoolean(keyStateOfAutoLotwSync, new.stateOfAutoLotwSync)
|
putBoolean(keyStateOfAutoLotwSync, new.stateOfAutoLotwSync)
|
||||||
@@ -614,6 +623,8 @@ class SettingsRepo(
|
|||||||
putString(keySstvMode, new.sstvMode)
|
putString(keySstvMode, new.sstvMode)
|
||||||
putLong(keyLowElevation, new.lowElevation.toRawBits())
|
putLong(keyLowElevation, new.lowElevation.toRawBits())
|
||||||
putLong(keyHighElevation, new.highElevation.toRawBits())
|
putLong(keyHighElevation, new.highElevation.toRawBits())
|
||||||
|
putString(keyMapSource, new.mapSource)
|
||||||
|
putString(keyTiandituKey, new.tiandituKey)
|
||||||
}
|
}
|
||||||
new
|
new
|
||||||
}
|
}
|
||||||
@@ -634,7 +645,9 @@ class SettingsRepo(
|
|||||||
shouldSeeWhatsNew = preferences.getBoolean(keyShouldSeeWhatsNew, true),
|
shouldSeeWhatsNew = preferences.getBoolean(keyShouldSeeWhatsNew, true),
|
||||||
sstvMode = preferences.getString(keySstvMode, null) ?: "Auto",
|
sstvMode = preferences.getString(keySstvMode, null) ?: "Auto",
|
||||||
lowElevation = Double.fromBits(preferences.getLong(keyLowElevation, 15.0.toRawBits())),
|
lowElevation = Double.fromBits(preferences.getLong(keyLowElevation, 15.0.toRawBits())),
|
||||||
highElevation = Double.fromBits(preferences.getLong(keyHighElevation, 45.0.toRawBits()))
|
highElevation = Double.fromBits(preferences.getLong(keyHighElevation, 45.0.toRawBits())),
|
||||||
|
mapSource = MapSource.normalize(preferences.getString(keyMapSource, null).orEmpty()),
|
||||||
|
tiandituKey = preferences.getString(keyTiandituKey, null).orEmpty().trim()
|
||||||
)
|
)
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -656,6 +669,7 @@ class SettingsRepo(
|
|||||||
putString(keySatelliteEnabled, normalized.satelliteEnabled.joinToString(separatorComma))
|
putString(keySatelliteEnabled, normalized.satelliteEnabled.joinToString(separatorComma))
|
||||||
putString(keyTransceiversEnabled, normalized.transceiversEnabled.joinToString(separatorComma))
|
putString(keyTransceiversEnabled, normalized.transceiversEnabled.joinToString(separatorComma))
|
||||||
putBoolean(keySatnogsTleSourceMigration, true)
|
putBoolean(keySatnogsTleSourceMigration, true)
|
||||||
|
putBoolean(keyAutoTleSourceMigration, true)
|
||||||
}
|
}
|
||||||
_dataSourcesSettings.value = normalized
|
_dataSourcesSettings.value = normalized
|
||||||
}
|
}
|
||||||
@@ -666,7 +680,7 @@ class SettingsRepo(
|
|||||||
defaultUrls = Sources.satelliteDataUrls.values.filter { it.isNotBlank() },
|
defaultUrls = Sources.satelliteDataUrls.values.filter { it.isNotBlank() },
|
||||||
legacyEnabledKey = keyUseCustomTle,
|
legacyEnabledKey = keyUseCustomTle,
|
||||||
legacyUrlKey = keyTleUrl
|
legacyUrlKey = keyTleUrl
|
||||||
).migrateSatnogsTleSource(),
|
).migrateSatnogsTleSource().migrateAutoTleSources(),
|
||||||
transceiversUrls = getDataSourceUrls(
|
transceiversUrls = getDataSourceUrls(
|
||||||
key = keyTransceiversUrls,
|
key = keyTransceiversUrls,
|
||||||
defaultUrls = Sources.transceiversDataUrls.values.filter { it.isNotBlank() },
|
defaultUrls = Sources.transceiversDataUrls.values.filter { it.isNotBlank() },
|
||||||
@@ -727,6 +741,28 @@ class SettingsRepo(
|
|||||||
return migrated
|
return migrated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<String>.migrateAutoTleSources(): List<String> {
|
||||||
|
if (preferences.getBoolean(keyAutoTleSourceMigration, false)) return this
|
||||||
|
val autoTleUrls = listOfNotNull(
|
||||||
|
Sources.satelliteDataUrls["BI4PYM AutoTLE (GitHub)"],
|
||||||
|
Sources.satelliteDataUrls["BI4PYM AutoTLE (Mirror)"]
|
||||||
|
).filter { it.isNotBlank() }
|
||||||
|
val missingUrls = autoTleUrls.filterNot { containsSourceUrl(it) }
|
||||||
|
if (missingUrls.isEmpty()) {
|
||||||
|
preferences.edit { putBoolean(keyAutoTleSourceMigration, true) }
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
val migrated = this + missingUrls
|
||||||
|
val enabled = alignFlags(migrated, readEnabledFlags(keySatelliteEnabled))
|
||||||
|
preferences.edit {
|
||||||
|
putString(keySatelliteUrls, migrated.joinToString(separatorUrl))
|
||||||
|
putString(keySatelliteEnabled, enabled.joinToString(separatorComma))
|
||||||
|
putBoolean(keyAutoTleSourceMigration, true)
|
||||||
|
}
|
||||||
|
return migrated
|
||||||
|
}
|
||||||
|
|
||||||
private fun List<String>.containsSourceUrl(url: String): Boolean = any { isSameSourceUrl(it, url) }
|
private fun List<String>.containsSourceUrl(url: String): Boolean = any { isSameSourceUrl(it, url) }
|
||||||
|
|
||||||
private fun isSameSourceUrl(first: String, second: String): Boolean =
|
private fun isSameSourceUrl(first: String, second: String): Boolean =
|
||||||
|
|||||||
@@ -75,9 +75,23 @@ data class OtherSettings(
|
|||||||
* ephemeris auto-update; only effective after at least one manual sync). */
|
* ephemeris auto-update; only effective after at least one manual sync). */
|
||||||
val stateOfAutoLotwSync: Boolean = true,
|
val stateOfAutoLotwSync: Boolean = true,
|
||||||
/** 指南针手动航向偏置(度, -180..180): 雷达方位 = 传感器方位 + 磁偏角 + 该偏置. */
|
/** 指南针手动航向偏置(度, -180..180): 雷达方位 = 传感器方位 + 磁偏角 + 该偏置. */
|
||||||
val compassOffsetDegrees: Float = 0f
|
val compassOffsetDegrees: Float = 0f,
|
||||||
|
val mapSource: String = MapSource.OSM,
|
||||||
|
val tiandituKey: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
object MapSource {
|
||||||
|
const val OSM = "osm"
|
||||||
|
const val TIANDITU_VECTOR = "tianditu_vector"
|
||||||
|
const val TIANDITU_IMAGE = "tianditu_image"
|
||||||
|
private const val LEGACY_TIANDITU = "tianditu"
|
||||||
|
|
||||||
|
fun normalize(source: String): String = when (source) {
|
||||||
|
OSM, TIANDITU_VECTOR, TIANDITU_IMAGE -> source
|
||||||
|
LEGACY_TIANDITU -> TIANDITU_VECTOR
|
||||||
|
else -> OSM
|
||||||
|
}
|
||||||
|
}
|
||||||
data class DataSourcesSettings(
|
data class DataSourcesSettings(
|
||||||
val satelliteUrls: List<String>,
|
val satelliteUrls: List<String>,
|
||||||
val transceiversUrls: List<String>,
|
val transceiversUrls: List<String>,
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ object Sources {
|
|||||||
val satelliteDataUrls = mapOf(
|
val satelliteDataUrls = mapOf(
|
||||||
"LAPAN-A2" to "https://www.kaggle.com/api/v1/datasets/download/muazamnugroho/lapan-a2-satellite-two-line-element-tle-dataset/LAPAN-A2_TLE_latest.txt",
|
"LAPAN-A2" to "https://www.kaggle.com/api/v1/datasets/download/muazamnugroho/lapan-a2-satellite-two-line-element-tle-dataset/LAPAN-A2_TLE_latest.txt",
|
||||||
"R4UAB" to "https://r4uab.ru/satonline.txt",
|
"R4UAB" to "https://r4uab.ru/satonline.txt",
|
||||||
|
"BI4PYM AutoTLE (GitHub)" to "https://raw.githubusercontent.com/BI4PYM/AutoTLE/refs/heads/master/AutoTLE.txt",
|
||||||
|
"BI4PYM AutoTLE (Mirror)" to "https://autotle.bi4pym.cn/AutoTLE.txt",
|
||||||
"All" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
|
"All" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
|
||||||
"SatNOGS" to "https://db.satnogs.org/api/tle/?format=3le",
|
"SatNOGS" to "https://db.satnogs.org/api/tle/?format=3le",
|
||||||
"Amateur" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=csv",
|
"Amateur" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=csv",
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
<!-- Map screen -->
|
<!-- Map screen -->
|
||||||
<string name="map_prev">Önceki</string>
|
<string name="map_prev">Önceki</string>
|
||||||
<string name="map_next">Sonraki</string>
|
<string name="map_next">Sonraki</string>
|
||||||
<string name="map_copyright" translatable="false">© OpenStreetMap contributors</string>
|
<string name="map_copyright_osm" translatable="false">© OpenStreetMap contributors</string>
|
||||||
<string name="map_azimuth">Azimut: %.1f°</string>
|
<string name="map_azimuth">Azimut: %.1f°</string>
|
||||||
<string name="map_elevation">Yükseklik açısı: %.1f°</string>
|
<string name="map_elevation">Yükseklik açısı: %.1f°</string>
|
||||||
<string name="map_altitude">İrtifa: %.0f km</string>
|
<string name="map_altitude">İrtifa: %.0f km</string>
|
||||||
@@ -209,7 +209,8 @@
|
|||||||
\n• Alexandru Csete (Gpredict)
|
\n• Alexandru Csete (Gpredict)
|
||||||
\n• Dr T.S. Kelso (Celestrak)
|
\n• Dr T.S. Kelso (Celestrak)
|
||||||
\n• Libre Space Foundation (SatNOGS)
|
\n• Libre Space Foundation (SatNOGS)
|
||||||
\n• BG7NTA BG5JSU (CW decoding feature)</string>
|
\n• BG7NTA BG5JSU (CW decoding feature)
|
||||||
|
\n• BH2VSQ BG5JSU (TianDiTu rendering support)</string>
|
||||||
<string name="prefs_outro_license">Bu uygulama herhangi bir garanti sunmaz</string>
|
<string name="prefs_outro_license">Bu uygulama herhangi bir garanti sunmaz</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -247,6 +247,15 @@
|
|||||||
<string name="rc_enable_switch">启用 CAT 控制</string>
|
<string name="rc_enable_switch">启用 CAT 控制</string>
|
||||||
|
|
||||||
<string name="prefs_other_title">其他设置</string>
|
<string name="prefs_other_title">其他设置</string>
|
||||||
|
<string name="prefs_map_title">地图设置</string>
|
||||||
|
<string name="prefs_map_configure">配置地图</string>
|
||||||
|
<string name="prefs_map_source_title">地图来源</string>
|
||||||
|
<string name="prefs_map_source_osm">离线 OSM 地图</string>
|
||||||
|
<string name="prefs_map_source_tianditu_vector">天地图(矢量)</string>
|
||||||
|
<string name="prefs_map_source_tianditu_image">天地图(影像)</string>
|
||||||
|
<string name="prefs_map_tianditu_key">天地图访问密钥</string>
|
||||||
|
<string name="prefs_map_tianditu_key_help">使用天地图前,需先在天地图开发者控制台申请服务端应用 Key。</string>
|
||||||
|
<string name="prefs_map_tianditu_key_required">选择天地图需要填写访问密钥;保存密钥前将继续使用离线 OSM 地图。</string>
|
||||||
<string name="prefs_other_switch_utc">以 UTC 时间显示</string>
|
<string name="prefs_other_switch_utc">以 UTC 时间显示</string>
|
||||||
<string name="prefs_other_switch_update">启用卫星数据自动更新</string>
|
<string name="prefs_other_switch_update">启用卫星数据自动更新</string>
|
||||||
<string name="prefs_other_switch_lotw_sync">自动同步 LoTW 已确认网格</string>
|
<string name="prefs_other_switch_lotw_sync">自动同步 LoTW 已确认网格</string>
|
||||||
@@ -282,7 +291,8 @@
|
|||||||
\n• Dr T.S. Kelso (Celestrak)
|
\n• Dr T.S. Kelso (Celestrak)
|
||||||
\n• Libre Space Foundation (SatNOGS)
|
\n• Libre Space Foundation (SatNOGS)
|
||||||
\n• xdsopl 和 Robot36 贡献者!
|
\n• xdsopl 和 Robot36 贡献者!
|
||||||
\n• BG7NTA BG5JSU(CW解码功能)</string>
|
\n• BG7NTA BG5JSU(CW解码功能)
|
||||||
|
\n• BH2VSQ BG5JSU(天地图渲染支持)</string>
|
||||||
<string name="prefs_outro_license">该应用程序不提供任何保修.</string>
|
<string name="prefs_outro_license">该应用程序不提供任何保修.</string>
|
||||||
|
|
||||||
<!-- 匹配页 Mutual match page -->
|
<!-- 匹配页 Mutual match page -->
|
||||||
|
|||||||
@@ -128,7 +128,8 @@
|
|||||||
<!-- Map screen -->
|
<!-- Map screen -->
|
||||||
<string name="map_prev">Prev</string>
|
<string name="map_prev">Prev</string>
|
||||||
<string name="map_next">Next</string>
|
<string name="map_next">Next</string>
|
||||||
<string name="map_copyright" translatable="false">© OpenStreetMap contributors</string>
|
<string name="map_copyright_osm" translatable="false">© OpenStreetMap contributors</string>
|
||||||
|
<string name="map_copyright_tianditu" translatable="false">© Tianditu</string>
|
||||||
<string name="map_grid_mode">Grid mode</string>
|
<string name="map_grid_mode">Grid mode</string>
|
||||||
<string name="map_satellite_mode">Satellite mode</string>
|
<string name="map_satellite_mode">Satellite mode</string>
|
||||||
<string name="map_first_call_mode">First call</string>
|
<string name="map_first_call_mode">First call</string>
|
||||||
@@ -276,6 +277,15 @@
|
|||||||
<string name="prefs_net_perm_error">Check your network permission</string>
|
<string name="prefs_net_perm_error">Check your network permission</string>
|
||||||
|
|
||||||
<string name="prefs_other_title">Other settings</string>
|
<string name="prefs_other_title">Other settings</string>
|
||||||
|
<string name="prefs_map_title">Map settings</string>
|
||||||
|
<string name="prefs_map_configure">Configure map</string>
|
||||||
|
<string name="prefs_map_source_title">Map source</string>
|
||||||
|
<string name="prefs_map_source_osm">Offline OSM</string>
|
||||||
|
<string name="prefs_map_source_tianditu_vector">TianDiTu vector</string>
|
||||||
|
<string name="prefs_map_source_tianditu_image">TianDiTu imagery</string>
|
||||||
|
<string name="prefs_map_tianditu_key">TianDiTu access key</string>
|
||||||
|
<string name="prefs_map_tianditu_key_help">Request a server-side application key from the TianDiTu developer console before using TianDiTu maps.</string>
|
||||||
|
<string name="prefs_map_tianditu_key_required">A TianDiTu key is required. Offline OSM remains active until it is entered.</string>
|
||||||
<string name="prefs_other_switch_utc">Show pass time in UTC</string>
|
<string name="prefs_other_switch_utc">Show pass time in UTC</string>
|
||||||
<string name="prefs_other_switch_update">Enable automatic data update</string>
|
<string name="prefs_other_switch_update">Enable automatic data update</string>
|
||||||
<string name="prefs_other_switch_lotw_sync">Auto-sync LoTW confirmed grids</string>
|
<string name="prefs_other_switch_lotw_sync">Auto-sync LoTW confirmed grids</string>
|
||||||
@@ -312,6 +322,7 @@
|
|||||||
\n* Libre Space Foundation (SatNOGS)
|
\n* Libre Space Foundation (SatNOGS)
|
||||||
\n* xdsopl and Robot36 contributors!
|
\n* xdsopl and Robot36 contributors!
|
||||||
\n* BG7NTA BG5JSU (CW decoding feature)
|
\n* BG7NTA BG5JSU (CW decoding feature)
|
||||||
|
\n* BH2VSQ BG5JSU (TianDiTu rendering support)
|
||||||
</string>
|
</string>
|
||||||
<string name="prefs_outro_license">The app comes with no warranty</string>
|
<string name="prefs_outro_license">The app comes with no warranty</string>
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.rtbishop.look4sat.core.domain.model.AwardCalculator
|
import com.rtbishop.look4sat.core.domain.model.AwardCalculator
|
||||||
import com.rtbishop.look4sat.core.domain.model.AwardProgress
|
import com.rtbishop.look4sat.core.domain.model.AwardProgress
|
||||||
import com.rtbishop.look4sat.core.domain.model.AwardType
|
import com.rtbishop.look4sat.core.domain.model.AwardType
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.MapSource
|
||||||
import com.rtbishop.look4sat.core.domain.predict.GeoPos
|
import com.rtbishop.look4sat.core.domain.predict.GeoPos
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
||||||
@@ -96,31 +97,40 @@ import com.rtbishop.look4sat.core.presentation.TimerRow
|
|||||||
import com.rtbishop.look4sat.core.presentation.TopBar
|
import com.rtbishop.look4sat.core.presentation.TopBar
|
||||||
import com.rtbishop.look4sat.core.presentation.isVerticalLayout
|
import com.rtbishop.look4sat.core.presentation.isVerticalLayout
|
||||||
import com.rtbishop.look4sat.core.presentation.layoutPadding
|
import com.rtbishop.look4sat.core.presentation.layoutPadding
|
||||||
|
import org.osmdroid.tileprovider.MapTileProviderBasic
|
||||||
|
import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase
|
||||||
import org.osmdroid.tileprovider.tilesource.XYTileSource
|
import org.osmdroid.tileprovider.tilesource.XYTileSource
|
||||||
import org.osmdroid.events.DelayedMapListener
|
import org.osmdroid.events.DelayedMapListener
|
||||||
import org.osmdroid.events.MapListener
|
import org.osmdroid.events.MapListener
|
||||||
import org.osmdroid.events.ScrollEvent
|
import org.osmdroid.events.ScrollEvent
|
||||||
import org.osmdroid.events.ZoomEvent
|
import org.osmdroid.events.ZoomEvent
|
||||||
import org.osmdroid.util.GeoPoint
|
import org.osmdroid.util.GeoPoint
|
||||||
|
import org.osmdroid.util.MapTileIndex
|
||||||
import org.osmdroid.views.CustomZoomButtonsController
|
import org.osmdroid.views.CustomZoomButtonsController
|
||||||
import org.osmdroid.views.MapView
|
import org.osmdroid.views.MapView
|
||||||
import org.osmdroid.views.overlay.FolderOverlay
|
import org.osmdroid.views.overlay.FolderOverlay
|
||||||
import org.osmdroid.views.overlay.Marker
|
import org.osmdroid.views.overlay.Marker
|
||||||
import org.osmdroid.views.overlay.Polyline
|
import org.osmdroid.views.overlay.Polyline
|
||||||
|
import org.osmdroid.views.overlay.TilesOverlay
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
// Overlay indices
|
// Overlay indices
|
||||||
private const val OVERLAY_GRID = 0
|
private const val OVERLAY_TDT_LABELS = 0
|
||||||
private const val OVERLAY_STATION = 1
|
private const val OVERLAY_GRID = 1
|
||||||
private const val OVERLAY_TRACK = 2
|
private const val OVERLAY_STATION = 2
|
||||||
private const val OVERLAY_FOOTPRINT = 3
|
private const val OVERLAY_TRACK = 3
|
||||||
private const val OVERLAY_POSITIONS = 4
|
private const val OVERLAY_FOOTPRINT = 4
|
||||||
private const val OVERLAY_TERMINATOR = 5
|
private const val OVERLAY_POSITIONS = 5
|
||||||
private const val OVERLAY_SUN = 6
|
private const val OVERLAY_TERMINATOR = 6
|
||||||
private const val OVERLAY_MOON = 7
|
private const val OVERLAY_SUN = 7
|
||||||
private const val OVERLAY_COUNT = 8
|
private const val OVERLAY_MOON = 8
|
||||||
|
private const val OVERLAY_COUNT = 9
|
||||||
|
|
||||||
private val minLat = MapView.getTileSystem().minLatitude
|
private val minLat = MapView.getTileSystem().minLatitude
|
||||||
private val maxLat = MapView.getTileSystem().maxLatitude
|
private val maxLat = MapView.getTileSystem().maxLatitude
|
||||||
|
private val offlineTileSource = XYTileSource("tiles", 0, 6, 256, ".webp", emptyArray<String>())
|
||||||
|
private const val OFFLINE_MAX_ZOOM = 7.0
|
||||||
|
private const val TIANDITU_MAX_ZOOM = 18.0
|
||||||
private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
strokeWidth = 3f
|
strokeWidth = 3f
|
||||||
style = Paint.Style.STROKE
|
style = Paint.Style.STROKE
|
||||||
@@ -209,6 +219,8 @@ private fun MapScreen(
|
|||||||
val rotateMod = Modifier.rotate(180f)
|
val rotateMod = Modifier.rotate(180f)
|
||||||
val timeString = uiState.mapData?.aosTime ?: "00:00:00"
|
val timeString = uiState.mapData?.aosTime ?: "00:00:00"
|
||||||
val isTimeAos = uiState.mapData?.isTimeAos ?: true
|
val isTimeAos = uiState.mapData?.isTimeAos ?: true
|
||||||
|
val usesTianditu = tiandituLayers(uiState.mapSource) != null && uiState.tiandituKey.isNotBlank()
|
||||||
|
val copyrightResId = if (usesTianditu) R.string.map_copyright_tianditu else R.string.map_copyright_osm
|
||||||
|
|
||||||
// Tapped worked grid -> centered QSO dialog. Local UI state: the map is the
|
// Tapped worked grid -> centered QSO dialog. Local UI state: the map is the
|
||||||
// only consumer and it resets when leaving the page.
|
// only consumer and it resets when leaving the page.
|
||||||
@@ -300,6 +312,9 @@ private fun MapScreen(
|
|||||||
val firstPos = uiState.track?.firstOrNull()?.firstOrNull() ?: return@LaunchedEffect
|
val firstPos = uiState.track?.firstOrNull()?.firstOrNull() ?: return@LaunchedEffect
|
||||||
mapView.controller.animateTo(GeoPoint(firstPos.latitude, firstPos.longitude))
|
mapView.controller.animateTo(GeoPoint(firstPos.latitude, firstPos.longitude))
|
||||||
}
|
}
|
||||||
|
LaunchedEffect(uiState.mapSource, uiState.tiandituKey) {
|
||||||
|
configureTileSources(mapView, uiState.mapSource, uiState.tiandituKey)
|
||||||
|
}
|
||||||
Column(modifier = Modifier.layoutPadding(), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
Column(modifier = Modifier.layoutPadding(), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
val isVertical = isVerticalLayout()
|
val isVertical = isVerticalLayout()
|
||||||
if (isGridMode) {
|
if (isGridMode) {
|
||||||
@@ -382,7 +397,8 @@ private fun MapScreen(
|
|||||||
}
|
}
|
||||||
uiState.mapData?.let { mapData ->
|
uiState.mapData?.let { mapData ->
|
||||||
if (!uiState.isGridMode) {
|
if (!uiState.isGridMode) {
|
||||||
if (isVertical) MapDataCard(mapData) else MapDataCards(mapData)
|
if (isVertical) MapDataCard(mapData, copyrightResId)
|
||||||
|
else MapDataCards(mapData, copyrightResId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Top-left: operated-grid selector for VUCC counting (grid mode only).
|
// Top-left: operated-grid selector for VUCC counting (grid mode only).
|
||||||
@@ -761,11 +777,11 @@ private fun formatTime(epochMs: Long, isUtc: Boolean): String {
|
|||||||
|
|
||||||
// region Map data composables
|
// region Map data composables
|
||||||
@Composable
|
@Composable
|
||||||
private fun MapDataCard(data: MapData) {
|
private fun MapDataCard(data: MapData, copyrightResId: Int) {
|
||||||
val textColor = MaterialTheme.colorScheme.primary
|
val textColor = MaterialTheme.colorScheme.primary
|
||||||
val cardColors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLowest)
|
val cardColors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLowest)
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text(text = stringResource(R.string.map_copyright), fontSize = 14.sp)
|
Text(text = stringResource(copyrightResId), fontSize = 14.sp)
|
||||||
Card(colors = cardColors) {
|
Card(colors = cardColors) {
|
||||||
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
|
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
|
||||||
MapDataRow(
|
MapDataRow(
|
||||||
@@ -803,7 +819,7 @@ private fun MapDataRow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MapDataCards(data: MapData) {
|
private fun MapDataCards(data: MapData, copyrightResId: Int) {
|
||||||
val cardColors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLowest)
|
val cardColors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLowest)
|
||||||
val paddingMod = Modifier
|
val paddingMod = Modifier
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||||
@@ -830,7 +846,7 @@ private fun MapDataCards(data: MapData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.map_copyright),
|
text = stringResource(copyrightResId),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
modifier = Modifier.align(Alignment.BottomCenter)
|
modifier = Modifier.align(Alignment.BottomCenter)
|
||||||
)
|
)
|
||||||
@@ -1045,6 +1061,100 @@ private fun FirstCallToggle(
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// region Tile sources
|
||||||
|
private var configuredTileMapView: MapView? = null
|
||||||
|
private var configuredTileSource: String? = null
|
||||||
|
|
||||||
|
private fun configureTileSources(mapView: MapView, mapSource: String, tiandituKey: String) {
|
||||||
|
val key = tiandituKey.trim()
|
||||||
|
val layers = tiandituLayers(mapSource)
|
||||||
|
val sourceKey = if (layers == null || key.isBlank()) MapSource.OSM else "${layers.base}:$key"
|
||||||
|
if (configuredTileMapView === mapView && configuredTileSource == sourceKey) return
|
||||||
|
|
||||||
|
configuredTileMapView = mapView
|
||||||
|
configuredTileSource = sourceKey
|
||||||
|
(mapView.overlays.getOrNull(OVERLAY_TDT_LABELS) as? TilesOverlay)?.onDetach(mapView)
|
||||||
|
|
||||||
|
if (layers == null || key.isBlank()) {
|
||||||
|
mapView.setUseDataConnection(false)
|
||||||
|
mapView.setTileSource(offlineTileSource)
|
||||||
|
mapView.maxZoomLevel = OFFLINE_MAX_ZOOM
|
||||||
|
mapView.overlayManager.tilesOverlay.applyTileOverlayDefaults(createColorFilter())
|
||||||
|
mapView.overlays[OVERLAY_TDT_LABELS] = FolderOverlay()
|
||||||
|
requestTileRefresh(mapView)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mapView.setUseDataConnection(true)
|
||||||
|
mapView.setTileSource(TiandituTileSource(layer = layers.base, key = key))
|
||||||
|
mapView.maxZoomLevel = TIANDITU_MAX_ZOOM
|
||||||
|
mapView.overlayManager.tilesOverlay.applyTileOverlayDefaults(null)
|
||||||
|
mapView.overlays[OVERLAY_TDT_LABELS] = TilesOverlay(
|
||||||
|
MapTileProviderBasic(mapView.context, TiandituTileSource(layer = layers.label, key = key)),
|
||||||
|
mapView.context
|
||||||
|
).apply {
|
||||||
|
applyTileOverlayDefaults(null)
|
||||||
|
setUseDataConnection(true)
|
||||||
|
}
|
||||||
|
requestTileRefresh(mapView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class TiandituLayers(val base: String, val label: String)
|
||||||
|
|
||||||
|
private fun tiandituLayers(mapSource: String): TiandituLayers? = when (MapSource.normalize(mapSource)) {
|
||||||
|
MapSource.TIANDITU_VECTOR -> TiandituLayers(base = "vec", label = "cva")
|
||||||
|
MapSource.TIANDITU_IMAGE -> TiandituLayers(base = "img", label = "cia")
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TilesOverlay.applyTileOverlayDefaults(colorFilter: ColorMatrixColorFilter?) {
|
||||||
|
setColorFilter(colorFilter)
|
||||||
|
setLoadingBackgroundColor(Color.TRANSPARENT)
|
||||||
|
setLoadingLineColor(Color.TRANSPARENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestTileRefresh(mapView: MapView) {
|
||||||
|
mapView.post {
|
||||||
|
mapView.requestLayout()
|
||||||
|
mapView.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildTiandituWmtsUrl(
|
||||||
|
baseUrl: String,
|
||||||
|
layer: String,
|
||||||
|
zoom: Int,
|
||||||
|
row: Int,
|
||||||
|
col: Int,
|
||||||
|
encodedKey: String
|
||||||
|
): String = "$baseUrl?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=$layer" +
|
||||||
|
"&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX=$zoom&TILEROW=$row&TILECOL=$col&tk=$encodedKey"
|
||||||
|
|
||||||
|
private class TiandituTileSource(
|
||||||
|
private val layer: String,
|
||||||
|
key: String
|
||||||
|
) : OnlineTileSourceBase(
|
||||||
|
"tianditu-wmts-https-$layer",
|
||||||
|
1,
|
||||||
|
TIANDITU_MAX_ZOOM.toInt(),
|
||||||
|
256,
|
||||||
|
".png",
|
||||||
|
Array(8) { index -> "https://t$index.tianditu.gov.cn/${layer}_w/wmts" },
|
||||||
|
"Tianditu"
|
||||||
|
) {
|
||||||
|
private val encodedKey = URLEncoder.encode(key, Charsets.UTF_8.name())
|
||||||
|
|
||||||
|
override fun getTileURLString(pMapTileIndex: Long): String = buildTiandituWmtsUrl(
|
||||||
|
baseUrl = getBaseUrl(),
|
||||||
|
layer = layer,
|
||||||
|
zoom = MapTileIndex.getZoom(pMapTileIndex),
|
||||||
|
row = MapTileIndex.getY(pMapTileIndex),
|
||||||
|
col = MapTileIndex.getX(pMapTileIndex),
|
||||||
|
encodedKey = encodedKey
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region Map overlay helpers
|
// region Map overlay helpers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1463,7 +1573,6 @@ private fun rememberMapViewWithLifecycle(
|
|||||||
mapFilterViewModel: MapFilterViewModel,
|
mapFilterViewModel: MapFilterViewModel,
|
||||||
restoreViewport: Boolean
|
restoreViewport: Boolean
|
||||||
): MapView {
|
): MapView {
|
||||||
val tileSource = XYTileSource("tiles", 0, 6, 256, ".webp", emptyArray<String>())
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isVertical = isVerticalLayout()
|
val isVertical = isVerticalLayout()
|
||||||
val mapView = remember {
|
val mapView = remember {
|
||||||
@@ -1475,9 +1584,9 @@ private fun rememberMapViewWithLifecycle(
|
|||||||
// draws a full-height artifact line whenever its X range crosses
|
// draws a full-height artifact line whenever its X range crosses
|
||||||
// the screen at low zoom. The map must never repeat vertically.
|
// the screen at low zoom. The map must never repeat vertically.
|
||||||
setVerticalMapRepetitionEnabled(false)
|
setVerticalMapRepetitionEnabled(false)
|
||||||
setTileSource(tileSource)
|
setTileSource(offlineTileSource)
|
||||||
minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels, isVertical)
|
minZoomLevel = getMinZoom(resources.displayMetrics.heightPixels, isVertical)
|
||||||
maxZoomLevel = 7.0
|
maxZoomLevel = OFFLINE_MAX_ZOOM
|
||||||
// Restore the viewport the user left on the previous visit, but only
|
// Restore the viewport the user left on the previous visit, but only
|
||||||
// in grid mode: satellite mode intentionally keeps its original
|
// in grid mode: satellite mode intentionally keeps its original
|
||||||
// behavior (default center, then follows the selected satellite).
|
// behavior (default center, then follows the selected satellite).
|
||||||
@@ -1522,6 +1631,8 @@ private fun clearMapCaches() {
|
|||||||
lastPositions = null
|
lastPositions = null
|
||||||
lastAction = null
|
lastAction = null
|
||||||
lastMapView = null
|
lastMapView = null
|
||||||
|
configuredTileMapView = null
|
||||||
|
configuredTileSource = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -21,11 +21,14 @@ import com.rtbishop.look4sat.core.domain.predict.GeoPos
|
|||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalObject
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPass
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalPass
|
||||||
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
import com.rtbishop.look4sat.core.domain.predict.OrbitalPos
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.MapSource
|
||||||
|
|
||||||
data class MapState(
|
data class MapState(
|
||||||
val mapData: MapData? = null,
|
val mapData: MapData? = null,
|
||||||
val isLightUi: Boolean = false,
|
val isLightUi: Boolean = false,
|
||||||
val isUtc: Boolean = false,
|
val isUtc: Boolean = false,
|
||||||
|
val mapSource: String = MapSource.OSM,
|
||||||
|
val tiandituKey: String = "",
|
||||||
val isGridMode: Boolean = false,
|
val isGridMode: Boolean = false,
|
||||||
/** Grid mode: label worked (green) cells with the first callsign worked in
|
/** Grid mode: label worked (green) cells with the first callsign worked in
|
||||||
* that grid instead of the grid code; non-worked cells get no label. */
|
* that grid instead of the grid code; non-worked cells get no label. */
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ class MapViewModel(
|
|||||||
MapState(
|
MapState(
|
||||||
isLightUi = settingsRepo.otherSettings.value.stateOfLightTheme,
|
isLightUi = settingsRepo.otherSettings.value.stateOfLightTheme,
|
||||||
isUtc = settingsRepo.otherSettings.value.stateOfUtc,
|
isUtc = settingsRepo.otherSettings.value.stateOfUtc,
|
||||||
|
mapSource = settingsRepo.otherSettings.value.mapSource,
|
||||||
|
tiandituKey = settingsRepo.otherSettings.value.tiandituKey,
|
||||||
orbitalPass = defaultPass
|
orbitalPass = defaultPass
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -82,6 +84,8 @@ class MapViewModel(
|
|||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isUtc = settings.stateOfUtc,
|
isUtc = settings.stateOfUtc,
|
||||||
|
mapSource = settings.mapSource,
|
||||||
|
tiandituKey = settings.tiandituKey,
|
||||||
isGridMode = settings.stateOfMapGrid,
|
isGridMode = settings.stateOfMapGrid,
|
||||||
showFirstCallLabels = settings.stateOfMapFirstCall
|
showFirstCallLabels = settings.stateOfMapFirstCall
|
||||||
)
|
)
|
||||||
|
|||||||
+119
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* 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.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.MapSource
|
||||||
|
import com.rtbishop.look4sat.core.domain.model.OtherSettings
|
||||||
|
import com.rtbishop.look4sat.core.presentation.R
|
||||||
|
import com.rtbishop.look4sat.core.presentation.SharedDialog
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MapSettingsDialog(
|
||||||
|
settings: OtherSettings,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onSave: (mapSource: String, tiandituKey: String) -> Unit
|
||||||
|
) {
|
||||||
|
var selectedSource by rememberSaveable {
|
||||||
|
mutableStateOf(MapSource.normalize(settings.mapSource))
|
||||||
|
}
|
||||||
|
var tiandituKey by rememberSaveable { mutableStateOf(settings.tiandituKey) }
|
||||||
|
|
||||||
|
SharedDialog(
|
||||||
|
title = stringResource(R.string.prefs_map_title),
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
onCancel = onDismiss,
|
||||||
|
onAccept = { onSave(selectedSource, tiandituKey) }
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier.padding(horizontal = padding)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.prefs_map_source_title),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
FlowRow(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
|
MapSourceChip(
|
||||||
|
selected = selectedSource == MapSource.OSM,
|
||||||
|
label = stringResource(R.string.prefs_map_source_osm),
|
||||||
|
onClick = { selectedSource = MapSource.OSM }
|
||||||
|
)
|
||||||
|
MapSourceChip(
|
||||||
|
selected = selectedSource == MapSource.TIANDITU_VECTOR,
|
||||||
|
label = stringResource(R.string.prefs_map_source_tianditu_vector),
|
||||||
|
onClick = { selectedSource = MapSource.TIANDITU_VECTOR }
|
||||||
|
)
|
||||||
|
MapSourceChip(
|
||||||
|
selected = selectedSource == MapSource.TIANDITU_IMAGE,
|
||||||
|
label = stringResource(R.string.prefs_map_source_tianditu_image),
|
||||||
|
onClick = { selectedSource = MapSource.TIANDITU_IMAGE }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.prefs_map_tianditu_key_help),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = tiandituKey,
|
||||||
|
onValueChange = { tiandituKey = it },
|
||||||
|
label = { Text(stringResource(R.string.prefs_map_tianditu_key)) },
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Password,
|
||||||
|
imeAction = ImeAction.Done
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
if (selectedSource != MapSource.OSM && tiandituKey.isBlank()) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.prefs_map_tianditu_key_required),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MapSourceChip(selected: Boolean, label: String, onClick: () -> Unit) {
|
||||||
|
FilterChip(selected = selected, onClick = onClick, label = { Text(label) })
|
||||||
|
}
|
||||||
+31
@@ -99,6 +99,7 @@ fun SettingsDestination() {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> Unit) {
|
private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> Unit) {
|
||||||
var showUpdateChecker by rememberSaveable { mutableStateOf(false) }
|
var showUpdateChecker by rememberSaveable { mutableStateOf(false) }
|
||||||
|
var showMapSettings by rememberSaveable { mutableStateOf(false) }
|
||||||
if (showUpdateChecker) {
|
if (showUpdateChecker) {
|
||||||
UpdateCheckerScreen(
|
UpdateCheckerScreen(
|
||||||
currentVersion = uiState.appVersionName,
|
currentVersion = uiState.appVersionName,
|
||||||
@@ -129,6 +130,17 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showMapSettings) {
|
||||||
|
MapSettingsDialog(
|
||||||
|
settings = uiState.otherSettings,
|
||||||
|
onDismiss = { showMapSettings = false },
|
||||||
|
onSave = { mapSource, tiandituKey ->
|
||||||
|
onAction(SettingsAction.UpdateMapSettings(mapSource, tiandituKey))
|
||||||
|
showMapSettings = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
if (dialogs.position) {
|
if (dialogs.position) {
|
||||||
PositionDialog(
|
PositionDialog(
|
||||||
@@ -368,6 +380,7 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
onAction = onAction
|
onAction = onAction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
item { MapSettingsCard(onClick = { showMapSettings = true }) }
|
||||||
item { CardCredits() }
|
item { CardCredits() }
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
CardButton(
|
CardButton(
|
||||||
@@ -558,6 +571,24 @@ private fun OtherCardPreview() = MainTheme {
|
|||||||
OtherCard(settings = values, onCompassCalibration = {}, onAction = {})
|
OtherCard(settings = values, onCompassCalibration = {}, onAction = {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MapSettingsCard(onClick: () -> Unit) {
|
||||||
|
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp)) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.prefs_map_title),
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
CardButton(
|
||||||
|
onClick = onClick,
|
||||||
|
text = stringResource(R.string.prefs_map_configure),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun OtherCard(
|
private fun OtherCard(
|
||||||
settings: OtherSettings,
|
settings: OtherSettings,
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ sealed interface SettingsAction {
|
|||||||
data class SetCompassOffset(val degrees: Float) : SettingsAction
|
data class SetCompassOffset(val degrees: Float) : SettingsAction
|
||||||
data class ToggleLightTheme(val value: Boolean) : SettingsAction
|
data class ToggleLightTheme(val value: Boolean) : SettingsAction
|
||||||
data class ToggleNightMode(val value: Boolean) : SettingsAction
|
data class ToggleNightMode(val value: Boolean) : SettingsAction
|
||||||
|
data class UpdateMapSettings(val mapSource: String, val tiandituKey: String) : SettingsAction
|
||||||
|
|
||||||
// Remote control
|
// Remote control
|
||||||
data class UpdateRC(val settings: RCSettings) : SettingsAction
|
data class UpdateRC(val settings: RCSettings) : SettingsAction
|
||||||
|
|||||||
+3
@@ -186,6 +186,9 @@ class SettingsViewModel(
|
|||||||
}
|
}
|
||||||
is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings { it.copy(stateOfLightTheme = action.value) }
|
is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings { it.copy(stateOfLightTheme = action.value) }
|
||||||
is SettingsAction.ToggleNightMode -> settingsRepo.updateOtherSettings { it.copy(stateOfNightMode = action.value) }
|
is SettingsAction.ToggleNightMode -> settingsRepo.updateOtherSettings { it.copy(stateOfNightMode = action.value) }
|
||||||
|
is SettingsAction.UpdateMapSettings -> settingsRepo.updateOtherSettings {
|
||||||
|
it.copy(mapSource = action.mapSource, tiandituKey = action.tiandituKey)
|
||||||
|
}
|
||||||
// Remote control & data sources
|
// Remote control & data sources
|
||||||
is SettingsAction.UpdateRC -> settingsRepo.updateRCSettings(action.settings)
|
is SettingsAction.UpdateRC -> settingsRepo.updateRCSettings(action.settings)
|
||||||
is SettingsAction.UpdateRadioControl -> settingsRepo.updateRadioControlSettings(action.settings)
|
is SettingsAction.UpdateRadioControl -> settingsRepo.updateRadioControlSettings(action.settings)
|
||||||
|
|||||||
Reference in new issue
Block a user