diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt index 0c404ad7..3dd53bd5 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepo.kt @@ -23,7 +23,6 @@ import com.rtbishop.look4sat.core.domain.repository.IDatabaseRepo import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo import com.rtbishop.look4sat.core.domain.source.ILocalSource import com.rtbishop.look4sat.core.domain.source.IRemoteSource -import com.rtbishop.look4sat.core.domain.source.Sources import com.rtbishop.look4sat.core.domain.utility.DataParser import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.async @@ -40,7 +39,6 @@ class DatabaseRepo( private val settingsRepo: ISettingsRepo ) : IDatabaseRepo { - private val customSourceType = "Other" override suspend fun updateTLEFromFile(uri: String): Int = withContext(dispatcher) { var importedCount = 0 @@ -65,18 +63,13 @@ class DatabaseRepo( } override suspend fun updateFromRemote() = withContext(dispatcher) { - val dataSourcesSettings = settingsRepo.dataSourcesSettings.value - val tleUrls = buildMap { - putAll(Sources.satelliteDataUrls) - if (dataSourcesSettings.useCustomTLE) put(customSourceType, dataSourcesSettings.tleUrl) - }.filterValues { it.isNotBlank() } - val radioUrls = buildMap { - putAll(Sources.transceiversDataUrls) - if (dataSourcesSettings.useCustomTransceivers) put(customSourceType, dataSourcesSettings.transceiversUrl) - }.filterValues { it.isNotBlank() } + val settings = settingsRepo.dataSourcesSettings.value + fun normalizeUrl(url: String) = if (url.startsWith("http")) url else "https://$url" + val tleUrls = settings.satelliteUrls.filter { it.isNotBlank() }.map(::normalizeUrl) + val radioUrls = settings.transceiversUrls.filter { it.isNotBlank() }.map(::normalizeUrl) // launch all network requests concurrently - val tleJobs = tleUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } } - val radioJobs = radioUrls.values.map { url -> async { url to remoteSource.getNetworkStream(url) } } + val tleJobs = tleUrls.map { url -> async { url to remoteSource.getNetworkStream(url) } } + val radioJobs = radioUrls.map { url -> async { url to remoteSource.getNetworkStream(url) } } // parse fetched data concurrently val importedEntries = tleJobs.awaitAll().flatMap { (url, stream) -> stream?.let { parseSatelliteStream(url, unwrapIfZipped(url, it)) }.orEmpty() diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt index 5abc6d63..07542265 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/repository/SettingsRepo.kt @@ -31,6 +31,7 @@ import com.rtbishop.look4sat.core.domain.model.RCSettings import com.rtbishop.look4sat.core.domain.model.RadioControlSettings import com.rtbishop.look4sat.core.domain.predict.GeoPos import com.rtbishop.look4sat.core.domain.repository.ISettingsRepo +import com.rtbishop.look4sat.core.domain.source.Sources import com.rtbishop.look4sat.core.domain.utility.positionToQth import com.rtbishop.look4sat.core.domain.utility.qthToPosition import com.rtbishop.look4sat.core.domain.utility.round @@ -89,11 +90,10 @@ class SettingsRepo( private val keyHighElevation = "highElevation" private val keyRadarCompassOffset = "radarCompassOffset" private val keyRadarCompassOffsetElev = "radarCompassOffsetElev" - private val keyUseCustomTle = "useCustomTle" - private val keyUseCustomTransceivers = "useCustomTransceivers" - private val keyTleUrl = "tleUrl" - private val keyTransceiversUrl = "transceiversUrl" + private val keySatelliteUrls = "satelliteUrls" + private val keyTransceiversUrls = "transceiversUrls" private val separatorComma = "," + private val separatorUrl = "\n" //region # Satellites selection settings private val _satelliteSelection = MutableStateFlow(getSelectedIds()) @@ -365,19 +365,19 @@ class SettingsRepo( override fun updateDataSourcesSettings(settings: DataSourcesSettings) { preferences.edit { - putBoolean(keyUseCustomTle, settings.useCustomTLE) - putBoolean(keyUseCustomTransceivers, settings.useCustomTransceivers) - putString(keyTleUrl, settings.tleUrl) - putString(keyTransceiversUrl, settings.transceiversUrl) + putString(keySatelliteUrls, settings.satelliteUrls.joinToString(separatorUrl)) + putString(keyTransceiversUrls, settings.transceiversUrls.joinToString(separatorUrl)) } _dataSourcesSettings.value = settings } private fun getDataSourcesSettings(): DataSourcesSettings = DataSourcesSettings( - useCustomTLE = preferences.getBoolean(keyUseCustomTle, false), - useCustomTransceivers = preferences.getBoolean(keyUseCustomTransceivers, false), - tleUrl = preferences.getString(keyTleUrl, "https://example.com/tle.txt") ?: "", - transceiversUrl = preferences.getString(keyTransceiversUrl, "https://example.com/radio.json") ?: "" + satelliteUrls = preferences.getString(keySatelliteUrls, null) + ?.split(separatorUrl)?.filter { it.isNotBlank() } + ?: Sources.satelliteDataUrls, + transceiversUrls = preferences.getString(keyTransceiversUrls, null) + ?.split(separatorUrl)?.filter { it.isNotBlank() } + ?: Sources.transceiversDataUrls ) //endregion diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt index e8b051fe..6041173b 100644 --- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/DatabaseRepoTest.kt @@ -90,10 +90,8 @@ class DatabaseRepoTest { } val settingsRepo = FakeSettingsRepo( dataSources = DataSourcesSettings( - useCustomTLE = true, - useCustomTransceivers = false, - tleUrl = customCsvUrl, - transceiversUrl = "" + satelliteUrls = listOf(customCsvUrl), + transceiversUrls = emptyList() ) ) val repository = DatabaseRepo(dispatcher, dataParser, localSource, remoteSource, settingsRepo) @@ -221,9 +219,7 @@ private class FakeSettingsRepo(dataSources: DataSourcesSettings = defaultDataSou private fun defaultDataSourcesSettings(): DataSourcesSettings { return DataSourcesSettings( - useCustomTLE = false, - useCustomTransceivers = false, - tleUrl = "", - transceiversUrl = "" + satelliteUrls = emptyList(), + transceiversUrls = emptyList() ) } diff --git a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt index 8bb71983..ebd521d1 100644 --- a/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt +++ b/core/data/src/test/java/com/rtbishop/look4sat/core/data/repository/SelectionRepoTest.kt @@ -135,7 +135,7 @@ class SelectionRepoTest { ) override val dataSourcesSettings: StateFlow = MutableStateFlow( - DataSourcesSettings(false, false, "", "") + DataSourcesSettings(emptyList(), emptyList()) ) override val radioControlSettings: StateFlow = MutableStateFlow( diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt index 281e0c86..9fd68bd3 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/model/Settings.kt @@ -67,10 +67,8 @@ data class OtherSettings( ) data class DataSourcesSettings( - val useCustomTLE: Boolean, - val useCustomTransceivers: Boolean, - val tleUrl: String, - val transceiversUrl: String + val satelliteUrls: List, + val transceiversUrls: List ) data class RadioControlSettings( diff --git a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt index 77e150a4..6236f072 100644 --- a/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt +++ b/core/domain/src/main/java/com/rtbishop/look4sat/core/domain/source/Sources.kt @@ -18,18 +18,17 @@ package com.rtbishop.look4sat.core.domain.source object Sources { - val satelliteDataUrls = mapOf( - "CelesTrak" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", - "AMSAT" to "https://amsat.org/tle/current/nasabare.txt", - "McCants Classified" to "https://www.mmccants.org/tles/classfd.zip", - "McCants Integrated" to "https://www.mmccants.org/tles/inttles.zip", - "R4UAB" to "https://r4uab.ru/satonline.txt", - "SatNOGS" to "https://db.satnogs.org/api/tle/?format=3le", - "ARISS" to "https://live.ariss.org/iss.txt", - "Other" to "" // key for sats filter + val satelliteDataUrls = listOf( + "celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", + "db.satnogs.org/api/tle/?format=3le", + "amsat.org/tle/current/nasabare.txt", + "mmccants.org/tles/classfd.zip", + "r4uab.ru/satonline.txt", + "live.ariss.org/iss.txt" ) - val transceiversDataUrls = mapOf( - "SatNOGS" to "https://db.satnogs.org/api/transmitters/?format=json&status=active" + val transceiversDataUrls = listOf( + "db.satnogs.org/api/transmitters/?format=json&status=active", + "r4uab.ru/transmitters.json" ) val satelliteModes = listOf( "4FSK", "64-QAM", "AFSK", "AFSK TUBiX10", "AHRPT", "AM", "APT", "ASK", "BPSK", diff --git a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt index b9ffe81e..d8118566 100644 --- a/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt +++ b/core/presentation/src/main/java/com/rtbishop/look4sat/core/presentation/Components.kt @@ -239,8 +239,13 @@ fun CardButton(onClick: () -> Unit, text: String, modifier: Modifier = Modifier) } @Composable -fun IconCard(action: () -> Unit, resId: Int, modifier: Modifier = Modifier, enabled: Boolean = true) { - ElevatedCard(modifier = Modifier.size(48.dp), enabled = enabled, onClick = action) { +fun IconCard( + action: () -> Unit, resId: Int, modifier: Modifier = Modifier, + enabled: Boolean = true, containerColor: Color = Color.Unspecified +) { + val colors = if (containerColor == Color.Unspecified) CardDefaults.elevatedCardColors() + else CardDefaults.elevatedCardColors(containerColor = containerColor) + ElevatedCard(modifier = Modifier.size(48.dp), enabled = enabled, onClick = action, colors = colors) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Icon(painter = painterResource(resId), contentDescription = null, modifier = modifier) } diff --git a/core/presentation/src/main/res/drawable/ic_add.xml b/core/presentation/src/main/res/drawable/ic_add.xml new file mode 100644 index 00000000..1b016dcf --- /dev/null +++ b/core/presentation/src/main/res/drawable/ic_add.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml index 3d6250d2..5ebb70f3 100644 --- a/core/presentation/src/main/res/values/strings.xml +++ b/core/presentation/src/main/res/values/strings.xml @@ -172,10 +172,7 @@ No satellites imported. Select a valid TLE/3LE (.txt) or OMM (.csv) file. No transceivers imported. Select a valid SatNOGS (.json) file. - Custom sources - TLE URL - Transceivers URL - URL (HTTPS) + Data sources Data output Network diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt index 43baaf24..38e7418b 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsDialog.kt @@ -17,26 +17,39 @@ */ package com.rtbishop.look4sat.feature.settings +import androidx.compose.animation.core.spring import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -46,6 +59,7 @@ import androidx.compose.ui.unit.sp import com.rtbishop.look4sat.core.domain.model.RCSettings import com.rtbishop.look4sat.core.domain.model.RadioControlSettings import com.rtbishop.look4sat.core.presentation.CardButton +import com.rtbishop.look4sat.core.presentation.IconCard import com.rtbishop.look4sat.core.presentation.LocalSpacing import com.rtbishop.look4sat.core.presentation.MainTheme import com.rtbishop.look4sat.core.presentation.R @@ -129,133 +143,144 @@ fun LocatorDialog(qthLocator: String, dismiss: () -> Unit, save: (String) -> Uni private fun TransceiversDialogPreview() { MainTheme { DataSourcesDialog( - useCustomTle = true, - useCustomTransceivers = true, - tleUrl = "https://example.com/tle.txt", - transceiversUrl = "https://example.com/tx.json", - requestCustomSourcesPermission = { onGranted, _ -> onGranted() }, + satelliteUrls = listOf( + "celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv", + "amsat.org/tle/current/nasabare.txt" + ), + transceiversUrls = listOf( + "db.satnogs.org/api/transmitters/?format=json&status=active" + ), onImportTle = {}, onImportTransceivers = {}, onDismiss = {}, - onSave = { _, _, _, _ -> } + onSave = { _, _ -> } ) } } @Composable fun DataSourcesDialog( - useCustomTle: Boolean, - useCustomTransceivers: Boolean, - tleUrl: String, - transceiversUrl: String, - requestCustomSourcesPermission: (onGranted: () -> Unit, onDenied: () -> Unit) -> Unit, + satelliteUrls: List, + transceiversUrls: List, onImportTle: () -> Unit, onImportTransceivers: () -> Unit, onDismiss: () -> Unit, - onSave: (Boolean, Boolean, String, String) -> Unit + onSave: (List, List) -> Unit ) { val padding = LocalSpacing.current.large - val isEnabledCustomTle = rememberSaveable { mutableStateOf(useCustomTle) } - val isEnabledCustomTransceivers = rememberSaveable { mutableStateOf(useCustomTransceivers) } - val urlTle = rememberSaveable { mutableStateOf(tleUrl) } - val urlTransceivers = rememberSaveable { mutableStateOf(transceiversUrl) } - val onAccept = { - onSave( - isEnabledCustomTle.value, - isEnabledCustomTransceivers.value, - urlTle.value, - urlTransceivers.value - ) - onDismiss() + // Use stable Long IDs to avoid key collisions (e.g. multiple empty "" entries). + val nextId = remember { mutableLongStateOf((satelliteUrls.size + transceiversUrls.size).toLong()) } + val satUrls = remember { + satelliteUrls.mapIndexed { i, url -> i.toLong() to url }.toMutableStateList() } - val onCancel = { onDismiss() } + val txUrls = remember { + transceiversUrls.mapIndexed { i, url -> (satelliteUrls.size + i).toLong() to url }.toMutableStateList() + } + val onAccept = { onSave(satUrls.map { it.second }, txUrls.map { it.second }); onDismiss() } ConfirmDialog( title = stringResource(id = R.string.prefs_data_sources_title), - onCancel = onCancel, + onCancel = onDismiss, onAccept = onAccept, ) { - Column(modifier = Modifier.padding(horizontal = padding)) { - Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { - CardButton( - onClick = { - onImportTle() - onDismiss() - }, - text = "TLE/3LE (.txt)\nOMM (.csv)", - modifier = Modifier.weight(1f) - ) - CardButton( - onClick = { - onImportTransceivers() - onDismiss() - }, - text = "Transceivers\nSatNOGS (.json)", - modifier = Modifier.weight(1f) - ) + LazyColumn( + modifier = Modifier + .fillMaxHeight(0.84f) + .padding(horizontal = padding), + verticalArrangement = Arrangement.spacedBy(4.dp), + contentPadding = PaddingValues(vertical = 6.dp) + ) { + item { + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.fillMaxWidth() + ) { + CardButton( + onClick = { onImportTle(); onDismiss() }, + text = "TLE/3LE (.txt)\nOMM (.csv)", + modifier = Modifier.weight(1f) + ) + CardButton( + onClick = { onImportTransceivers(); onDismiss() }, + text = "Transceivers\nSatNOGS (.json)", + modifier = Modifier.weight(1f) + ) + } } - Spacer(modifier = Modifier.height(6.dp)) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(id = R.string.prefs_data_sources_tle_switch)) - Switch( - checked = isEnabledCustomTle.value, - onCheckedChange = { enabled -> - if (!enabled) { - isEnabledCustomTle.value = false - } else { - requestCustomSourcesPermission( - { isEnabledCustomTle.value = true }, - { isEnabledCustomTle.value = false } - ) - } - } - ) - } - OutlinedTextField( - value = urlTle.value, - onValueChange = { urlTle.value = it }, - label = { Text(text = stringResource(id = R.string.prefs_data_sources_url_title)) }, - singleLine = true, - modifier = Modifier.fillMaxWidth(), - enabled = isEnabledCustomTle.value, + sourceSection( + sectionKey = "sat", + label = "Satellites data", + urls = satUrls, + onAdd = { satUrls.add(nextId.longValue++ to "") }, + onMoveUp = { i -> if (i > 0) satUrls.add(i - 1, satUrls.removeAt(i)) }, + onRemove = { i -> satUrls.removeAt(i) }, + onUrlChange = { i, v -> satUrls[i] = satUrls[i].first to v } ) - Spacer(modifier = Modifier.height(6.dp)) - Row( - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text(text = stringResource(id = R.string.prefs_data_sources_transceivers_switch)) - Switch( - checked = isEnabledCustomTransceivers.value, - onCheckedChange = { enabled -> - if (!enabled) { - isEnabledCustomTransceivers.value = false - } else { - requestCustomSourcesPermission( - { isEnabledCustomTransceivers.value = true }, - { isEnabledCustomTransceivers.value = false } - ) - } - } - ) - } - OutlinedTextField( - value = urlTransceivers.value, - onValueChange = { urlTransceivers.value = it }, - label = { Text(text = stringResource(id = R.string.prefs_data_sources_url_title)) }, - singleLine = true, - modifier = Modifier.fillMaxWidth(), - enabled = isEnabledCustomTransceivers.value, + sourceSection( + sectionKey = "tx", + label = "Transceivers data", + urls = txUrls, + onAdd = { txUrls.add(nextId.longValue++ to "") }, + onMoveUp = { i -> if (i > 0) txUrls.add(i - 1, txUrls.removeAt(i)) }, + onRemove = { i -> txUrls.removeAt(i) }, + onUrlChange = { i, v -> txUrls[i] = txUrls[i].first to v } ) - Spacer(modifier = Modifier.height(12.dp)) } } } +private fun LazyListScope.sourceSection( + sectionKey: String, + label: String, + urls: List>, + onAdd: () -> Unit, + onMoveUp: (Int) -> Unit, + onRemove: (Int) -> Unit, + onUrlChange: (Int, String) -> Unit +) { + item { + Spacer(modifier = Modifier.height(6.dp)) + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) { + Text( + text = label, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.weight(1f) + ) + IconCard(action = onAdd, resId = R.drawable.ic_add, containerColor = MaterialTheme.colorScheme.surfaceVariant) + } + } + itemsIndexed(urls, key = { _, entry -> "$sectionKey-${entry.first}" }) { index, (_, url) -> + val enabledTint = MaterialTheme.colorScheme.onSurfaceVariant + OutlinedTextField( + value = url, + onValueChange = { onUrlChange(index, it) }, + label = { Text("Source URL") }, + leadingIcon = { + IconButton(onClick = { onMoveUp(index) }) { + Icon( + painter = painterResource(R.drawable.ic_arrow), + contentDescription = null, + tint = if (index > 0) enabledTint else enabledTint.copy(alpha = 0.32f), + modifier = Modifier.rotate(270f) + ) + } + }, + trailingIcon = { + IconButton(onClick = { onRemove(index) }) { + Icon( + painter = painterResource(R.drawable.ic_delete), + contentDescription = null + ) + } + }, + singleLine = true, + modifier = Modifier + .fillMaxWidth() + .animateItem(fadeInSpec = spring(), fadeOutSpec = spring()) + ) + } +} + @Preview(showBackground = true) @Composable fun PreviewNetworkOutputDialog() { diff --git a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt index 63c3cf0f..3f828b21 100644 --- a/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/java/com/rtbishop/look4sat/feature/settings/SettingsScreen.kt @@ -64,6 +64,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import com.rtbishop.look4sat.core.domain.model.DataSourcesSettings import com.rtbishop.look4sat.core.domain.model.OtherSettings import com.rtbishop.look4sat.core.domain.predict.GeoPos import com.rtbishop.look4sat.core.domain.repository.IContainerProvider @@ -93,22 +94,10 @@ fun SettingsDestination() { @Composable private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> Unit) { val dialogs = rememberDialogVisibility() - val pendingCustomSourcesGrant = remember { mutableStateOf<(() -> Unit)?>(null) } - val pendingCustomSourcesDeny = remember { mutableStateOf<(() -> Unit)?>(null) } val permissions = rememberSettingsPermissions( sendAction = onAction, onBluetoothGranted = { dialogs.bluetooth = true }, - onNetworkGranted = { dialogs.network = true }, - onCustomSourcesPermissionGranted = { - pendingCustomSourcesGrant.value?.invoke() - pendingCustomSourcesGrant.value = null - pendingCustomSourcesDeny.value = null - }, - onCustomSourcesPermissionDenied = { - pendingCustomSourcesDeny.value?.invoke() - pendingCustomSourcesGrant.value = null - pendingCustomSourcesDeny.value = null - } + onNetworkGranted = { dialogs.network = true } ) // Dialogs @@ -129,30 +118,15 @@ private fun SettingsScreen(uiState: SettingsState, onAction: (SettingsAction) -> } if (dialogs.dataSources) { DataSourcesDialog( - useCustomTle = uiState.dataSourcesSettings.useCustomTLE, - useCustomTransceivers = uiState.dataSourcesSettings.useCustomTransceivers, - tleUrl = uiState.dataSourcesSettings.tleUrl, - transceiversUrl = uiState.dataSourcesSettings.transceiversUrl, - requestCustomSourcesPermission = { onGranted, onDenied -> - pendingCustomSourcesGrant.value = onGranted - pendingCustomSourcesDeny.value = onDenied - permissions.launchCustomSourcesPermission() - }, + satelliteUrls = uiState.dataSourcesSettings.satelliteUrls, + transceiversUrls = uiState.dataSourcesSettings.transceiversUrls, onImportTle = { permissions.launchTleImport(); dialogs.dataSources = false }, onImportTransceivers = { permissions.launchTransceiverImport(); dialogs.dataSources = false }, onDismiss = { dialogs.dataSources = false }, - onSave = { useCustomTle, useCustomTransceivers, tleUrl, transceiversUrl -> - val current = uiState.dataSourcesSettings - val newSettings = current.copy( - useCustomTLE = if (!useCustomTle || tleUrl.isNotBlank()) useCustomTle else current.useCustomTLE, - tleUrl = if (!useCustomTle || tleUrl.isNotBlank()) tleUrl else current.tleUrl, - useCustomTransceivers = if (!useCustomTransceivers || transceiversUrl.isNotBlank()) useCustomTransceivers else current.useCustomTransceivers, - transceiversUrl = if (!useCustomTransceivers || transceiversUrl.isNotBlank()) transceiversUrl else current.transceiversUrl - ) - if (newSettings != current) onAction(SettingsAction.UpdateDataSources(newSettings)) - if (newSettings.useCustomTLE || newSettings.useCustomTransceivers) { - onAction(SettingsAction.UpdateFromWeb) - } + onSave = { satUrls, txUrls -> + val newSettings = DataSourcesSettings(satelliteUrls = satUrls, transceiversUrls = txUrls) + if (newSettings != uiState.dataSourcesSettings) onAction(SettingsAction.UpdateDataSources(newSettings)) + onAction(SettingsAction.UpdateFromWeb) } ) } @@ -752,17 +726,14 @@ private class SettingsPermissions( val launchTleImport: () -> Unit, val launchTransceiverImport: () -> Unit, val launchBluetooth: () -> Unit, - val launchNetwork: () -> Unit, - val launchCustomSourcesPermission: () -> Unit + val launchNetwork: () -> Unit ) @Composable private fun rememberSettingsPermissions( sendAction: (SettingsAction) -> Unit, onBluetoothGranted: () -> Unit, - onNetworkGranted: () -> Unit, - onCustomSourcesPermissionGranted: () -> Unit, - onCustomSourcesPermissionDenied: () -> Unit + onNetworkGranted: () -> Unit ): SettingsPermissions { val locationError = stringResource(R.string.prefs_loc_gps_error) val locationRequest = rememberLauncherForActivityResult( @@ -799,15 +770,6 @@ private fun rememberSettingsPermissions( else sendAction(SettingsAction.ShowToast(networkError)) } - val customSourcesRequest = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> - if (granted) { - onCustomSourcesPermissionGranted() - } else { - onCustomSourcesPermissionDenied() - sendAction(SettingsAction.ShowToast(networkError)) - } - } - return remember { SettingsPermissions( launchLocation = { @@ -824,13 +786,6 @@ private fun rememberSettingsPermissions( } else { onNetworkGranted() } - }, - launchCustomSourcesPermission = { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN) { - customSourcesRequest.launch(Manifest.permission.ACCESS_LOCAL_NETWORK) - } else { - onCustomSourcesPermissionGranted() - } } ) } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a8002a4a..fb0ce11b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ -#Sat Jun 27 12:51:12 BST 2026 +#Tue Aug 11 14:16:53 CEST 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME