mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
feat(settings): 致谢与声明卡、版本卡打开检查更新、浅色主题、品牌与 GPL 头
This commit is contained in:
1 parent
5acf71b2a6
commit
45cd72faf7
23 files changed
+410
-100
No files matched your search
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
**BA7OPF 定制版** — 基于 [rt-bishop/Look4Sat](https://github.com/rt-bishop/Look4Sat) 的业余无线电卫星追踪器,增加了线性卫星频率计算器等功能。
|
**BA7OPF 定制版** — 基于 [rt-bishop/Look4Sat](https://github.com/rt-bishop/Look4Sat) 的业余无线电卫星追踪器,增加了线性卫星频率计算器等功能。
|
||||||
|
|
||||||
|
非官方修改版(fork),独立维护;首次发布 2026-09,持续修改中。
|
||||||
|
|
||||||
## 本仓库特色功能
|
## 本仓库特色功能
|
||||||
|
|
||||||
- **网格地图模式(Grid Mode)** — 地图页内置 VUCC 网格覆盖图:已通联网格绿色填充、漫游网格蓝色 45° 斜纹(疏朗半透明样式)、当前所在方格淡黄加粗框;点击已通联网格弹出该网格内通联明细弹窗;进入网格模式默认 VUCC 视图
|
- **网格地图模式(Grid Mode)** — 地图页内置 VUCC 网格覆盖图:已通联网格绿色填充、漫游网格蓝色 45° 斜纹(疏朗半透明样式)、当前所在方格淡黄加粗框;点击已通联网格弹出该网格内通联明细弹窗;进入网格模式默认 VUCC 视图
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED"
|
android:name="android.telephony.PROPERTY_SATELLITE_DATA_OPTIMIZED"
|
||||||
android:value="com.rtbishop.look4sat" />
|
android:value="cn.ba7opf.look4sat" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ import android.view.View
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
||||||
import com.rtbishop.look4sat.core.presentation.MainTheme
|
import com.rtbishop.look4sat.core.presentation.MainTheme
|
||||||
@@ -47,9 +49,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
installSplashScreen()
|
installSplashScreen()
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
val settingsRepo = (applicationContext as IContainerProvider).getMainContainer().settingsRepo
|
||||||
observeNightFilterState()
|
observeNightFilterState()
|
||||||
setContent {
|
setContent {
|
||||||
MainTheme(isDarkTheme = true) { NavRoot() }
|
val otherSettings by settingsRepo.otherSettings.collectAsStateWithLifecycle()
|
||||||
|
// Light theme is opt-in: the app stays dark until the user turns it on.
|
||||||
|
MainTheme(isDarkTheme = !otherSettings.stateOfLightTheme) { NavRoot() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat-BA7OPF. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2026 BA7OPF.
|
||||||
|
* Based on Look4Sat by 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.core.data.repository
|
package com.rtbishop.look4sat.core.data.repository
|
||||||
|
|
||||||
import com.rtbishop.look4sat.core.domain.model.LatestRelease
|
import com.rtbishop.look4sat.core.domain.model.LatestRelease
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class AddToCalendar(private val context: Context) : IAddToCalendar {
|
|||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
setData(CalendarContract.Events.CONTENT_URI)
|
setData(CalendarContract.Events.CONTENT_URI)
|
||||||
putExtra(CalendarContract.Events.TITLE, name)
|
putExtra(CalendarContract.Events.TITLE, name)
|
||||||
putExtra(CalendarContract.Events.DESCRIPTION, "Look4Sat Pass")
|
putExtra(CalendarContract.Events.DESCRIPTION, "Look4Sat-BA7OPF Pass")
|
||||||
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, aosTime)
|
putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, aosTime)
|
||||||
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, losTime)
|
putExtra(CalendarContract.EXTRA_EVENT_END_TIME, losTime)
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat-BA7OPF. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2026 BA7OPF.
|
||||||
|
* Based on Look4Sat by 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.core.presentation
|
package com.rtbishop.look4sat.core.presentation
|
||||||
|
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
|
|||||||
+3
-2
@@ -61,8 +61,9 @@ fun MainTheme(isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable
|
|||||||
SideEffect {
|
SideEffect {
|
||||||
val window = (view.context as ComponentActivity).window
|
val window = (view.context as ComponentActivity).window
|
||||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||||
insetsController.isAppearanceLightStatusBars = false
|
// Light theme needs dark status/nav bar icons; the dark theme needs light ones.
|
||||||
insetsController.isAppearanceLightNavigationBars = false
|
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
||||||
|
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
window.isNavigationBarContrastEnforced = false
|
window.isNavigationBarContrastEnforced = false
|
||||||
}
|
}
|
||||||
|
|||||||
+18
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat-BA7OPF. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2026 BA7OPF.
|
||||||
|
* Based on Look4Sat by 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.core.presentation
|
package com.rtbishop.look4sat.core.presentation
|
||||||
|
|
||||||
import androidx.compose.animation.core.animateDpAsState
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
|||||||
@@ -83,7 +83,6 @@
|
|||||||
<string name="map_visible">Visible</string>
|
<string name="map_visible">Visible</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
<string name="prefs_donate_title">Soporte</string>
|
|
||||||
<string name="prefs_privacy_title">Política de privacidad</string>
|
<string name="prefs_privacy_title">Política de privacidad</string>
|
||||||
|
|
||||||
<string name="prefs_updated_title">Actualizado: %s</string>
|
<string name="prefs_updated_title">Actualizado: %s</string>
|
||||||
|
|||||||
@@ -82,7 +82,6 @@
|
|||||||
<string name="map_visible">Видимый</string>
|
<string name="map_visible">Видимый</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
<string name="prefs_donate_title">Поддержать</string>
|
|
||||||
<string name="prefs_privacy_title">Политика конфиденциальности</string>
|
<string name="prefs_privacy_title">Политика конфиденциальности</string>
|
||||||
|
|
||||||
<string name="prefs_updated_title">Обновлено: %s</string>
|
<string name="prefs_updated_title">Обновлено: %s</string>
|
||||||
|
|||||||
@@ -83,7 +83,6 @@
|
|||||||
<string name="map_visible">දෘශ්ය</string>
|
<string name="map_visible">දෘශ්ය</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
<string name="prefs_donate_title">දායකත්ව</string>
|
|
||||||
<string name="prefs_privacy_title">රහස්යතා ප්රතිපත්තිය</string>
|
<string name="prefs_privacy_title">රහස්යතා ප්රතිපත්තිය</string>
|
||||||
|
|
||||||
<string name="prefs_updated_title">යාවත්කාලීන කරන ලදී: %s</string>
|
<string name="prefs_updated_title">යාවත්කාලීන කරන ලදී: %s</string>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Commmon -->
|
<!-- Commmon -->
|
||||||
<string name="app_name" translatable="false">Look4Sat</string>
|
<string name="app_name" translatable="false">Look4Sat-BA7OPF</string>
|
||||||
<string name="btn_accept">Kabul et</string>
|
<string name="btn_accept">Kabul et</string>
|
||||||
<string name="btn_cancel">İptal</string>
|
<string name="btn_cancel">İptal</string>
|
||||||
<string name="empty_list_message">Gösterilecek veri bulunamadı</string>
|
<string name="empty_list_message">Gösterilecek veri bulunamadı</string>
|
||||||
@@ -104,18 +104,16 @@
|
|||||||
<string name="map_visible">Görünür</string>
|
<string name="map_visible">Görünür</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
<string name="prefs_app_title" translatable="false">Look4Sat v%s</string>
|
<string name="prefs_app_title" translatable="false">Look4Sat-BA7OPF 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_app_url" translatable="false">https://github.com/atsunatsu/Look4Sat/releases</string>
|
||||||
<string name="prefs_donate_title">Bağış yap</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_title" translatable="false">GitHub</string>
|
||||||
<string name="prefs_github_url" translatable="false">https://github.com/rt-bishop/Look4Sat/</string>
|
<string name="prefs_github_url" translatable="false">https://github.com/atsunatsu/Look4Sat</string>
|
||||||
<string name="prefs_license_title" translatable="false">GPLv3</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_license_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.html</string>
|
||||||
<string name="prefs_privacy_title">Gizlilik Politikası</string>
|
<string name="prefs_privacy_title">Gizlilik Politikası</string>
|
||||||
<string name="prefs_privacy_url" translatable="false">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_upstream_title" translatable="false">Upstream Project</string>
|
||||||
|
<string name="prefs_upstream_url" translatable="false">https://github.com/rt-bishop/Look4Sat</string>
|
||||||
|
|
||||||
<string name="prefs_updated_title">Güncellendi: %s</string>
|
<string name="prefs_updated_title">Güncellendi: %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>
|
||||||
|
|||||||
@@ -83,7 +83,6 @@
|
|||||||
<string name="map_visible">Видимий</string>
|
<string name="map_visible">Видимий</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
<string name="prefs_donate_title">Підтримати</string>
|
|
||||||
<string name="prefs_privacy_title">Політика конфіденційності</string>
|
<string name="prefs_privacy_title">Політика конфіденційності</string>
|
||||||
|
|
||||||
<string name="prefs_updated_title">Оновлено: %s</string>
|
<string name="prefs_updated_title">Оновлено: %s</string>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
<string name="pass_welcome_message">请务必在设置中通过GPS、经纬度或QTH定位您的位置\n建议至少每周更新一次数据库,以确保预测结果的准确性</string>
|
<string name="pass_welcome_message">请务必在设置中通过GPS、经纬度或QTH定位您的位置\n建议至少每周更新一次数据库,以确保预测结果的准确性</string>
|
||||||
|
|
||||||
<!-- AMSAT Status screen -->
|
<!-- AMSAT Status screen -->
|
||||||
<string name="pass_whatsnew_message">4.4.6 以来的全部更新(4.4.7 系列):\n\n• 网格模式地图:LoTW 已确认网格绿色填充、漫游网格蓝色条纹、当前网格加粗框;右上角网格/卫星模式开关。\n\n• 首通呼号(VUCC 视图):绿格显示该格最早通联的呼号,非绿格空白。\n\n• worked 网格可按操作网格筛选(含“全部”);点击网格弹出 QSO 明细;每格 QSO 计数。\n\n• 各操作网格独立 VUCC 计数;极区网格标签;反经线修复;漫游条纹密度一致。\n\n• WAPC/WAJA 地图标签随系统语言。\n\n• 网格模式视口与筛选跨页保持。\n\n• LoTW 同步:账号直连、全量/增量模式、实时进度、可取消、启动自动同步(独立开关)、设置页显示上次同步时间。\n\n• 历史过境:过境筛选可回看过去数小时(历史过境时间)。\n\n• 指南针校准:8 字动作精度指示 + 手动航向偏置。\n\n• AMSAT 状态页:连续相同状态报告计数。\n\n• 过境/雷达:UTC 切换重算过境;日出日落与仰角颜色修复;卫星模糊搜索;相互过境页滚动保持、跟随 UTC、雷达快捷入口。\n\n• CW 解码器支持 32/64 位设备;Android 17 (API 37) 适配。</string>
|
<string name="pass_whatsnew_message">4.4.6 以来的全部更新(4.4.7 / 4.4.8 系列):\n\n• 合并上游 4.4.8:IC-705 读频修复;地图昼夜分界线极性修复(秋分后昼夜阴影反相);白色主题(地图、状态栏、弹窗与自绘网格/奖状图层一并适配);卫星名模糊搜索。\n\n• LoTW 日志本:上传预检按真实 BAND/SATELLITE 给出原因;QSO 全详情编辑;卫星名按 TQSL 官方名录校验(非官方名不予保存);FM 卫星固定记录语音转发器频率(SO-50 下行 436.795);LoTW 确认记录会并入已上传的那条 QSO,不再另起一行(卫星名经 TQSL 别名表比对,报告里的 BAND 按上行解析)。\n\n• 弹窗统一为底部弹出栏同款配色与圆角。\n\n• 设置页链接指向本项目:版本号 → 发布页,GitHub → 本仓库。\n\n• 网格模式地图:LoTW 已确认网格绿色填充、漫游网格蓝色条纹、当前网格加粗框;右上角网格/卫星模式开关。\n\n• 首通呼号(VUCC 视图):绿格显示该格最早通联的呼号,非绿格空白。\n\n• worked 网格可按操作网格筛选(含“全部”);点击网格弹出 QSO 明细;每格 QSO 计数。\n\n• 各操作网格独立 VUCC 计数;极区网格标签;反经线修复;漫游条纹密度一致。\n\n• WAPC/WAJA 地图标签随系统语言。\n\n• 网格模式视口与筛选跨页保持。\n\n• LoTW 同步:账号直连、全量/增量模式、实时进度、可取消、启动自动同步(独立开关)、设置页显示上次同步时间。\n\n• 历史过境:过境筛选可回看过去数小时(历史过境时间)。\n\n• 指南针校准:8 字动作精度指示 + 手动航向偏置。\n\n• AMSAT 状态页:连续相同状态报告计数。\n\n• 过境/雷达:UTC 切换重算过境;日出日落与仰角颜色修复;卫星模糊搜索;相互过境页滚动保持、跟随 UTC、雷达快捷入口。\n\n• CW 解码器支持 32/64 位设备;Android 17 (API 37) 适配。\n\n• v4.4.7-ba7opf.21:日志本卫星名统一按 TQSL 官方名存取(SAUDISAT 1C → SO-50),存量记录自动改名;日志页与通联帖按卫星身份匹配。\n• v4.4.7-ba7opf.22:同步时自动修正旧确认行的频段方向;上传重复判定改为时间+卫星+呼号三者相同。\n• v4.4.7-ba7opf.23:应用更名为 Look4Sat-BA7OPF;致谢页声明为非官方修改版,关于页加入上游项目链接,移除打赏/F-Droid 卡片。</string>
|
||||||
|
|
||||||
<string name="nav_amsat">AMSAT</string>
|
<string name="nav_amsat">AMSAT</string>
|
||||||
<string name="amsat_title">AMSAT 卫星状态</string>
|
<string name="amsat_title">AMSAT 卫星状态</string>
|
||||||
@@ -141,7 +141,6 @@
|
|||||||
<string name="map_visible">日照中</string>
|
<string name="map_visible">日照中</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
<string name="prefs_donate_title">支持</string>
|
|
||||||
<string name="prefs_privacy_title">隐私政策</string>
|
<string name="prefs_privacy_title">隐私政策</string>
|
||||||
|
|
||||||
<string name="prefs_updated_title">更新:%s</string>
|
<string name="prefs_updated_title">更新:%s</string>
|
||||||
@@ -304,6 +303,7 @@
|
|||||||
<string name="prefs_compass_offset">手动航向偏置(°)</string>
|
<string name="prefs_compass_offset">手动航向偏置(°)</string>
|
||||||
<string name="prefs_compass_offset_support">范围 −180° 至 +180°;正值为顺时针偏移。</string>
|
<string name="prefs_compass_offset_support">范围 −180° 至 +180°;正值为顺时针偏移。</string>
|
||||||
<string name="prefs_compass_offset_reset">偏置归零</string>
|
<string name="prefs_compass_offset_reset">偏置归零</string>
|
||||||
|
<string name="prefs_other_switch_light_theme">浅色主题</string>
|
||||||
<string name="prefs_other_switch_night_mode">启用红色夜间模式</string>
|
<string name="prefs_other_switch_night_mode">启用红色夜间模式</string>
|
||||||
<string name="prefs_other_switch_map_grid">地图显示 Maidenhead 网格</string>
|
<string name="prefs_other_switch_map_grid">地图显示 Maidenhead 网格</string>
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@
|
|||||||
<string name="prefs_highlight_mid">中 %1$d-%2$d°</string>
|
<string name="prefs_highlight_mid">中 %1$d-%2$d°</string>
|
||||||
<string name="prefs_highlight_high">高 > %1$d°</string>
|
<string name="prefs_highlight_high">高 > %1$d°</string>
|
||||||
|
|
||||||
<string name="prefs_outro_title">我要感谢:</string>
|
<string name="prefs_outro_title">致谢与声明</string>
|
||||||
<string name="prefs_outro_thanks" translatable="false">
|
<string name="prefs_outro_thanks" translatable="false">
|
||||||
• Look4Sat 所有用户及贡献者!
|
• Look4Sat 所有用户及贡献者!
|
||||||
\n• David A. B. Johnson (predict4java)
|
\n• David A. B. Johnson (predict4java)
|
||||||
@@ -325,7 +325,9 @@
|
|||||||
\n• BH2VSQ BG5JSU(天地图渲染支持)
|
\n• BH2VSQ BG5JSU(天地图渲染支持)
|
||||||
\n• BG5JSU(LoTW上传支持)
|
\n• BG5JSU(LoTW上传支持)
|
||||||
</string>
|
</string>
|
||||||
<string name="prefs_outro_license">该应用程序不提供任何保修.</string>
|
<string name="prefs_outro_notice">Look4Sat-BA7OPF 是基于 Look4Sat(GPL-3.0)的非官方修改版,由 BA7OPF 独立维护,与原项目无隶属关系,亦未获其背书。
|
||||||
|
上游项目:https://github.com/rt-bishop/Look4Sat</string>
|
||||||
|
<string name="prefs_outro_license">基于 GNU GPL v3 许可 · 本程序不提供任何保修</string>
|
||||||
|
|
||||||
<!-- 匹配页 Mutual match page -->
|
<!-- 匹配页 Mutual match page -->
|
||||||
<string name="mutual_title">过境匹配</string>
|
<string name="mutual_title">过境匹配</string>
|
||||||
@@ -374,4 +376,32 @@
|
|||||||
<string name="update_check_download_error">下载失败,请重试。</string>
|
<string name="update_check_download_error">下载失败,请重试。</string>
|
||||||
<string name="update_check_no_apk">该版本未附带 APK 安装包。</string>
|
<string name="update_check_no_apk">该版本未附带 APK 安装包。</string>
|
||||||
|
|
||||||
|
<!-- QSO edit dialog -->
|
||||||
|
<string name="qso_edit_title">编辑通联记录</string>
|
||||||
|
<string name="qso_edit_uploaded_notice">该通联已上传或已确认。保存将保留原记录不变,并新建一条记录。</string>
|
||||||
|
<string name="qso_edit_callsign">呼号</string>
|
||||||
|
<string name="qso_edit_date">日期 (UTC)</string>
|
||||||
|
<string name="qso_edit_time">时间 (UTC)</string>
|
||||||
|
<string name="qso_edit_tx">上行频率 (MHz)</string>
|
||||||
|
<string name="qso_edit_rx">下行频率 (MHz)</string>
|
||||||
|
<string name="qso_edit_band">频段:%1$s</string>
|
||||||
|
<string name="qso_edit_mode">模式</string>
|
||||||
|
<string name="qso_edit_satellite">卫星</string>
|
||||||
|
<string name="qso_edit_hint_satellite">LoTW 只接受它的官方卫星名</string>
|
||||||
|
<string name="qso_edit_sent">发送报告</string>
|
||||||
|
<string name="qso_edit_received">接收报告</string>
|
||||||
|
<string name="qso_edit_grid">对方网格</string>
|
||||||
|
<string name="qso_edit_comment">备注</string>
|
||||||
|
<string name="qso_edit_save">保存</string>
|
||||||
|
<string name="qso_edit_cancel">取消</string>
|
||||||
|
<string name="qso_edit_err_callsign">呼号不能为空</string>
|
||||||
|
<string name="qso_edit_err_datetime">日期或时间格式不对 —— 应为 yyyy-MM-dd 与 HH:mm:ss(UTC)</string>
|
||||||
|
<string name="qso_edit_err_frequency">频率格式不对 —— 请输入 MHz,例如 145.850</string>
|
||||||
|
<string name="qso_edit_err_satellite">不是 LoTW 官方卫星名 —— 请从列表中选一个</string>
|
||||||
|
<string name="qso_edit_satellite_pick">选择</string>
|
||||||
|
<string name="qso_edit_satellite_pick_title">LoTW 官方卫星名</string>
|
||||||
|
<string name="qso_edit_satellite_search">搜索卫星名或数据源别名</string>
|
||||||
|
<string name="qso_edit_satellite_official">保存并签名时使用 %1$s</string>
|
||||||
|
<string name="qso_edit_warn_no_freq">没有上行频率:LoTW 要求 BAND,补上频率前这条无法上传</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Commmon -->
|
<!-- Commmon -->
|
||||||
<string name="app_name" translatable="false">Look4Sat</string>
|
<string name="app_name" translatable="false">Look4Sat-BA7OPF</string>
|
||||||
<string name="btn_accept">Accept</string>
|
<string name="btn_accept">Accept</string>
|
||||||
<string name="btn_cancel">Cancel</string>
|
<string name="btn_cancel">Cancel</string>
|
||||||
<string name="empty_list_message">No data to show</string>
|
<string name="empty_list_message">No data to show</string>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
Make sure to set your position via GPS, Lat/Lon or QTH in Settings.
|
Make sure to set your position via GPS, Lat/Lon or QTH in Settings.
|
||||||
\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">All changes since 4.4.6 (the 4.4.7 series):\n\n• Grid-mode map: LoTW confirmed grids in green, roamed grids with blue stripes, current grid highlighted; mode switch pill at the map\'s top-right.\n\n• First-call labels (VUCC view): show the first callsign worked in each green grid instead of the grid code; non-worked cells stay empty.\n\n• Worked grids filtered by operated grid (with an All option); tap a grid to see its QSO detail dialog; per-grid QSO counts.\n\n• Per-operated-grid VUCC counting; polar grid labels; antimeridian fixes; consistent roamed-stripe density.\n\n• WAPC/WAJA map labels follow the system language.\n\n• Grid-mode viewport and award filter survive page switches.\n\n• LoTW sync: direct login, full/incremental modes, live progress, cancel button, auto-sync on app start with its own toggle, last-sync time shown.\n\n• Pass history: filter can look back over past passes (Time before).\n\n• Compass calibration: figure-eight accuracy meter plus manual heading offset.\n\n• AMSAT status page: consecutive same-status report counts.\n\n• Passes/radar: UTC toggle recalculates passes; sunrise/sunset and elevation color fixes; satellite fuzzy search; mutual page keeps scroll, follows UTC, radar shortcut.\n\n• CW decoder for 32/64-bit devices; Android 17 (API 37) support.</string>
|
<string name="pass_whatsnew_message">All changes since 4.4.6 (the 4.4.7 / 4.4.8 series):\n\n• Merged from upstream 4.4.8: IC-705 frequency readout fix; day/night terminator polarity fix (shading was inverted after the September equinox); white/light theme (map, status bar, dialogs and the custom grid/award overlays included); fuzzy satellite search.\n\n• LoTW logbook: upload pre-check reports the real BAND/SATELLITE reasons; full QSO detail editing; satellite names validated against the TQSL list (unofficial names are not saved); FM satellites always log the voice repeater frequency (SO-50 downlink 436.795); a LoTW confirmation now folds into the uploaded QSO instead of appearing as a second row (satellite names are matched through the TQSL aliases, and the report\'s BAND is read as the uplink).\n\n• Dialogs share the bottom-sheet colours and corner radius.\n\n• Settings links point to this project: version number -> releases page, GitHub -> repository.\n\n• Grid-mode map: LoTW confirmed grids in green, roamed grids with blue stripes, current grid highlighted; mode switch pill at the map\'s top-right.\n\n• First-call labels (VUCC view): show the first callsign worked in each green grid instead of the grid code; non-worked cells stay empty.\n\n• Worked grids filtered by operated grid (with an All option); tap a grid to see its QSO detail dialog; per-grid QSO counts.\n\n• Per-operated-grid VUCC counting; polar grid labels; antimeridian fixes; consistent roamed-stripe density.\n\n• WAPC/WAJA map labels follow the system language.\n\n• Grid-mode viewport and award filter survive page switches.\n\n• LoTW sync: direct login, full/incremental modes, live progress, cancel button, auto-sync on app start with its own toggle, last-sync time shown.\n\n• Pass history: filter can look back over past passes (Time before).\n\n• Compass calibration: figure-eight accuracy meter plus manual heading offset.\n\n• AMSAT status page: consecutive same-status report counts.\n\n• Passes/radar: UTC toggle recalculates passes; sunrise/sunset and elevation color fixes; satellite fuzzy search; mutual page keeps scroll, follows UTC, radar shortcut.\n\n• CW decoder for 32/64-bit devices; Android 17 (API 37) support.\n\n• v4.4.7-ba7opf.21: the logbook stores and shows satellites under their TQSL official names (SAUDISAT 1C -> SO-50); existing records are renamed automatically; the log page and QSO post match by satellite identity.\n• v4.4.7-ba7opf.22: stale confirmation rows get their band direction fixed on sync; upload duplicate detection keys on time + satellite + callsign.\n• v4.4.7-ba7opf.23: renamed to Look4Sat-BA7OPF; the credits card states this is an unofficial build, the About screen links the upstream project, and the Donate / F-Droid cards were removed.</string>
|
||||||
|
|
||||||
<!-- AMSAT Status screen -->
|
<!-- AMSAT Status screen -->
|
||||||
<string name="nav_amsat">AMSAT</string>
|
<string name="nav_amsat">AMSAT</string>
|
||||||
@@ -162,18 +162,16 @@
|
|||||||
<string name="map_visible">Visible</string>
|
<string name="map_visible">Visible</string>
|
||||||
|
|
||||||
<!-- Settings screen -->
|
<!-- Settings screen -->
|
||||||
<string name="prefs_app_title" translatable="false">Look4Sat v%s</string>
|
<string name="prefs_app_title" translatable="false">Look4Sat-BA7OPF 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_app_url" translatable="false">https://github.com/atsunatsu/Look4Sat/releases</string>
|
||||||
<string name="prefs_donate_title">Donate</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_title" translatable="false">GitHub</string>
|
||||||
<string name="prefs_github_url" translatable="false">https://github.com/rt-bishop/Look4Sat/</string>
|
<string name="prefs_github_url" translatable="false">https://github.com/atsunatsu/Look4Sat</string>
|
||||||
<string name="prefs_license_title" translatable="false">GPLv3</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_license_url" translatable="false">https://www.gnu.org/licenses/gpl-3.0.html</string>
|
||||||
<string name="prefs_privacy_title">Privacy Policy</string>
|
<string name="prefs_privacy_title">Privacy Policy</string>
|
||||||
<string name="prefs_privacy_url" translatable="false">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_upstream_title" translatable="false">Upstream Project</string>
|
||||||
|
<string name="prefs_upstream_url" translatable="false">https://github.com/rt-bishop/Look4Sat</string>
|
||||||
|
|
||||||
<string name="prefs_updated_title">Updated: %s</string>
|
<string name="prefs_updated_title">Updated: %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>
|
||||||
@@ -334,6 +332,7 @@
|
|||||||
<string name="prefs_compass_offset">Manual heading offset (°)</string>
|
<string name="prefs_compass_offset">Manual heading offset (°)</string>
|
||||||
<string name="prefs_compass_offset_support">Range −180° to +180°. Positive values rotate clockwise.</string>
|
<string name="prefs_compass_offset_support">Range −180° to +180°. Positive values rotate clockwise.</string>
|
||||||
<string name="prefs_compass_offset_reset">Reset offset</string>
|
<string name="prefs_compass_offset_reset">Reset offset</string>
|
||||||
|
<string name="prefs_other_switch_light_theme">Light theme</string>
|
||||||
<string name="prefs_other_switch_night_mode">Enable red night mode filter</string>
|
<string name="prefs_other_switch_night_mode">Enable red night mode filter</string>
|
||||||
<string name="prefs_other_switch_map_grid">Show Maidenhead grid on map</string>
|
<string name="prefs_other_switch_map_grid">Show Maidenhead grid on map</string>
|
||||||
|
|
||||||
@@ -342,7 +341,7 @@
|
|||||||
<string name="prefs_highlight_mid">Mid %1$d–%2$d°</string>
|
<string name="prefs_highlight_mid">Mid %1$d–%2$d°</string>
|
||||||
<string name="prefs_highlight_high">High > %1$d°</string>
|
<string name="prefs_highlight_high">High > %1$d°</string>
|
||||||
|
|
||||||
<string name="prefs_outro_title">I would like to say thanks to</string>
|
<string name="prefs_outro_title">Credits & Notices</string>
|
||||||
<string name="prefs_outro_thanks" translatable="false">
|
<string name="prefs_outro_thanks" translatable="false">
|
||||||
* Look4Sat users and contributors!
|
* Look4Sat users and contributors!
|
||||||
\n* David A. B. Johnson (predict4java)
|
\n* David A. B. Johnson (predict4java)
|
||||||
@@ -355,7 +354,9 @@
|
|||||||
\n* BH2VSQ BG5JSU (TianDiTu rendering support)
|
\n* BH2VSQ BG5JSU (TianDiTu rendering support)
|
||||||
\n* BG5JSU (LoTW upload support)
|
\n* BG5JSU (LoTW upload support)
|
||||||
</string>
|
</string>
|
||||||
<string name="prefs_outro_license">The app comes with no warranty</string>
|
<string name="prefs_outro_notice">Look4Sat-BA7OPF is an unofficial modified build based on Look4Sat (GPL-3.0), independently maintained by BA7OPF. It is not affiliated with or endorsed by the original Look4Sat project.
|
||||||
|
Upstream: https://github.com/rt-bishop/Look4Sat</string>
|
||||||
|
<string name="prefs_outro_license">Licensed under the GNU General Public License v3 · The app comes with no warranty</string>
|
||||||
|
|
||||||
<!-- Mutual match page -->
|
<!-- Mutual match page -->
|
||||||
<string name="mutual_title">Pass Match</string>
|
<string name="mutual_title">Pass Match</string>
|
||||||
@@ -404,4 +405,32 @@
|
|||||||
<string name="update_check_download_error">Download failed. Try again.</string>
|
<string name="update_check_download_error">Download failed. Try again.</string>
|
||||||
<string name="update_check_no_apk">No APK attached to this release.</string>
|
<string name="update_check_no_apk">No APK attached to this release.</string>
|
||||||
|
|
||||||
|
<!-- QSO edit dialog -->
|
||||||
|
<string name="qso_edit_title">Edit QSO</string>
|
||||||
|
<string name="qso_edit_uploaded_notice">This QSO was already uploaded or confirmed. Saving keeps the original record unchanged and creates a new one.</string>
|
||||||
|
<string name="qso_edit_callsign">Callsign</string>
|
||||||
|
<string name="qso_edit_date">Date (UTC)</string>
|
||||||
|
<string name="qso_edit_time">Time (UTC)</string>
|
||||||
|
<string name="qso_edit_tx">Uplink (MHz)</string>
|
||||||
|
<string name="qso_edit_rx">Downlink (MHz)</string>
|
||||||
|
<string name="qso_edit_band">Band: %1$s</string>
|
||||||
|
<string name="qso_edit_mode">Mode</string>
|
||||||
|
<string name="qso_edit_satellite">Satellite</string>
|
||||||
|
<string name="qso_edit_hint_satellite">LoTW only accepts its official satellite name</string>
|
||||||
|
<string name="qso_edit_sent">Sent RST</string>
|
||||||
|
<string name="qso_edit_received">Received RST</string>
|
||||||
|
<string name="qso_edit_grid">Opposite grid</string>
|
||||||
|
<string name="qso_edit_comment">Comment</string>
|
||||||
|
<string name="qso_edit_save">Save</string>
|
||||||
|
<string name="qso_edit_cancel">Cancel</string>
|
||||||
|
<string name="qso_edit_err_callsign">Callsign is required</string>
|
||||||
|
<string name="qso_edit_err_datetime">Invalid date/time — use yyyy-MM-dd and HH:mm:ss (UTC)</string>
|
||||||
|
<string name="qso_edit_err_frequency">Invalid frequency — enter MHz, e.g. 145.850</string>
|
||||||
|
<string name="qso_edit_err_satellite">Not an ARRL satellite name — pick one from the list</string>
|
||||||
|
<string name="qso_edit_satellite_pick">Pick</string>
|
||||||
|
<string name="qso_edit_satellite_pick_title">ARRL satellite name</string>
|
||||||
|
<string name="qso_edit_satellite_search">Search name or catalogue alias</string>
|
||||||
|
<string name="qso_edit_satellite_official">Saved and signed as %1$s</string>
|
||||||
|
<string name="qso_edit_warn_no_freq">No uplink frequency: LoTW requires a band, so this QSO cannot be uploaded yet</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat-BA7OPF. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2026 BA7OPF.
|
||||||
|
* Based on Look4Sat by 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/>.
|
||||||
|
*/
|
||||||
/*
|
/*
|
||||||
* CwDecodeScreen.kt — Look4Sat 的 CW 解码页面(Compose 外壳)。
|
* CwDecodeScreen.kt — Look4Sat 的 CW 解码页面(Compose 外壳)。
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -139,6 +139,18 @@ class AwardBoundaryOverlay : Overlay() {
|
|||||||
color = Color.argb(90, 76, 217, 100)
|
color = Color.argb(90, 76, 217, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recolor the amber boundary/label paints for the active map theme (dark amber + white halo on
|
||||||
|
* the light map, light amber + black halo on the dark map). The worked fill reads on both.
|
||||||
|
*/
|
||||||
|
fun applyTheme(isLightUi: Boolean) {
|
||||||
|
val line = if (isLightUi) Color.argb(200, 113, 92, 12) else Color.argb(160, 255, 224, 130)
|
||||||
|
val label = if (isLightUi) Color.argb(235, 30, 25, 5) else Color.argb(220, 255, 224, 130)
|
||||||
|
linePaint.color = line
|
||||||
|
labelPaint.color = label
|
||||||
|
labelPaint.setShadowLayer(3f, 2f, 2f, if (isLightUi) Color.WHITE else Color.BLACK)
|
||||||
|
}
|
||||||
|
|
||||||
/** Boundary regions to render; assigning recomputes culling bounds. */
|
/** Boundary regions to render; assigning recomputes culling bounds. */
|
||||||
var regions: List<AwardRegion> = emptyList()
|
var regions: List<AwardRegion> = emptyList()
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|||||||
@@ -89,6 +89,24 @@ class MaidenheadGridOverlay : Overlay() {
|
|||||||
color = Color.argb(255, 76, 217, 100)
|
color = Color.argb(255, 76, 217, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recolor the amber line/label paints for the active map theme: dark amber with a white halo
|
||||||
|
* on the light map, light amber with a black halo on the dark map. The worked / marked / roam /
|
||||||
|
* selected colors stay as they are — they read on both backgrounds.
|
||||||
|
*/
|
||||||
|
fun applyTheme(isLightUi: Boolean) {
|
||||||
|
val line = if (isLightUi) Color.argb(200, 113, 92, 12) else Color.argb(160, 255, 224, 130)
|
||||||
|
val label = if (isLightUi) Color.argb(235, 30, 25, 5) else Color.argb(220, 255, 224, 130)
|
||||||
|
val halo = if (isLightUi) Color.WHITE else Color.BLACK
|
||||||
|
linePaint.color = line
|
||||||
|
// ownLine stays the same color as the normal grid lines (only thicker).
|
||||||
|
ownLinePaint.color = line
|
||||||
|
labelPaint.color = label
|
||||||
|
labelPaint.setShadowLayer(3f, 2f, 2f, halo)
|
||||||
|
firstCallPaint.color = label
|
||||||
|
firstCallPaint.setShadowLayer(3f, 2f, 2f, halo)
|
||||||
|
}
|
||||||
|
|
||||||
/** Worked gridsquares (4-char, uppercase) to highlight, e.g. {"OL62", "PM95"}. */
|
/** Worked gridsquares (4-char, uppercase) to highlight, e.g. {"OL62", "PM95"}. */
|
||||||
var workedGrids: Set<String> = emptySet()
|
var workedGrids: Set<String> = emptySet()
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Look4Sat-BA7OPF. Amateur radio satellite tracker and pass predictor.
|
||||||
|
* Copyright (C) 2026 BA7OPF.
|
||||||
|
* Based on Look4Sat by 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.map
|
package com.rtbishop.look4sat.feature.map
|
||||||
|
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
|||||||
@@ -159,6 +159,71 @@ private val moonIconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|||||||
android.graphics.PorterDuffColorFilter("#E0E0E0".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
android.graphics.PorterDuffColorFilter("#E0E0E0".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accent used for the station marker, footprint outline and map labels. It is recolored per
|
||||||
|
* theme (dark amber on light tiles, light amber on dark tiles) so every map element stays
|
||||||
|
* readable; [applyMapColors] keeps it in sync with the shared paints.
|
||||||
|
*/
|
||||||
|
private var mapAccentColor = "#FFE082".toColorInt()
|
||||||
|
|
||||||
|
/** Grayscale + invert filter that turns the offline OSM map dark; unused (and off) in light theme. */
|
||||||
|
private val darkTileFilter by lazy { createColorFilter() }
|
||||||
|
|
||||||
|
/** Theme the shared map paints were last colored for; null = not applied yet this process. */
|
||||||
|
private var mapIsLightUi: Boolean? = null
|
||||||
|
|
||||||
|
/** Theme the tile filter / icon caches were last applied for (drives the per-frame theme check). */
|
||||||
|
private var appliedLightUi: Boolean? = null
|
||||||
|
|
||||||
|
/** True while the offline OSM source is active — satellite imagery (Tianditu) is never filtered. */
|
||||||
|
private var offlineTilesActive = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recolor the shared overlay paints and custom overlays for the current theme. Must run before the
|
||||||
|
* markers are (re)built each frame so freshly created icons use the new colors; [applyThemeToOverlays]
|
||||||
|
* and the icon-cache eviction in the update block handle the already created ones.
|
||||||
|
*/
|
||||||
|
private fun applyMapColors(isLightUi: Boolean) {
|
||||||
|
mapIsLightUi = isLightUi
|
||||||
|
if (isLightUi) {
|
||||||
|
// Dark amber (lightScheme primary) reads on light tiles; labels get a white halo.
|
||||||
|
mapAccentColor = "#715C0C".toColorInt()
|
||||||
|
footprintPaint.color = mapAccentColor
|
||||||
|
textPaint.color = "#1E1B13".toColorInt()
|
||||||
|
textPaint.setShadowLayer(3f, 3f, 3f, Color.WHITE)
|
||||||
|
sunIconPaint.colorFilter =
|
||||||
|
android.graphics.PorterDuffColorFilter(mapAccentColor, android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
|
moonIconPaint.colorFilter = android.graphics.PorterDuffColorFilter(
|
||||||
|
"#4C4639".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
mapAccentColor = "#FFE082".toColorInt()
|
||||||
|
footprintPaint.color = mapAccentColor
|
||||||
|
textPaint.color = mapAccentColor
|
||||||
|
textPaint.setShadowLayer(3f, 3f, 3f, Color.BLACK)
|
||||||
|
sunIconPaint.colorFilter =
|
||||||
|
android.graphics.PorterDuffColorFilter(mapAccentColor, android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
|
moonIconPaint.colorFilter =
|
||||||
|
android.graphics.PorterDuffColorFilter("#E0E0E0".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
||||||
|
}
|
||||||
|
applyThemeToOverlays()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Push the current theme into the grid / award overlays, which own their own paints. */
|
||||||
|
private fun applyThemeToOverlays() {
|
||||||
|
val isLight = mapIsLightUi == true
|
||||||
|
currentMapView?.overlays?.forEach { overlay ->
|
||||||
|
when (overlay) {
|
||||||
|
is MaidenheadGridOverlay -> overlay.applyTheme(isLight)
|
||||||
|
is AwardBoundaryOverlay -> overlay.applyTheme(isLight)
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The MapView currently attached to the map page; used to recolor overlays on a theme change. */
|
||||||
|
private var currentMapView: MapView? = null
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapDestination(
|
fun MapDestination(
|
||||||
mapFilterViewModel: MapFilterViewModel,
|
mapFilterViewModel: MapFilterViewModel,
|
||||||
@@ -320,8 +385,8 @@ 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) {
|
LaunchedEffect(uiState.mapSource, uiState.tiandituKey, uiState.isLightUi) {
|
||||||
configureTileSources(mapView, uiState.mapSource, uiState.tiandituKey)
|
configureTileSources(mapView, uiState.mapSource, uiState.tiandituKey, uiState.isLightUi)
|
||||||
}
|
}
|
||||||
Column(modifier = Modifier.layoutPadding(), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
Column(modifier = Modifier.layoutPadding(), verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
val isVertical = isVerticalLayout()
|
val isVertical = isVerticalLayout()
|
||||||
@@ -359,6 +424,18 @@ private fun MapScreen(
|
|||||||
mutableStateOf(restoredViewport && uiState.isGridMode)
|
mutableStateOf(restoredViewport && uiState.isGridMode)
|
||||||
}
|
}
|
||||||
AndroidView({ mapView }) { view ->
|
AndroidView({ mapView }) { view ->
|
||||||
|
// Theme change: recolor the paints/tile filter/overlays and drop the icon
|
||||||
|
// caches, which baked the previous theme's colors into their bitmaps.
|
||||||
|
if (appliedLightUi != uiState.isLightUi) {
|
||||||
|
appliedLightUi = uiState.isLightUi
|
||||||
|
applyMapColors(uiState.isLightUi)
|
||||||
|
iconCache.evictAll()
|
||||||
|
dotIcon = null
|
||||||
|
view.overlayManager.tilesOverlay.setColorFilter(
|
||||||
|
if (!uiState.isLightUi && offlineTilesActive) darkTileFilter else null
|
||||||
|
)
|
||||||
|
requestTileRefresh(view)
|
||||||
|
}
|
||||||
// Award filter mode: show the selected award's regions with
|
// Award filter mode: show the selected award's regions with
|
||||||
// worked ones filled green; hide satellite layers like grid mode.
|
// worked ones filled green; hide satellite layers like grid mode.
|
||||||
val awardMode = selectedAward?.takeIf { it != AwardType.VUCC }
|
val awardMode = selectedAward?.takeIf { it != AwardType.VUCC }
|
||||||
@@ -393,13 +470,13 @@ private fun MapScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!uiState.isGridMode) {
|
if (!uiState.isGridMode) {
|
||||||
uiState.stationPosition?.let { setStationPosition(it, view) }
|
uiState.stationPosition?.let { setStationPosition(it, view, uiState.isLightUi) }
|
||||||
uiState.track?.let { setSatelliteTrack(it, view) }
|
uiState.track?.let { setSatelliteTrack(it, view) }
|
||||||
uiState.footprint?.let { setFootprint(it, view) }
|
uiState.footprint?.let { setFootprint(it, view) }
|
||||||
uiState.positions?.let { setPositions(it, view) { item -> onAction(MapAction.SelectItem(item)) } }
|
uiState.positions?.let { setPositions(it, view) { item -> onAction(MapAction.SelectItem(item)) } }
|
||||||
setTerminator(uiState.sunLatDeg, uiState.sunLonDeg, view)
|
setTerminator(uiState.sunLatDeg, uiState.sunLonDeg, view)
|
||||||
setSubSolarPoint(uiState.sunLatDeg, uiState.sunLonDeg, view)
|
setSubSolarPoint(uiState.sunLatDeg, uiState.sunLonDeg, view, uiState.isLightUi)
|
||||||
setMoonPosition(uiState.moonLatDeg, uiState.moonLonDeg, view)
|
setMoonPosition(uiState.moonLatDeg, uiState.moonLonDeg, view, uiState.isLightUi)
|
||||||
}
|
}
|
||||||
view.invalidate()
|
view.invalidate()
|
||||||
}
|
}
|
||||||
@@ -634,7 +711,7 @@ private fun MarkedStationRow(
|
|||||||
text = marked.call,
|
text = marked.call,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
||||||
color = ComposeColor(0xFFFFE082),
|
color = MaterialTheme.colorScheme.primary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
@@ -651,7 +728,7 @@ private fun MarkedStationRow(
|
|||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_pin),
|
painter = painterResource(R.drawable.ic_pin),
|
||||||
contentDescription = stringResource(R.string.grid_mark_pin),
|
contentDescription = stringResource(R.string.grid_mark_pin),
|
||||||
tint = if (isPinned) ComposeColor(0xFF9E9E9E) else ComposeColor(0xFFFFE082),
|
tint = if (isPinned) ComposeColor(0xFF9E9E9E) else MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(18.dp)
|
.size(18.dp)
|
||||||
.clickable(onClick = onPinMark)
|
.clickable(onClick = onPinMark)
|
||||||
@@ -706,7 +783,7 @@ private fun WorkedGridCallRow(
|
|||||||
text = call,
|
text = call,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
||||||
color = ComposeColor(0xFFFFE082),
|
color = MaterialTheme.colorScheme.primary,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
@@ -1092,22 +1169,29 @@ private fun FirstCallToggle(
|
|||||||
// region Tile sources
|
// region Tile sources
|
||||||
private var configuredTileMapView: MapView? = null
|
private var configuredTileMapView: MapView? = null
|
||||||
private var configuredTileSource: String? = null
|
private var configuredTileSource: String? = null
|
||||||
|
private var configuredTileIsLightUi: Boolean? = null
|
||||||
|
|
||||||
private fun configureTileSources(mapView: MapView, mapSource: String, tiandituKey: String) {
|
private fun configureTileSources(mapView: MapView, mapSource: String, tiandituKey: String, isLightUi: Boolean) {
|
||||||
val key = tiandituKey.trim()
|
val key = tiandituKey.trim()
|
||||||
val layers = tiandituLayers(mapSource)
|
val layers = tiandituLayers(mapSource)
|
||||||
val sourceKey = if (layers == null || key.isBlank()) MapSource.OSM else "${layers.base}:$key"
|
val offlineSource = layers == null || key.isBlank()
|
||||||
if (configuredTileMapView === mapView && configuredTileSource == sourceKey) return
|
offlineTilesActive = offlineSource
|
||||||
|
val sourceKey = if (offlineSource) MapSource.OSM else "${layers.base}:$key"
|
||||||
|
if (configuredTileMapView === mapView && configuredTileSource == sourceKey &&
|
||||||
|
configuredTileIsLightUi == isLightUi
|
||||||
|
) return
|
||||||
|
|
||||||
configuredTileMapView = mapView
|
configuredTileMapView = mapView
|
||||||
configuredTileSource = sourceKey
|
configuredTileSource = sourceKey
|
||||||
|
configuredTileIsLightUi = isLightUi
|
||||||
(mapView.overlays.getOrNull(OVERLAY_TDT_LABELS) as? TilesOverlay)?.onDetach(mapView)
|
(mapView.overlays.getOrNull(OVERLAY_TDT_LABELS) as? TilesOverlay)?.onDetach(mapView)
|
||||||
|
|
||||||
if (layers == null || key.isBlank()) {
|
if (offlineSource) {
|
||||||
mapView.setUseDataConnection(false)
|
mapView.setUseDataConnection(false)
|
||||||
mapView.setTileSource(offlineTileSource)
|
mapView.setTileSource(offlineTileSource)
|
||||||
mapView.maxZoomLevel = OFFLINE_MAX_ZOOM
|
mapView.maxZoomLevel = OFFLINE_MAX_ZOOM
|
||||||
mapView.overlayManager.tilesOverlay.applyTileOverlayDefaults(createColorFilter())
|
// The dark-map filter belongs to the dark theme only; the light theme shows plain tiles.
|
||||||
|
mapView.overlayManager.tilesOverlay.applyTileOverlayDefaults(if (isLightUi) null else darkTileFilter)
|
||||||
mapView.overlays[OVERLAY_TDT_LABELS] = FolderOverlay()
|
mapView.overlays[OVERLAY_TDT_LABELS] = FolderOverlay()
|
||||||
requestTileRefresh(mapView)
|
requestTileRefresh(mapView)
|
||||||
return
|
return
|
||||||
@@ -1210,6 +1294,7 @@ private fun setAwardMode(award: AwardType, workedCodes: Set<String>, mapView: Ma
|
|||||||
isEnabled = true
|
isEnabled = true
|
||||||
regions = AwardBoundaryData.load(mapView.context, asset)
|
regions = AwardBoundaryData.load(mapView.context, asset)
|
||||||
this.workedCodes = workedCodes
|
this.workedCodes = workedCodes
|
||||||
|
applyTheme(mapIsLightUi == true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (index in OVERLAY_STATION..OVERLAY_MOON) {
|
for (index in OVERLAY_STATION..OVERLAY_MOON) {
|
||||||
@@ -1252,6 +1337,7 @@ private fun setGridMode(
|
|||||||
this.firstCallsByGrid = firstCallsByGrid
|
this.firstCallsByGrid = firstCallsByGrid
|
||||||
this.markedGrids = markedGrids
|
this.markedGrids = markedGrids
|
||||||
this.ownGrid = ownGridOf(stationPosition)
|
this.ownGrid = ownGridOf(stationPosition)
|
||||||
|
applyTheme(mapIsLightUi == true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Satellite-related layers are hidden in grid mode; the grid overlay
|
// Satellite-related layers are hidden in grid mode; the grid overlay
|
||||||
@@ -1306,17 +1392,28 @@ private fun gridOfPoint(latitude: Double, longitude: Double): String? {
|
|||||||
return "${'A' + fieldLon}${'A' + fieldLat}$subLon$subLat"
|
return "${'A' + fieldLon}${'A' + fieldLat}$subLon$subLat"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setStationPosition(stationPos: GeoPos, mapView: MapView) {
|
private fun setStationPosition(stationPos: GeoPos, mapView: MapView, isLightUi: Boolean) {
|
||||||
try {
|
try {
|
||||||
val overlay = mapView.overlays[OVERLAY_STATION]
|
val overlay = mapView.overlays[OVERLAY_STATION]
|
||||||
|
val stationPoint = GeoPoint(stationPos.latitude, stationPos.longitude)
|
||||||
if (overlay is Marker) {
|
if (overlay is Marker) {
|
||||||
overlay.position = GeoPoint(stationPos.latitude, stationPos.longitude)
|
overlay.position = stationPoint
|
||||||
|
// Icon tint is baked into the drawable, so re-tint after a theme change.
|
||||||
|
if ((overlay.relatedObject as? Boolean) != isLightUi) {
|
||||||
|
overlay.icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position)?.apply {
|
||||||
|
setTint(mapAccentColor)
|
||||||
|
}
|
||||||
|
overlay.relatedObject = isLightUi
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mapView.overlays[OVERLAY_STATION] = Marker(mapView).apply {
|
mapView.overlays[OVERLAY_STATION] = Marker(mapView).apply {
|
||||||
setInfoWindow(null)
|
setInfoWindow(null)
|
||||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||||
icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position)
|
icon = ContextCompat.getDrawable(mapView.context, R.drawable.ic_position)?.apply {
|
||||||
position = GeoPoint(stationPos.latitude, stationPos.longitude)
|
setTint(mapAccentColor)
|
||||||
|
}
|
||||||
|
position = stationPoint
|
||||||
|
relatedObject = isLightUi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -1539,26 +1636,37 @@ private fun setTerminator(sunLatDeg: Double, sunLonDeg: Double, mapView: MapView
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Build the 48dp sun/moon marker icon, tinted with the theme-dependent paint. */
|
||||||
|
private fun buildMarkerIcon(mapView: MapView, resId: Int, paint: Paint): Drawable {
|
||||||
|
val iconSize = 48
|
||||||
|
val bmp = createBitmap(iconSize, iconSize)
|
||||||
|
ContextCompat.getDrawable(mapView.context, resId)?.apply {
|
||||||
|
setBounds(0, 0, iconSize, iconSize)
|
||||||
|
colorFilter = paint.colorFilter
|
||||||
|
draw(Canvas(bmp))
|
||||||
|
}
|
||||||
|
return bmp.toDrawable(mapView.context.resources)
|
||||||
|
}
|
||||||
|
|
||||||
/** Place an ic_sun icon marker at the sub-solar point. */
|
/** Place an ic_sun icon marker at the sub-solar point. */
|
||||||
private fun setSubSolarPoint(sunLatDeg: Double, sunLonDeg: Double, mapView: MapView) {
|
private fun setSubSolarPoint(sunLatDeg: Double, sunLonDeg: Double, mapView: MapView, isLightUi: Boolean) {
|
||||||
try {
|
try {
|
||||||
val overlay = mapView.overlays[OVERLAY_SUN]
|
val overlay = mapView.overlays[OVERLAY_SUN]
|
||||||
val sunPos = GeoPoint(sunLatDeg, sunLonDeg)
|
val sunPos = GeoPoint(sunLatDeg, sunLonDeg)
|
||||||
if (overlay is Marker) {
|
if (overlay is Marker) {
|
||||||
overlay.position = sunPos
|
overlay.position = sunPos
|
||||||
} else {
|
// The icon bitmap bakes in the theme tint, so rebuild it after a theme change.
|
||||||
val iconSize = 48
|
if ((overlay.relatedObject as? Boolean) != isLightUi) {
|
||||||
val bmp = createBitmap(iconSize, iconSize)
|
overlay.icon = buildMarkerIcon(mapView, R.drawable.ic_sun, sunIconPaint)
|
||||||
ContextCompat.getDrawable(mapView.context, R.drawable.ic_sun)?.apply {
|
overlay.relatedObject = isLightUi
|
||||||
setBounds(0, 0, iconSize, iconSize)
|
|
||||||
colorFilter = sunIconPaint.colorFilter
|
|
||||||
draw(Canvas(bmp))
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
mapView.overlays[OVERLAY_SUN] = Marker(mapView).apply {
|
mapView.overlays[OVERLAY_SUN] = Marker(mapView).apply {
|
||||||
setInfoWindow(null)
|
setInfoWindow(null)
|
||||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||||
icon = bmp.toDrawable(mapView.context.resources)
|
icon = buildMarkerIcon(mapView, R.drawable.ic_sun, sunIconPaint)
|
||||||
position = sunPos
|
position = sunPos
|
||||||
|
relatedObject = isLightUi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -1567,26 +1675,23 @@ private fun setSubSolarPoint(sunLatDeg: Double, sunLonDeg: Double, mapView: MapV
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Place an ic_moon icon marker at the sub-lunar point. */
|
/** Place an ic_moon icon marker at the sub-lunar point. */
|
||||||
private fun setMoonPosition(moonLatDeg: Double, moonLonDeg: Double, mapView: MapView) {
|
private fun setMoonPosition(moonLatDeg: Double, moonLonDeg: Double, mapView: MapView, isLightUi: Boolean) {
|
||||||
try {
|
try {
|
||||||
val overlay = mapView.overlays[OVERLAY_MOON]
|
val overlay = mapView.overlays[OVERLAY_MOON]
|
||||||
val moonPos = GeoPoint(moonLatDeg, moonLonDeg)
|
val moonPos = GeoPoint(moonLatDeg, moonLonDeg)
|
||||||
if (overlay is Marker) {
|
if (overlay is Marker) {
|
||||||
overlay.position = moonPos
|
overlay.position = moonPos
|
||||||
} else {
|
if ((overlay.relatedObject as? Boolean) != isLightUi) {
|
||||||
val iconSize = 48
|
overlay.icon = buildMarkerIcon(mapView, R.drawable.ic_moon, moonIconPaint)
|
||||||
val bmp = createBitmap(iconSize, iconSize)
|
overlay.relatedObject = isLightUi
|
||||||
val c = Canvas(bmp)
|
|
||||||
ContextCompat.getDrawable(mapView.context, R.drawable.ic_moon)?.apply {
|
|
||||||
setBounds(0, 0, iconSize, iconSize)
|
|
||||||
colorFilter = moonIconPaint.colorFilter
|
|
||||||
draw(c)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
mapView.overlays[OVERLAY_MOON] = Marker(mapView).apply {
|
mapView.overlays[OVERLAY_MOON] = Marker(mapView).apply {
|
||||||
setInfoWindow(null)
|
setInfoWindow(null)
|
||||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
|
||||||
icon = bmp.toDrawable(mapView.context.resources)
|
icon = buildMarkerIcon(mapView, R.drawable.ic_moon, moonIconPaint)
|
||||||
position = moonPos
|
position = moonPos
|
||||||
|
relatedObject = isLightUi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -1631,10 +1736,12 @@ private fun rememberMapViewWithLifecycle(
|
|||||||
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
|
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
|
||||||
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
|
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
|
||||||
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
|
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
|
||||||
overlayManager.tilesOverlay.setColorFilter(createColorFilter())
|
// The dark-map tile filter (or no filter in the light theme) is applied per theme from
|
||||||
|
// the AndroidView update block, so it also follows a theme switch at runtime.
|
||||||
setScrollableAreaLimitLatitude(maxLat, minLat, 0)
|
setScrollableAreaLimitLatitude(maxLat, minLat, 0)
|
||||||
overlays.addAll(Array(OVERLAY_COUNT) { FolderOverlay() })
|
overlays.addAll(Array(OVERLAY_COUNT) { FolderOverlay() })
|
||||||
overlays[OVERLAY_GRID] = MaidenheadGridOverlay()
|
overlays[OVERLAY_GRID] = MaidenheadGridOverlay().apply { applyTheme(mapIsLightUi == true) }
|
||||||
|
currentMapView = this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val lifecycleObserver = rememberMapViewLifecycleObserver(mapView)
|
val lifecycleObserver = rememberMapViewLifecycleObserver(mapView)
|
||||||
@@ -1647,7 +1754,10 @@ private fun rememberMapViewWithLifecycle(
|
|||||||
// released with the MapView or they keep the Activity and its bitmaps alive after disposal.
|
// released with the MapView or they keep the Activity and its bitmaps alive after disposal.
|
||||||
// (The viewport save lives in MapDestination, where the grid/satellite mode is known.)
|
// (The viewport save lives in MapDestination, where the grid/satellite mode is known.)
|
||||||
DisposableEffect(mapView) {
|
DisposableEffect(mapView) {
|
||||||
onDispose { clearMapCaches() }
|
onDispose {
|
||||||
|
clearMapCaches()
|
||||||
|
if (currentMapView === mapView) currentMapView = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return mapView
|
return mapView
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class MapViewModel(
|
|||||||
_uiState.update {
|
_uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
isUtc = settings.stateOfUtc,
|
isUtc = settings.stateOfUtc,
|
||||||
|
isLightUi = settings.stateOfLightTheme,
|
||||||
mapSource = settings.mapSource,
|
mapSource = settings.mapSource,
|
||||||
tiandituKey = settings.tiandituKey,
|
tiandituKey = settings.tiandituKey,
|
||||||
isGridMode = settings.stateOfMapGrid,
|
isGridMode = settings.stateOfMapGrid,
|
||||||
|
|||||||
+43
-29
@@ -35,6 +35,7 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
|||||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -264,11 +265,13 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
if (dialogs.logbook) {
|
if (dialogs.logbook) {
|
||||||
LogbookDialog(
|
LogbookDialog(
|
||||||
records = uiState.logbookRecords,
|
records = uiState.logbookRecords,
|
||||||
|
satelliteCandidates = uiState.satelliteCatalog,
|
||||||
uploadBusy = uiState.logbookUploadBusy,
|
uploadBusy = uiState.logbookUploadBusy,
|
||||||
uploadMessage = uiState.logbookUploadMessage,
|
uploadMessage = uiState.logbookUploadMessage,
|
||||||
preview = uiState.logbookPreview,
|
preview = uiState.logbookPreview,
|
||||||
onDismiss = { dialogs.logbook = false },
|
onDismiss = { dialogs.logbook = false },
|
||||||
onDelete = { onAction(SettingsAction.DeleteLogbookRecord(it)) },
|
onDelete = { onAction(SettingsAction.DeleteLogbookRecord(it)) },
|
||||||
|
onEdit = { onAction(SettingsAction.UpdateLogbookRecord(it)) },
|
||||||
onUpload = { onAction(SettingsAction.PrepareLogbookUpload) },
|
onUpload = { onAction(SettingsAction.PrepareLogbookUpload) },
|
||||||
onConfirmUpload = { onAction(SettingsAction.ConfirmLogbookUpload) },
|
onConfirmUpload = { onAction(SettingsAction.ConfirmLogbookUpload) },
|
||||||
onDismissPreview = { onAction(SettingsAction.DismissLogbookPreview) },
|
onDismissPreview = { onAction(SettingsAction.DismissLogbookPreview) },
|
||||||
@@ -292,10 +295,8 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
|
|
||||||
// URLs for top bar
|
// URLs for top bar
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
val appUrl = stringResource(R.string.prefs_app_url)
|
val upstreamTitle = stringResource(R.string.prefs_upstream_title)
|
||||||
val donateUrl = stringResource(R.string.prefs_donate_url)
|
val upstreamUrl = stringResource(R.string.prefs_upstream_url)
|
||||||
val fdroidTitle = stringResource(R.string.prefs_fdroid_title)
|
|
||||||
val fdroidUrl = stringResource(R.string.prefs_fdroid_url)
|
|
||||||
val gitHubTitle = stringResource(R.string.prefs_github_title)
|
val gitHubTitle = stringResource(R.string.prefs_github_title)
|
||||||
val gitHubUrl = stringResource(R.string.prefs_github_url)
|
val gitHubUrl = stringResource(R.string.prefs_github_url)
|
||||||
val licenseUrl = stringResource(R.string.prefs_license_url)
|
val licenseUrl = stringResource(R.string.prefs_license_url)
|
||||||
@@ -309,18 +310,17 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
if (isVerticalLayout) {
|
if (isVerticalLayout) {
|
||||||
TopBar {
|
TopBar {
|
||||||
TopCard(
|
TopCard(
|
||||||
onClick = { safeOpenUri(appUrl) },
|
onClick = { showUpdateChecker = true },
|
||||||
version = uiState.appVersionName,
|
version = uiState.appVersionName,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
PrimaryIconCard(onClick = { safeOpenUri(donateUrl) }, resId = R.drawable.ic_pound)
|
|
||||||
}
|
}
|
||||||
TopBar {
|
TopBar {
|
||||||
Row(modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
Row(modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
BotCard(
|
BotCard(
|
||||||
onClick = { safeOpenUri(fdroidUrl) },
|
onClick = { safeOpenUri(upstreamUrl) },
|
||||||
resId = R.drawable.ic_fdroid,
|
resId = R.drawable.ic_github,
|
||||||
text = fdroidTitle,
|
text = upstreamTitle,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
BotCard(
|
BotCard(
|
||||||
@@ -335,17 +335,16 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) ->
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TopBar {
|
TopBar {
|
||||||
PrimaryIconCard(onClick = { safeOpenUri(donateUrl) }, resId = R.drawable.ic_pound)
|
|
||||||
TopCard(
|
TopCard(
|
||||||
onClick = { safeOpenUri(appUrl) },
|
onClick = { showUpdateChecker = true },
|
||||||
version = uiState.appVersionName,
|
version = uiState.appVersionName,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
Row(modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
Row(modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
BotCard(
|
BotCard(
|
||||||
onClick = { safeOpenUri(fdroidUrl) },
|
onClick = { safeOpenUri(upstreamUrl) },
|
||||||
resId = R.drawable.ic_fdroid,
|
resId = R.drawable.ic_github,
|
||||||
text = fdroidTitle,
|
text = upstreamTitle,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
BotCard(
|
BotCard(
|
||||||
@@ -639,7 +638,7 @@ private fun OtherCard(
|
|||||||
ElevatedCard(
|
ElevatedCard(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(370.dp)
|
.height(420.dp) // 370dp + one switch row (light theme)
|
||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
|
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
|
||||||
Text(
|
Text(
|
||||||
@@ -666,7 +665,16 @@ private fun OtherCard(
|
|||||||
text = stringResource(R.string.prefs_compass_calibration_button),
|
text = stringResource(R.string.prefs_compass_calibration_button),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
SwitchRow(R.string.prefs_other_switch_night_mode, settings.stateOfNightMode) {
|
// Appearance: light theme + the red night filter (mutually exclusive —
|
||||||
|
// the night filter is disabled while the light theme is on).
|
||||||
|
SwitchRow(R.string.prefs_other_switch_light_theme, settings.stateOfLightTheme) {
|
||||||
|
onAction(SettingsAction.ToggleLightTheme(it))
|
||||||
|
}
|
||||||
|
SwitchRow(
|
||||||
|
R.string.prefs_other_switch_night_mode,
|
||||||
|
settings.stateOfNightMode,
|
||||||
|
enabled = !settings.stateOfLightTheme
|
||||||
|
) {
|
||||||
onAction(SettingsAction.ToggleNightMode(it))
|
onAction(SettingsAction.ToggleNightMode(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -762,14 +770,19 @@ private fun CompassCalibrationDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SwitchRow(labelResId: Int, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
private fun SwitchRow(
|
||||||
|
labelResId: Int,
|
||||||
|
checked: Boolean,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
onCheckedChange: (Boolean) -> Unit
|
||||||
|
) {
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text(text = stringResource(id = labelResId))
|
Text(text = stringResource(id = labelResId))
|
||||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
Switch(checked = checked, enabled = enabled, onCheckedChange = onCheckedChange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -845,27 +858,28 @@ private fun CardCreditsPreview() = MainTheme { CardCredits() }
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun CardCredits(modifier: Modifier = Modifier) {
|
private fun CardCredits(modifier: Modifier = Modifier) {
|
||||||
ElevatedCard(
|
ElevatedCard(modifier = modifier.fillMaxWidth()) {
|
||||||
modifier = modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(268.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
|
||||||
.fillMaxHeight()
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.prefs_outro_title),
|
text = stringResource(id = R.string.prefs_outro_title),
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.prefs_outro_thanks)
|
text = stringResource(id = R.string.prefs_outro_notice),
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
HorizontalDivider()
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.prefs_outro_thanks),
|
||||||
|
fontSize = 12.sp
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.prefs_outro_license),
|
text = stringResource(id = R.string.prefs_outro_license),
|
||||||
color = MaterialTheme.colorScheme.primary
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontSize = 12.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ data class SettingsState(
|
|||||||
val lotwLastSyncEpochMs: Long = 0L,
|
val lotwLastSyncEpochMs: Long = 0L,
|
||||||
/** Logbook (QSO records + LoTW confirmations), newest first. */
|
/** Logbook (QSO records + LoTW confirmations), newest first. */
|
||||||
val logbookRecords: List<com.rtbishop.look4sat.core.domain.logbook.QsoRecord> = emptyList(),
|
val logbookRecords: List<com.rtbishop.look4sat.core.domain.logbook.QsoRecord> = emptyList(),
|
||||||
|
/** ARRL satellite names (config.tq6) — the only names a record may be signed with. */
|
||||||
|
val satelliteCatalog: List<String> = emptyList(),
|
||||||
/** Imported LoTW upload certificate (null when none). */
|
/** Imported LoTW upload certificate (null when none). */
|
||||||
val lotwCertificate: com.rtbishop.look4sat.core.domain.repository.LoTWCertificate? = null,
|
val lotwCertificate: com.rtbishop.look4sat.core.domain.repository.LoTWCertificate? = null,
|
||||||
/** LoTW upload station location (null when unset). */
|
/** LoTW upload station location (null when unset). */
|
||||||
@@ -153,6 +155,8 @@ sealed interface SettingsAction {
|
|||||||
// Logbook (QSO records + LoTW confirmations)
|
// Logbook (QSO records + LoTW confirmations)
|
||||||
data object RefreshLogbook : SettingsAction
|
data object RefreshLogbook : SettingsAction
|
||||||
data class DeleteLogbookRecord(val id: Long) : SettingsAction
|
data class DeleteLogbookRecord(val id: Long) : SettingsAction
|
||||||
|
/** Persist an edited record (frequency/callsign/time/…) from the logbook dialog. */
|
||||||
|
data class UpdateLogbookRecord(val record: com.rtbishop.look4sat.core.domain.logbook.QsoRecord) : SettingsAction
|
||||||
data object PrepareLogbookUpload : SettingsAction
|
data object PrepareLogbookUpload : SettingsAction
|
||||||
data object ConfirmLogbookUpload : SettingsAction
|
data object ConfirmLogbookUpload : SettingsAction
|
||||||
data object DismissLogbookPreview : SettingsAction
|
data object DismissLogbookPreview : SettingsAction
|
||||||
|
|||||||
Reference in new issue
Block a user