mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
782b438a07 | ||
|
|
fac319b215 | ||
|
|
b7ac9c913a | ||
|
|
de3d5a6d84 | ||
|
|
5e39c945b2 | ||
|
|
1d07f76ada |
No files matched your search
@@ -27,6 +27,8 @@ import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.rtbishop.look4sat.core.domain.repository.IContainerProvider
|
||||
@@ -48,8 +50,10 @@ class MainActivity : ComponentActivity() {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
observeNightFilterState()
|
||||
val settingsRepo = (applicationContext as IContainerProvider).getMainContainer().settingsRepo
|
||||
setContent {
|
||||
MainTheme(isDarkTheme = true) { NavRoot() }
|
||||
val otherSettings by settingsRepo.otherSettings.collectAsState()
|
||||
MainTheme(isDarkTheme = !otherSettings.stateOfLightTheme) { NavRoot() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,29 +99,43 @@ fun NavRoot(deeplink: String? = null) {
|
||||
// Reverse: outgoing slides out to the right, incoming drifts in from the left
|
||||
val popTransition = slideInHorizontally(tween(300)) { -it / 3 } togetherWith
|
||||
slideOutHorizontally(tween(300)) { it }
|
||||
NavDisplay(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
backStack = rootBackStack,
|
||||
onBack = navigateBack,
|
||||
transitionSpec = { pushTransition },
|
||||
popTransitionSpec = { popTransition },
|
||||
predictivePopTransitionSpec = { popTransition },
|
||||
entryDecorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator()
|
||||
),
|
||||
entryProvider = entryProvider {
|
||||
entry<Screen.Passes> { MainScreen(navigateToRadar = navigateToRadar) }
|
||||
entry<RadarDestination> {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
RadarDestination(navigateUp = navigateBack)
|
||||
// Elevation color thresholds must be provided at the root level so that BOTH
|
||||
// the tab content (MainScreen) and the root-level RadarDestination see the
|
||||
// user's custom thresholds. RadarDestination is a sibling entry of MainScreen
|
||||
// in this NavDisplay, so a provider inside MainScreen never reaches it.
|
||||
val context = LocalContext.current
|
||||
val container = (context.applicationContext as IContainerProvider).getMainContainer()
|
||||
val otherSettings by container.settingsRepo.otherSettings.collectAsStateWithLifecycle()
|
||||
CompositionLocalProvider(
|
||||
LocalElevationThresholds provides ElevationThresholds(
|
||||
low = otherSettings.lowElevation,
|
||||
high = otherSettings.highElevation
|
||||
)
|
||||
) {
|
||||
NavDisplay(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
backStack = rootBackStack,
|
||||
onBack = navigateBack,
|
||||
transitionSpec = { pushTransition },
|
||||
popTransitionSpec = { popTransition },
|
||||
predictivePopTransitionSpec = { popTransition },
|
||||
entryDecorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator()
|
||||
),
|
||||
entryProvider = entryProvider {
|
||||
entry<Screen.Passes> { MainScreen(navigateToRadar = navigateToRadar) }
|
||||
entry<RadarDestination> {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
RadarDestination(navigateUp = navigateBack)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -129,7 +143,6 @@ private fun MainScreen(navigateToRadar: () -> Unit = {}) {
|
||||
val context = LocalContext.current
|
||||
val container = (context.applicationContext as IContainerProvider).getMainContainer()
|
||||
val trackingState by container.radioTrackingService.state.collectAsStateWithLifecycle()
|
||||
val otherSettings by container.settingsRepo.otherSettings.collectAsStateWithLifecycle()
|
||||
|
||||
val backStack = rememberNavBackStack(Screen.Passes)
|
||||
val currentKey = backStack.lastOrNull()
|
||||
@@ -139,88 +152,81 @@ private fun MainScreen(navigateToRadar: () -> Unit = {}) {
|
||||
val navItems =
|
||||
listOf(Screen.Satellites, Screen.Passes, Screen.Status, Screen.Map, Screen.Settings)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalElevationThresholds provides ElevationThresholds(
|
||||
low = otherSettings.lowElevation,
|
||||
high = otherSettings.highElevation
|
||||
)
|
||||
) {
|
||||
NavigationSuiteScaffold(
|
||||
navigationSuiteItems = {
|
||||
navItems.forEach { screen ->
|
||||
val isSelected = when (currentKey) {
|
||||
is Screen.Satellites -> screen is Screen.Satellites
|
||||
is Screen.Passes -> screen is Screen.Passes
|
||||
is Screen.Status -> screen is Screen.Status
|
||||
is Screen.Map -> screen is Screen.Map
|
||||
is Screen.Settings -> screen is Screen.Settings
|
||||
else -> false
|
||||
}
|
||||
item(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(screen.iconResId),
|
||||
contentDescription = stringResource(screen.titleResId)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(screen.titleResId)) },
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
if (isSelected) return@item
|
||||
while (backStack.size > 1) backStack.removeAt(backStack.size - 1)
|
||||
if (screen !is Screen.Passes) backStack.add(screen)
|
||||
}
|
||||
)
|
||||
NavigationSuiteScaffold(
|
||||
navigationSuiteItems = {
|
||||
navItems.forEach { screen ->
|
||||
val isSelected = when (currentKey) {
|
||||
is Screen.Satellites -> screen is Screen.Satellites
|
||||
is Screen.Passes -> screen is Screen.Passes
|
||||
is Screen.Status -> screen is Screen.Status
|
||||
is Screen.Map -> screen is Screen.Map
|
||||
is Screen.Settings -> screen is Screen.Settings
|
||||
else -> false
|
||||
}
|
||||
},
|
||||
navigationSuiteColors = NavigationSuiteDefaults.colors(
|
||||
navigationRailContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
),
|
||||
layoutType = when {
|
||||
!hasEnoughHeight() && hasEnoughWidth() -> NavigationSuiteType.NavigationRail
|
||||
!hasEnoughWidth() -> NavigationSuiteType.ShortNavigationBarCompact
|
||||
else -> NavigationSuiteType.ShortNavigationBarMedium
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
modifier = Modifier.weight(1f),
|
||||
onBack = navigateBack,
|
||||
transitionSpec = { fadeTransition },
|
||||
popTransitionSpec = { fadeTransition },
|
||||
predictivePopTransitionSpec = { fadeTransition },
|
||||
entryDecorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator()
|
||||
),
|
||||
entryProvider = entryProvider {
|
||||
entry<Screen.Satellites> {
|
||||
SatellitesDestination(navigateUp = navigateBack)
|
||||
}
|
||||
entry<Screen.Passes> {
|
||||
PassesDestination { catNum, aosTime ->
|
||||
container.satelliteRepo.selectPass(catNum, aosTime)
|
||||
navigateToRadar()
|
||||
}
|
||||
}
|
||||
entry<Screen.Status> { SatStatusDestination() }
|
||||
entry<Screen.Map> { MapDestination() }
|
||||
entry<Screen.Settings> { SettingsDestination() }
|
||||
item(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(screen.iconResId),
|
||||
contentDescription = stringResource(screen.titleResId)
|
||||
)
|
||||
},
|
||||
label = { Text(stringResource(screen.titleResId)) },
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
if (isSelected) return@item
|
||||
while (backStack.size > 1) backStack.removeAt(backStack.size - 1)
|
||||
if (screen !is Screen.Passes) backStack.add(screen)
|
||||
}
|
||||
)
|
||||
if (trackingState.isActive) {
|
||||
TrackingBanner(
|
||||
state = trackingState,
|
||||
onClick = {
|
||||
val pass = trackingState.currentPass
|
||||
if (pass != null) {
|
||||
container.satelliteRepo.selectPass(pass.catNum, pass.aosTime)
|
||||
navigateToRadar()
|
||||
}
|
||||
}
|
||||
},
|
||||
navigationSuiteColors = NavigationSuiteDefaults.colors(
|
||||
navigationRailContainerColor = MaterialTheme.colorScheme.surfaceContainer
|
||||
),
|
||||
layoutType = when {
|
||||
!hasEnoughHeight() && hasEnoughWidth() -> NavigationSuiteType.NavigationRail
|
||||
!hasEnoughWidth() -> NavigationSuiteType.ShortNavigationBarCompact
|
||||
else -> NavigationSuiteType.ShortNavigationBarMedium
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
modifier = Modifier.weight(1f),
|
||||
onBack = navigateBack,
|
||||
transitionSpec = { fadeTransition },
|
||||
popTransitionSpec = { fadeTransition },
|
||||
predictivePopTransitionSpec = { fadeTransition },
|
||||
entryDecorators = listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator()
|
||||
),
|
||||
entryProvider = entryProvider {
|
||||
entry<Screen.Satellites> {
|
||||
SatellitesDestination(navigateUp = navigateBack)
|
||||
}
|
||||
entry<Screen.Passes> {
|
||||
PassesDestination { catNum, aosTime ->
|
||||
container.satelliteRepo.selectPass(catNum, aosTime)
|
||||
navigateToRadar()
|
||||
}
|
||||
)
|
||||
}
|
||||
entry<Screen.Status> { SatStatusDestination() }
|
||||
entry<Screen.Map> { MapDestination() }
|
||||
entry<Screen.Settings> { SettingsDestination() }
|
||||
}
|
||||
)
|
||||
if (trackingState.isActive) {
|
||||
TrackingBanner(
|
||||
state = trackingState,
|
||||
onClick = {
|
||||
val pass = trackingState.currentPass
|
||||
if (pass != null) {
|
||||
container.satelliteRepo.selectPass(pass.catNum, pass.aosTime)
|
||||
navigateToRadar()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -330,12 +330,13 @@ object IcomCivProtocol {
|
||||
|
||||
/**
|
||||
* Parse frequency + mode from a CMD_READ_FREQ reply payload.
|
||||
* Payload layout after stripping command byte: [5 freq bytes] [mode byte] [filter byte]
|
||||
* Payload layout after stripping command byte: [5 freq bytes]
|
||||
* The IC-705 does not include a mode byte; if one is present it is parsed, otherwise mode is empty.
|
||||
*/
|
||||
fun parseFreqModePayload(payload: ByteArray): Pair<Long, String>? {
|
||||
if (payload.size < 6) return null
|
||||
if (payload.size < 5) return null
|
||||
val freqHz = decodeFrequencyBcd(payload.copyOfRange(0, 5))
|
||||
val mode = BYTE_TO_MODE[payload[5]] ?: return null
|
||||
val mode = if (payload.size > 5) BYTE_TO_MODE[payload[5]] ?: return null else ""
|
||||
return freqHz to mode
|
||||
}
|
||||
|
||||
|
||||
+12
-3
@@ -35,6 +35,8 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.TimeZone
|
||||
@@ -67,8 +69,9 @@ class SatelliteRepo(
|
||||
override suspend fun initRepository() = withContext(dispatcher) {
|
||||
combine(
|
||||
settingsRepo.selectedIds,
|
||||
settingsRepo.stationPosition
|
||||
) { selectedIds, _ -> selectedIds }
|
||||
settingsRepo.stationPosition,
|
||||
settingsRepo.otherSettings.map { it.stateOfUtc }.distinctUntilChanged()
|
||||
) { selectedIds, _, _ -> selectedIds }
|
||||
.collect { selectedIds ->
|
||||
_satellites.update { localStorage.getEntriesWithIds(selectedIds) }
|
||||
val settings = settingsRepo.passesSettings.value
|
||||
@@ -176,7 +179,13 @@ class SatelliteRepo(
|
||||
aosEndMinute: Int,
|
||||
invertAosTimeWindow: Boolean
|
||||
): Boolean {
|
||||
val offsetMillis = TimeZone.getDefault().getOffset(aosTime).toLong()
|
||||
// Must match the timezone used to display pass times (UTC toggle vs device local)
|
||||
val tz = if (settingsRepo.otherSettings.value.stateOfUtc) {
|
||||
TimeZone.getTimeZone("UTC")
|
||||
} else {
|
||||
TimeZone.getDefault()
|
||||
}
|
||||
val offsetMillis = tz.getOffset(aosTime).toLong()
|
||||
val localMillis = Math.floorMod(aosTime + offsetMillis, 24L * 60L * 60L * 1000L)
|
||||
val aosMinute = (localMillis / 60_000L).toInt()
|
||||
val inRange = if (aosStartMinute <= aosEndMinute) {
|
||||
|
||||
+19
-2
@@ -114,12 +114,29 @@ class SelectionRepo(
|
||||
/**
|
||||
* Filters items by query. Uses toIntOrNull() instead of exception-based flow,
|
||||
* and lowercases the query once up front instead of per-item.
|
||||
*
|
||||
* Fuzzy search: the query is split into space-separated tokens and every
|
||||
* token must appear in the satellite name after both sides are normalized
|
||||
* (lowercased, non-alphanumeric separators such as dashes, spaces, brackets
|
||||
* and dots stripped). This makes "ao7" match "AO-7 (AMSAT-OSCAR 7)" and
|
||||
* "iss zarya" match "ISS (ZARYA)" — exact continuous-substring matching
|
||||
* previously failed whenever the name contained a separator the query lacked.
|
||||
*/
|
||||
private fun filterByQuery(items: List<SatItem>, query: String): List<SatItem> {
|
||||
if (query.isBlank()) return items
|
||||
val catnum = query.toIntOrNull()
|
||||
if (catnum != null) return items.filter { it.catnum == catnum }
|
||||
val lowerQuery = query.lowercase()
|
||||
return items.filter { it.name.lowercase().contains(lowerQuery) }
|
||||
val tokens = query.split(' ')
|
||||
.map { normalizeForSearch(it) }
|
||||
.filter { it.isNotEmpty() }
|
||||
if (tokens.isEmpty()) return items
|
||||
return items.filter { item ->
|
||||
val normalizedName = normalizeForSearch(item.name)
|
||||
tokens.all { normalizedName.contains(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/** Lowercases and strips all non-alphanumeric chars for fuzzy matching. */
|
||||
private fun normalizeForSearch(text: String): String =
|
||||
text.lowercase().filter { it.isLetterOrDigit() }
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.rtbishop.look4sat.core.data.framework
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
|
||||
class IcomCivProtocolTest {
|
||||
|
||||
private fun bytes(vararg values: Int) = ByteArray(values.size) { values[it].toByte() }
|
||||
|
||||
@Test
|
||||
fun parseFreqModePayload_ic705ReadFreqReply() {
|
||||
// Reply captured from an IC-705 to CMD 0x03: frequency only, no mode byte
|
||||
val reply = bytes(0xFE, 0xFE, 0xE0, 0xA4, 0x03, 0x60, 0x74, 0x95, 0x45, 0x01, 0xFD)
|
||||
val response = IcomCivProtocol.parseResponse(reply, IcomCivProtocol.CMD_READ_FREQ)
|
||||
assertNotNull(response)
|
||||
assertEquals(145957460L to "", IcomCivProtocol.parseFreqModePayload(response!!.payload))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseFreqModePayload_withModeByte() {
|
||||
assertEquals(
|
||||
435611000L to "USB",
|
||||
IcomCivProtocol.parseFreqModePayload(bytes(0x00, 0x10, 0x61, 0x35, 0x04, 0x01, 0x01))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseFreqModePayload_tooShort() {
|
||||
assertNull(IcomCivProtocol.parseFreqModePayload(bytes(0x60, 0x74, 0x95, 0x45)))
|
||||
}
|
||||
}
|
||||
+59
@@ -84,6 +84,65 @@ class SelectionRepoTest {
|
||||
assertEquals(listOf(true, false, false), items.map { it.isSelected })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `query without separators matches name with dashes spaces brackets`() = runTest(dispatcher) {
|
||||
val repository = repoWithSearchEntries()
|
||||
val flow = repository.getEntriesFlow()
|
||||
repository.setQuery("ao7")
|
||||
val items = flow.first()
|
||||
// "ao7" is a substring of the normalized "AO-73 (FUNcube-1)" too, so a
|
||||
// fuzzy search legitimately returns both AO-7 (first) and AO-73. The key
|
||||
// guarantee is that AO-7 — unreachable before because of dashes/brackets
|
||||
// — is now found.
|
||||
assertEquals(7530, items.first().catnum)
|
||||
assertEquals(listOf(7530, 39444), items.map { it.catnum })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `query without separators matches name with only dashes`() = runTest(dispatcher) {
|
||||
val repository = repoWithSearchEntries()
|
||||
val flow = repository.getEntriesFlow()
|
||||
repository.setQuery("fo29")
|
||||
assertEquals(listOf(99999), flow.first().map { it.catnum })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `space-separated tokens all must match in any order`() = runTest(dispatcher) {
|
||||
val repository = repoWithSearchEntries()
|
||||
val flow = repository.getEntriesFlow()
|
||||
repository.setQuery("zarya iss")
|
||||
assertEquals(listOf(25544), flow.first().map { it.catnum })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `partial token query matches substring`() = runTest(dispatcher) {
|
||||
val repository = repoWithSearchEntries()
|
||||
val flow = repository.getEntriesFlow()
|
||||
repository.setQuery("funcube")
|
||||
assertEquals(listOf(39444), flow.first().map { it.catnum })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unmatched query returns nothing`() = runTest(dispatcher) {
|
||||
val repository = repoWithSearchEntries()
|
||||
val flow = repository.getEntriesFlow()
|
||||
repository.setQuery("zzzznomatch")
|
||||
assertEquals(emptyList<Int>(), flow.first().map { it.catnum })
|
||||
}
|
||||
|
||||
private fun repoWithSearchEntries(): SelectionRepo {
|
||||
val localSource = FakeLocalSource(
|
||||
entries = listOf(
|
||||
SatItem(25544, "ISS (ZARYA)", false),
|
||||
SatItem(7530, "AO-7 (AMSAT-OSCAR 7)", false),
|
||||
SatItem(39444, "AO-73 (FUNcube-1)", false),
|
||||
SatItem(43803, "JO-97 (BIRDS-3)", false),
|
||||
SatItem(99999, "FO-29", false)
|
||||
)
|
||||
)
|
||||
return SelectionRepo(dispatcher, localSource, FakeSettingsRepo(selectedModes = emptyList()))
|
||||
}
|
||||
|
||||
private class FakeLocalSource(
|
||||
private val entries: List<SatItem>
|
||||
) : ILocalSource {
|
||||
|
||||
+2
-2
@@ -61,8 +61,8 @@ fun MainTheme(isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable
|
||||
SideEffect {
|
||||
val window = (view.context as ComponentActivity).window
|
||||
val insetsController = WindowCompat.getInsetsController(window, view)
|
||||
insetsController.isAppearanceLightStatusBars = false
|
||||
insetsController.isAppearanceLightNavigationBars = false
|
||||
insetsController.isAppearanceLightStatusBars = !isDarkTheme
|
||||
insetsController.isAppearanceLightNavigationBars = !isDarkTheme
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
|
||||
@@ -161,10 +161,13 @@
|
||||
|
||||
<string name="prefs_other_title">Otros</string>
|
||||
<string name="prefs_other_switch_utc">Hora UTC</string>
|
||||
<string name="prefs_other_switch_update">Actualización automática</string>
|
||||
<string name="prefs_other_switch_update">Auto-actualizar</string>
|
||||
<string name="prefs_other_switch_sweep">Animación radar</string>
|
||||
<string name="prefs_other_switch_sensors">Sensores</string>
|
||||
<string name="prefs_other_switch_night_mode">Filtro nocturno</string>
|
||||
<string name="prefs_other_switch_light_theme">Tema claro</string>
|
||||
<string name="prefs_other_compass_offset_az">Desfase de brújula (ácimut)</string>
|
||||
<string name="prefs_other_compass_offset_elev">Desfase de brújula (elevación)</string>
|
||||
|
||||
<string name="prefs_highlight_title">Resaltado de elevación</string>
|
||||
<string name="prefs_highlight_low">Baja < %1$d°</string>
|
||||
|
||||
@@ -161,10 +161,13 @@
|
||||
|
||||
<string name="prefs_other_title">Прочее</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_sweep">Анимация радара</string>
|
||||
<string name="prefs_other_switch_sensors">Сенсоры</string>
|
||||
<string name="prefs_other_switch_night_mode">Ночной фильтр</string>
|
||||
<string name="prefs_other_switch_light_theme">Светлая тема</string>
|
||||
<string name="prefs_other_compass_offset_az">Смещение компаса (азимут)</string>
|
||||
<string name="prefs_other_compass_offset_elev">Смещение компаса (элевация)</string>
|
||||
|
||||
<string name="prefs_highlight_title">Подсветка высоты</string>
|
||||
<string name="prefs_highlight_low">Низко < %1$d°</string>
|
||||
|
||||
@@ -161,10 +161,13 @@
|
||||
|
||||
<string name="prefs_other_title">වෙනත්</string>
|
||||
<string name="prefs_other_switch_utc">UTC වේලාව</string>
|
||||
<string name="prefs_other_switch_update">දත්ත ස්වයං යාවත්කාලීන</string>
|
||||
<string name="prefs_other_switch_sweep">Radar sweep</string>
|
||||
<string name="prefs_other_switch_update">ස්වයං යාවත්කාලීන</string>
|
||||
<string name="prefs_other_switch_sweep">රේඩාර් පරිලෝකනය</string>
|
||||
<string name="prefs_other_switch_sensors">සංවේදක</string>
|
||||
<string name="prefs_other_switch_night_mode">රාත්රී පෙරහන</string>
|
||||
<string name="prefs_other_switch_light_theme">ආලෝක තේමාව</string>
|
||||
<string name="prefs_other_compass_offset_az">මාලිමා අපගමනය (දිගංශය)</string>
|
||||
<string name="prefs_other_compass_offset_elev">මාලිමා අපගමනය (උත්තෝලනය)</string>
|
||||
|
||||
<string name="prefs_highlight_title">උස් උද්දීපනය</string>
|
||||
<string name="prefs_highlight_low">අඩු < %1$d°</string>
|
||||
|
||||
@@ -193,10 +193,13 @@
|
||||
|
||||
<string name="prefs_other_title">Diğer</string>
|
||||
<string name="prefs_other_switch_utc">UTC saati</string>
|
||||
<string name="prefs_other_switch_update">Veriyi otomatik güncelle</string>
|
||||
<string name="prefs_other_switch_update">Otomatik güncelle</string>
|
||||
<string name="prefs_other_switch_sweep">Radar taraması</string>
|
||||
<string name="prefs_other_switch_sensors">Sensör</string>
|
||||
<string name="prefs_other_switch_night_mode">Gece filtresi</string>
|
||||
<string name="prefs_other_switch_light_theme">Açık tema</string>
|
||||
<string name="prefs_other_compass_offset_az">Pusula sapması (azimut)</string>
|
||||
<string name="prefs_other_compass_offset_elev">Pusula sapması (yükseklik açısı)</string>
|
||||
|
||||
<string name="prefs_highlight_title">Elevasyon vurgusu</string>
|
||||
<string name="prefs_highlight_low">Düşük < %1$d°</string>
|
||||
|
||||
@@ -161,10 +161,13 @@
|
||||
|
||||
<string name="prefs_other_title">Інше</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_sweep">Анімація радара</string>
|
||||
<string name="prefs_other_switch_sensors">Сенсори</string>
|
||||
<string name="prefs_other_switch_night_mode">Нічний фільтр</string>
|
||||
<string name="prefs_other_switch_light_theme">Світла тема</string>
|
||||
<string name="prefs_other_compass_offset_az">Зміщення компаса (азимут)</string>
|
||||
<string name="prefs_other_compass_offset_elev">Зміщення компаса (піднесення)</string>
|
||||
|
||||
<string name="prefs_highlight_title">Підсвічування висоти</string>
|
||||
<string name="prefs_highlight_low">Низько < %1$d°</string>
|
||||
|
||||
@@ -219,12 +219,13 @@
|
||||
|
||||
<string name="prefs_other_title">其他</string>
|
||||
<string name="prefs_other_switch_utc">UTC 时间</string>
|
||||
<string name="prefs_other_switch_update">自动更新数据</string>
|
||||
<string name="prefs_other_switch_sweep">雷达扫描动画</string>
|
||||
<string name="prefs_other_switch_sensors">传感器控制雷达</string>
|
||||
<string name="prefs_other_switch_update">自动更新</string>
|
||||
<string name="prefs_other_switch_sweep">雷达扫描</string>
|
||||
<string name="prefs_other_switch_sensors">传感器</string>
|
||||
<string name="prefs_other_switch_night_mode">红色夜间模式</string>
|
||||
<string name="prefs_other_compass_offset">雷达罗盘水平偏移量</string>
|
||||
<string name="prefs_other_compass_offset_elev">雷达罗盘俯仰偏移量</string>
|
||||
<string name="prefs_other_switch_light_theme">浅色主题</string>
|
||||
<string name="prefs_other_compass_offset_az">罗盘偏移量(方位角)</string>
|
||||
<string name="prefs_other_compass_offset_elev">罗盘偏移量(仰角)</string>
|
||||
|
||||
<string name="prefs_highlight_title">仰角高亮</string>
|
||||
<string name="prefs_highlight_low">低 < %1$d°</string>
|
||||
|
||||
@@ -56,14 +56,12 @@
|
||||
\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_message" translatable="false">
|
||||
* Tweaked sunrise/sunset calculations, added tests by atsunatsu (#241)
|
||||
\n\n* Overhauled sources import, added HTTP status by wty2019wty (#242)
|
||||
\nSources now have priority - the top ones define satellite names and transceiver data.
|
||||
\nYou can enable/disable sources and restore defaults via the Satellite data import dialog.
|
||||
\n\n* Localized AMSAT status screen for Chinese by atsunatsu (#245)
|
||||
\n\n* Extracted hardcoded UI strings to resources by TianhengZhuang (#247)
|
||||
\n\n* Added AMSAT satellite status reports submission by atsunatsu (#248)
|
||||
\n\n* Slightly optimized map performance to reduce ANRs and memory consumption. Zoom in to see satellite labels.
|
||||
* Fixed AOS window filter to follow the UTC setting by wty2019wty (#252)
|
||||
\n\n* Applied elevation color thresholds to the Radar by atsunatsu (#253)
|
||||
\n\n* Added fuzzy satellite search ignoring separators by atsunatsu (#255)
|
||||
\n\n* Added light theme suitable for e-ink devices by Ondrej Kolonicny (#256)
|
||||
\n\n* Fixed IC-705 frequency read in dual-radio mode by Michael S (#260)
|
||||
\n\n* Fixed day/night map overlay calculation and display issue by rt-bishop
|
||||
</string>
|
||||
|
||||
<!-- AMSAT Status screen -->
|
||||
@@ -262,12 +260,13 @@
|
||||
|
||||
<string name="prefs_other_title">Other</string>
|
||||
<string name="prefs_other_switch_utc">UTC time</string>
|
||||
<string name="prefs_other_switch_update">Auto-update data</string>
|
||||
<string name="prefs_other_switch_update">Auto-update</string>
|
||||
<string name="prefs_other_switch_sweep">Radar sweep</string>
|
||||
<string name="prefs_other_switch_sensors">Use sensors</string>
|
||||
<string name="prefs_other_switch_sensors">Sensors</string>
|
||||
<string name="prefs_other_switch_night_mode">Night filter</string>
|
||||
<string name="prefs_other_compass_offset">Radar compass offset</string>
|
||||
<string name="prefs_other_compass_offset_elev">Radar compass offset (elev)</string>
|
||||
<string name="prefs_other_switch_light_theme">Light theme</string>
|
||||
<string name="prefs_other_compass_offset_az">Compass offset (azimuth)</string>
|
||||
<string name="prefs_other_compass_offset_elev">Compass offset (elevation)</string>
|
||||
|
||||
<string name="prefs_highlight_title">Elevation highlight</string>
|
||||
<string name="prefs_highlight_low">Low < %1$d°</string>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
* Tweaked sun calculations, added tests by atsunatsu (#241)
|
||||
* Overhauled sources import, added HTTP status by wty2019wty (#242)
|
||||
* Localized AMSAT status screen for Chinese by atsunatsu (#245)
|
||||
* Extracted hardcoded UI strings to resources by TianhengZhuang (#247)
|
||||
* Added AMSAT satellite status reports submission by atsunatsu (#248)
|
||||
* Fixed AOS window filter to follow the UTC setting by wty2019wty (#252)
|
||||
* Applied elevation color thresholds to the Radar by atsunatsu (#253)
|
||||
* Added fuzzy satellite search ignoring separators by atsunatsu (#255)
|
||||
* Added light theme suitable for e-ink devices by Ondrej Kolonicny (#256)
|
||||
* Fixed IC-705 frequency read in dual-radio mode by Michael S (#260)
|
||||
* Fixed day/night map overlay calculation and display issue by rt-bishop
|
||||
@@ -116,8 +116,9 @@ class MapNightOverlay : Overlay() {
|
||||
|
||||
else -> {
|
||||
// Terminator crosses this column — find the crossing pixel by binary search
|
||||
val crossY = findCrossingY(proj, x, 0, h - 1, sinSunLat, cosSunLat, cosLonDiff)
|
||||
if (dotTop < 0) {
|
||||
val isNightAtTop = dotTop < 0
|
||||
val crossY = findCrossingY(proj, x, 0, h - 1, sinSunLat, cosSunLat, cosLonDiff, isNightAtTop)
|
||||
if (isNightAtTop) {
|
||||
// Night at top, day at bottom
|
||||
rect.set(x.toFloat(), 0f, (x + stepPx).toFloat(), crossY.toFloat())
|
||||
canvas.drawRect(rect, nightPaint)
|
||||
@@ -134,7 +135,11 @@ class MapNightOverlay : Overlay() {
|
||||
|
||||
/**
|
||||
* Binary-search for the pixel row where the day/night boundary crosses column [x].
|
||||
* [yTop] is in day, [yBot] is in night (or vice versa).
|
||||
*
|
||||
* [isNightAtTop] states which side [yTop] is on. Both polarities occur: the pole nearest
|
||||
* the sub-solar point is lit, so the terminator runs day-over-night while the sub-solar
|
||||
* latitude is positive and night-over-day once it turns negative after the September
|
||||
* equinox. Anchoring lo to the top pixel's side keeps the search valid either way.
|
||||
*/
|
||||
private fun findCrossingY(
|
||||
proj: org.osmdroid.views.Projection,
|
||||
@@ -143,7 +148,8 @@ class MapNightOverlay : Overlay() {
|
||||
yBot: Int,
|
||||
sinSunLat: Double,
|
||||
cosSunLat: Double,
|
||||
cosLonDiff: Double
|
||||
cosLonDiff: Double,
|
||||
isNightAtTop: Boolean
|
||||
): Int {
|
||||
var lo = yTop
|
||||
var hi = yBot
|
||||
@@ -152,7 +158,8 @@ class MapNightOverlay : Overlay() {
|
||||
val geo = proj.fromPixels(x, mid, reusableGeoPoint) ?: return mid
|
||||
val latRad = Math.toRadians(geo.latitude)
|
||||
val dot = sin(latRad) * sinSunLat + cos(latRad) * cosSunLat * cosLonDiff
|
||||
if (dot < 0) hi = mid else lo = mid
|
||||
// Keep lo on the top pixel's side of the terminator and hi on the opposite side
|
||||
if ((dot < 0) == isNightAtTop) lo = mid else hi = mid
|
||||
}
|
||||
return (lo + hi) / 2
|
||||
}
|
||||
|
||||
@@ -122,6 +122,39 @@ private val moonIconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
android.graphics.PorterDuffColorFilter("#E0E0E0".toColorInt(), android.graphics.PorterDuff.Mode.SRC_IN)
|
||||
}
|
||||
|
||||
/** Accent used for the station marker; recolored per theme so it stays visible on a light map. */
|
||||
private var mapAccentColor = "#FFE082".toColorInt()
|
||||
|
||||
/** The dark-map filter (grayscale + invert) is only applied when NOT in light theme; cached to avoid rebuilds. */
|
||||
private val darkTileFilter by lazy { createColorFilter() }
|
||||
|
||||
/**
|
||||
* Recolor the shared overlay paints for the current theme. The light OSM tiles are white, so the
|
||||
* dark-theme amber/grey overlays would vanish; light theme swaps them for dark, high-contrast colors.
|
||||
* Must run before the set*() overlay builders on each frame, as several icons cache the paint color.
|
||||
*/
|
||||
private fun applyMapColors(isLightUi: Boolean) {
|
||||
if (isLightUi) {
|
||||
mapAccentColor = "#715C0C".toColorInt() // dark amber (matches app lightScheme primary)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MapDestination() {
|
||||
val context = LocalContext.current
|
||||
@@ -177,6 +210,8 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView:
|
||||
ElevatedCard(modifier = Modifier.weight(1f)) {
|
||||
Box(contentAlignment = Alignment.BottomCenter) {
|
||||
AndroidView({ mapView }) { view ->
|
||||
applyMapColors(uiState.isLightUi)
|
||||
view.overlayManager.tilesOverlay.setColorFilter(if (uiState.isLightUi) null else darkTileFilter)
|
||||
uiState.stationPosition?.let { setStationPosition(it, view) }
|
||||
uiState.track?.let { setSatelliteTrack(it, view) }
|
||||
uiState.footprint?.let { setFootprint(it, view) }
|
||||
@@ -288,7 +323,9 @@ private fun setStationPosition(stationPos: GeoPos, mapView: MapView) {
|
||||
mapView.overlays[OVERLAY_STATION] = Marker(mapView).apply {
|
||||
setInfoWindow(null)
|
||||
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 {
|
||||
setTint(mapAccentColor)
|
||||
}
|
||||
position = GeoPoint(stationPos.latitude, stationPos.longitude)
|
||||
}
|
||||
}
|
||||
@@ -585,7 +622,7 @@ private fun rememberMapViewWithLifecycle(): MapView {
|
||||
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
|
||||
overlayManager.tilesOverlay.loadingBackgroundColor = Color.TRANSPARENT
|
||||
overlayManager.tilesOverlay.loadingLineColor = Color.TRANSPARENT
|
||||
overlayManager.tilesOverlay.setColorFilter(createColorFilter())
|
||||
// Tile color filter (dark inversion vs light) is applied per-theme in the AndroidView update block.
|
||||
setScrollableAreaLimitLatitude(maxLat, minLat, 0)
|
||||
overlays.addAll(Array(OVERLAY_COUNT) { FolderOverlay() })
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class MapViewModel(
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
settingsRepo.otherSettings.collectLatest { settings ->
|
||||
_uiState.update { it.copy(isUtc = settings.stateOfUtc) }
|
||||
_uiState.update { it.copy(isUtc = settings.stateOfUtc, isLightUi = settings.stateOfLightTheme) }
|
||||
}
|
||||
}
|
||||
val (selectedCatNum, _) = satelliteRepo.selectedPass.value
|
||||
|
||||
+38
-25
@@ -495,17 +495,31 @@ private fun OtherCard(settings: OtherSettings, onAction: (SettingsAction) -> Uni
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
// Display preferences: UTC clock + night filter
|
||||
// Data preferences: auto-update + UTC clock
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
SwitchTile(R.string.prefs_other_switch_update, settings.stateOfAutoUpdate) {
|
||||
onAction(SettingsAction.ToggleUpdate(it))
|
||||
}
|
||||
SwitchTile(R.string.prefs_other_switch_utc, settings.stateOfUtc) {
|
||||
onAction(SettingsAction.ToggleUtc(it))
|
||||
}
|
||||
SwitchTile(R.string.prefs_other_switch_night_mode, settings.stateOfNightMode) {
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
// Appearance: light theme + night filter
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
SwitchTile(R.string.prefs_other_switch_light_theme, settings.stateOfLightTheme) {
|
||||
onAction(SettingsAction.ToggleLightTheme(it))
|
||||
}
|
||||
SwitchTile(
|
||||
R.string.prefs_other_switch_night_mode,
|
||||
settings.stateOfNightMode,
|
||||
enabled = !settings.stateOfLightTheme
|
||||
) {
|
||||
onAction(SettingsAction.ToggleNightMode(it))
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
// Radar behavior: sweep animation + sensor control
|
||||
// Radar behavior: sweep animation + sensors, followed by the compass offset sliders
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
SwitchTile(R.string.prefs_other_switch_sweep, settings.stateOfSweep) {
|
||||
onAction(SettingsAction.ToggleSweep(it))
|
||||
@@ -515,14 +529,9 @@ private fun OtherCard(settings: OtherSettings, onAction: (SettingsAction) -> Uni
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
// Data management (full width)
|
||||
SwitchRow(R.string.prefs_other_switch_update, settings.stateOfAutoUpdate) {
|
||||
onAction(SettingsAction.ToggleUpdate(it))
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
// Compass calibration sliders at the bottom
|
||||
CompassOffsetRow(
|
||||
labelResId = R.string.prefs_other_compass_offset,
|
||||
labelResId = R.string.prefs_other_compass_offset_az,
|
||||
value = settings.radarCompassOffset,
|
||||
range = -180f..180f
|
||||
) { onAction(SettingsAction.SetRadarCompassOffset(it)) }
|
||||
@@ -549,7 +558,14 @@ private fun CompassOffsetRow(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(id = labelResId))
|
||||
Text(
|
||||
text = stringResource(id = labelResId),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 4.dp)
|
||||
.infiniteMarquee(),
|
||||
maxLines = 1
|
||||
)
|
||||
Text(text = "${value.toInt()}°")
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
@@ -561,19 +577,12 @@ private fun CompassOffsetRow(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SwitchRow(labelResId: Int, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(id = labelResId))
|
||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.SwitchTile(labelResId: Int, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {
|
||||
private fun RowScope.SwitchTile(
|
||||
labelResId: Int,
|
||||
checked: Boolean,
|
||||
enabled: Boolean = true,
|
||||
onCheckedChange: (Boolean) -> Unit
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -581,9 +590,13 @@ private fun RowScope.SwitchTile(labelResId: Int, checked: Boolean, onCheckedChan
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = labelResId),
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 4.dp)
|
||||
.infiniteMarquee(),
|
||||
maxLines = 1
|
||||
)
|
||||
Switch(checked = checked, onCheckedChange = onCheckedChange)
|
||||
Switch(checked = checked, enabled = enabled, onCheckedChange = onCheckedChange)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -127,7 +127,10 @@ class SettingsViewModel(
|
||||
is SettingsAction.ToggleUpdate -> settingsRepo.updateOtherSettings { it.copy(stateOfAutoUpdate = action.value) }
|
||||
is SettingsAction.ToggleSweep -> settingsRepo.updateOtherSettings { it.copy(stateOfSweep = action.value) }
|
||||
is SettingsAction.ToggleSensor -> settingsRepo.updateOtherSettings { it.copy(stateOfSensors = action.value) }
|
||||
is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings { it.copy(stateOfLightTheme = action.value) }
|
||||
is SettingsAction.ToggleLightTheme -> settingsRepo.updateOtherSettings {
|
||||
// Night filter (red-only overlay) is incompatible with the light theme, so turn it off.
|
||||
it.copy(stateOfLightTheme = action.value, stateOfNightMode = if (action.value) false else it.stateOfNightMode)
|
||||
}
|
||||
is SettingsAction.ToggleNightMode -> settingsRepo.updateOtherSettings { it.copy(stateOfNightMode = action.value) }
|
||||
is SettingsAction.SetRadarCompassOffset -> settingsRepo.updateOtherSettings { it.copy(radarCompassOffset = action.value) }
|
||||
is SettingsAction.SetRadarCompassOffsetElev -> settingsRepo.updateOtherSettings { it.copy(radarCompassOffsetElev = action.value) }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[versions]
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
appVersionCode = "447"
|
||||
appVersionCode = "448"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
appVersionName = "4.4.7"
|
||||
appVersionName = "4.4.8"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
compileSdk = "37"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
@@ -12,16 +12,16 @@ jdkVersion = "21"
|
||||
#noinspection UnusedVersionCatalogEntry
|
||||
packageName = "com.rtbishop.look4sat"
|
||||
|
||||
android-gradle-plugin = "9.4.0"
|
||||
android-gradle-plugin = "9.4.1"
|
||||
|
||||
androidx-core-ktx = "1.19.0"
|
||||
androidx-core-ktx = "1.19.1"
|
||||
androidx-core-splashscreen = "1.2.0"
|
||||
androidx-room = "2.8.5"
|
||||
|
||||
compose-bom = "2026.09.00"
|
||||
compose-activity = "1.13.0"
|
||||
compose-lifecycle = "2.11.0"
|
||||
compose-navigation3 = "1.1.7"
|
||||
compose-navigation3 = "1.2.0"
|
||||
|
||||
google-ksp = "2.3.11"
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#Sat Sep 12 11:29:59 BST 2026
|
||||
#Sun Sep 27 11:54:01 BST 2026
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Reference in new issue
Block a user