diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt index 73c042a0..6c18ef7a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt @@ -330,6 +330,40 @@ fun InfoDialog(title: String, text: String, onDismiss: () -> Unit) { } } +@Composable +fun InfoDialog( + title: String, + onDismiss: () -> Unit, + content: @Composable () -> Unit +) { + val padding = LocalSpacing.current.large + + Dialog(onDismissRequest = onDismiss) { + ElevatedCard { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(padding) + ) { + Text( + text = title, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(start = padding, top = padding, end = padding) + ) + content() + Row(modifier = Modifier.padding(start = padding, bottom = padding, end = padding)) { + Spacer(modifier = Modifier.weight(1f)) + CardButton( + onClick = onDismiss, + text = stringResource(id = R.string.btn_cancel) + ) + } + } + } + } +} + @Composable fun hasEnoughHeight(): Boolean { return currentWindowAdaptiveInfo().windowSizeClass.isHeightAtLeastBreakpoint(480) diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsDialog.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsDialog.kt index e47269e4..f13958cf 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsDialog.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsDialog.kt @@ -27,6 +27,18 @@ import androidx.compose.ui.tooling.preview.Preview import com.rtbishop.look4sat.R import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.common.SharedDialog +import com.rtbishop.look4sat.presentation.common.InfoDialog +import com.rtbishop.look4sat.presentation.common.CardButton +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Switch +import androidx.compose.ui.unit.dp +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.rtbishop.look4sat.presentation.LocalSpacing @Preview(showBackground = true) @Composable @@ -71,3 +83,132 @@ fun LocatorDialog(qthLocator: String, dismiss: () -> Unit, save: (String) -> Uni OutlinedTextField(value = locator.value, onValueChange = { locator.value = it }) } } + +@Preview(showBackground = true) +@Composable +private fun ImportDialogPreview() = MainTheme { + ImportDialog( + onImportTle = {}, + onImportTransceivers = {}, + onDismiss = {} + ) +} + +@Composable +fun ImportDialog( + onImportTle: () -> Unit, + onImportTransceivers: () -> Unit, + onDismiss: () -> Unit +) { + val padding = LocalSpacing.current.large + InfoDialog( + title = stringResource(R.string.prefs_data_import), + onDismiss = onDismiss + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier + .padding(horizontal = padding) + .fillMaxWidth() + ) { + CardButton( + onClick = { + onImportTle() + onDismiss() + }, + text = "TLE", + modifier = Modifier.weight(0.7f) + ) + CardButton( + onClick = { + onImportTransceivers() + onDismiss() + }, + text = "Transceivers", + modifier = Modifier.weight(1.3f) + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun TransceiversDialogPreview() { + MainTheme { + DataSourcesDialog( + useCustomTle = true, + useCustomTransceivers = true, + tleUrl = "https://example.com/tle.txt", + transceiversUrl = "https://example.com/tx.json", + dismiss = {}, + onSave = { _, _, _, _ -> } + ) + } +} + +@Composable +fun DataSourcesDialog( + useCustomTle: Boolean, + useCustomTransceivers: Boolean, + tleUrl: String, + transceiversUrl: String, + dismiss: () -> Unit, + onSave: (Boolean, Boolean, String, String) -> 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) + dismiss() + } + val onCancel = { dismiss() } + SharedDialog( + title = stringResource(id = R.string.prefs_data_sources_title), + onCancel = onCancel, + onAccept = onAccept, + ) { + Column(modifier = Modifier.padding(horizontal = padding)) { + 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 = { isEnabledCustomTle.value = it } + ) + } + 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, + ) + 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 = { isEnabledCustomTransceivers.value = it } + ) + } + 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, + ) + } + } +} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt index 878c3c9d..ddc0093d 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt @@ -112,8 +112,11 @@ private fun SettingsScreen(uiState: SettingsState) { } } val contentContract = ActivityResultContracts.GetContent() - val contentRequest = rememberLauncherForActivityResult(contentContract) { uri -> - uri?.let { uiState.sendAction(SettingsAction.UpdateFromFile(uri.toString())) } + val contentRequestForTle = rememberLauncherForActivityResult(contentContract) { uri -> + uri?.let { uiState.sendAction(SettingsAction.UpdateTLEFromFile(uri.toString())) } + } + val contentRequestForTransceivers = rememberLauncherForActivityResult(contentContract) { uri -> + uri?.let { uiState.sendAction(SettingsAction.UpdateTransceiversFromFile(uri.toString())) } } // Position settings @@ -137,10 +140,46 @@ private fun SettingsScreen(uiState: SettingsState) { LocatorDialog(posSettings.stationPos.qthLocator, showLocDialog, setQthPos) } + // Data sources dialog + val dataSourcesDialogState = rememberSaveable { mutableStateOf(false) } + val showDataSourcesDialog = { dataSourcesDialogState.value = true } + val dismissDataSourcesDialog = { dataSourcesDialogState.value = false } + if (dataSourcesDialogState.value) { + DataSourcesDialog( + useCustomTle = uiState.dataSourcesSettings.useCustomTLE, + useCustomTransceivers = uiState.dataSourcesSettings.useCustomTransceivers, + tleUrl = uiState.dataSourcesSettings.tleUrl, + transceiversUrl = uiState.dataSourcesSettings.transceiversUrl, + dismiss = dismissDataSourcesDialog, + onSave = { + useCustomTle, useCustomTransceivers, tleUrl, transceiversUrl -> + if (!useCustomTle || tleUrl.isNotBlank()) { + uiState.sendDataSourcesAction(DataSourcesAction.SetUseCustomTle(useCustomTle)) + uiState.sendDataSourcesAction(DataSourcesAction.SetTleUrl(tleUrl)) + } + if (!useCustomTransceivers || transceiversUrl.isNotBlank()) { + uiState.sendDataSourcesAction(DataSourcesAction.SetUseCustomTransceivers(useCustomTransceivers)) + uiState.sendDataSourcesAction(DataSourcesAction.SetTransceiversUrl(transceiversUrl)) + } + } + ) + } + + // Import dialog + val importDialogState = rememberSaveable { mutableStateOf(false) } + val showImportDialog = { importDialogState.value = true } + val dismissImportDialog = { importDialogState.value = false } + if (importDialogState.value) { + ImportDialog( + onImportTle = { contentRequestForTle.launch("*/*") }, + onImportTransceivers = { contentRequestForTransceivers.launch("*/*") }, + onDismiss = dismissImportDialog + ) + } + // Data settings val dataSettings = uiState.dataSettings val updateFromWeb: () -> Unit = { uiState.sendAction(SettingsAction.UpdateFromWeb) } - val updateFromFile = { contentRequest.launch("*/*") } val clearAllData: () -> Unit = { uiState.sendAction(SettingsAction.ClearAllData) } // RC settings @@ -209,7 +248,7 @@ private fun SettingsScreen(uiState: SettingsState) { item { LocationCard(posSettings, setGpsPos, showPosDialog, showLocDialog, dismissPos, uiState.sendSystemAction) } - item { DataCard(dataSettings, updateFromWeb, updateFromFile, clearAllData) } + item { DataCard(dataSettings, updateFromWeb, clearAllData, showImportDialog, showDataSourcesDialog) } item { NetworkOutputCard(rcSettings, setRotatorState, setRotatorAddress, setRotatorPort) } item { BluetoothOutputCard(rcSettings, setBluetoothState, setBluetoothAddress, setBluetoothFormat) } item { OtherCard(otherSettings, toggleUtc, toggleUpdate, toggleSweep, toggleSensor) } @@ -290,15 +329,16 @@ private fun LocationCard( @Composable private fun DataCardPreview() = MainTheme { val settings = DataSettings(true, 5000, 2500, 0L) - DataCard(settings = settings, {}, {}) {} + DataCard(settings = settings, {}, {},{}) {} } @Composable private fun DataCard( settings: DataSettings, updateFromWeb: () -> Unit, - updateFromFile: () -> Unit, - clearAllData: () -> Unit + clearAllData: () -> Unit, + showImportDialog: () -> Unit, + showDataSourcesDialog: () -> Unit ) { ElevatedCard(modifier = Modifier.fillMaxWidth()) { Column( @@ -320,25 +360,37 @@ private fun DataCard( Text(text = stringResource(R.string.prefs_data_radios, settings.radiosTotal)) } Spacer(modifier = Modifier.height(1.dp)) - Row(horizontalArrangement = Arrangement.SpaceEvenly) { + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.fillMaxWidth() + ) { CardButton( - onClick = { updateFromWeb() }, + onClick = updateFromWeb, text = stringResource(id = R.string.prefs_data_update), modifier = Modifier.weight(1f) ) - Spacer(modifier = Modifier.width(6.dp)) CardButton( - onClick = { updateFromFile() }, + onClick = showImportDialog, text = stringResource(id = R.string.prefs_data_import), modifier = Modifier.weight(1f) ) - Spacer(modifier = Modifier.width(6.dp)) CardButton( - onClick = { clearAllData() }, + onClick = clearAllData, text = stringResource(id = R.string.prefs_data_clear), modifier = Modifier.weight(1f) ) } + Spacer(modifier = Modifier.height(6.dp)) + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.fillMaxWidth() + ) { + CardButton( + onClick = showDataSourcesDialog, + text = stringResource(id = R.string.prefs_data_sources), + modifier = Modifier.weight(1f) + ) + } } } } @@ -633,3 +685,4 @@ private fun BotCard(onClick: () -> Unit, resId: Int, text: String, modifier: Mod } } } + diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsState.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsState.kt index ae6fed54..880d61cf 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsState.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsState.kt @@ -19,6 +19,7 @@ package com.rtbishop.look4sat.presentation.settings import com.rtbishop.look4sat.domain.model.OtherSettings import com.rtbishop.look4sat.domain.model.RCSettings +import com.rtbishop.look4sat.domain.model.DataSourcesSettings import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.predict.OrbitalPass @@ -39,9 +40,11 @@ data class SettingsState( val dataSettings: DataSettings, val otherSettings: OtherSettings, val rcSettings: RCSettings, + val dataSourcesSettings: DataSourcesSettings, val sendAction: (SettingsAction) -> Unit, val sendRCAction: (RCAction) -> Unit, - val sendSystemAction: (SystemAction) -> Unit + val sendSystemAction: (SystemAction) -> Unit, + val sendDataSourcesAction: (DataSourcesAction) -> Unit ) sealed class SettingsAction { @@ -50,7 +53,8 @@ sealed class SettingsAction { data class SetQthPosition(val locator: String) : SettingsAction() data object DismissPosMessages : SettingsAction() data object UpdateFromWeb : SettingsAction() - data class UpdateFromFile(val uri: String) : SettingsAction() + data class UpdateTLEFromFile(val uri: String) : SettingsAction() + data class UpdateTransceiversFromFile(val uri: String) : SettingsAction() data object ClearAllData : SettingsAction() data class ToggleUtc(val value: Boolean) : SettingsAction() data class ToggleUpdate(val value: Boolean) : SettingsAction() @@ -72,3 +76,10 @@ sealed class RCAction { data class SetBluetoothName(val value: String) : RCAction() data class SetBluetoothAddress(val value: String) : RCAction() } + +sealed class DataSourcesAction { + data class SetUseCustomTle(val value: Boolean) : DataSourcesAction() + data class SetUseCustomTransceivers(val value: Boolean) : DataSourcesAction() + data class SetTleUrl(val value: String) : DataSourcesAction() + data class SetTransceiversUrl(val value: String) : DataSourcesAction() +} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt index 5415e7a9..834c4ffc 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsViewModel.kt @@ -24,6 +24,7 @@ import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import com.rtbishop.look4sat.MainApplication import com.rtbishop.look4sat.R +import com.rtbishop.look4sat.domain.model.DataSourcesSettings import com.rtbishop.look4sat.domain.predict.OrbitalPass import com.rtbishop.look4sat.domain.repository.IDatabaseRepo import com.rtbishop.look4sat.domain.repository.ISatelliteRepo @@ -51,6 +52,7 @@ class SettingsViewModel( private val defaultPosSettings = PositionSettings(false, settingsRepo.stationPosition.value, 0) private val defaultDataSettings = DataSettings(false, 0, 0, 0L) + private val defaultDataSourcesSettings = DataSourcesSettings(false, false, settingsRepo.dataSourcesSettings.value.tleUrl, settingsRepo.dataSourcesSettings.value.transceiversUrl) private val _uiState = MutableStateFlow( SettingsState( nextTime = "00:00:00", @@ -61,9 +63,11 @@ class SettingsViewModel( dataSettings = defaultDataSettings, otherSettings = settingsRepo.otherSettings.value, rcSettings = settingsRepo.rcSettings.value, + dataSourcesSettings = defaultDataSourcesSettings, sendAction = ::handleAction, sendRCAction = ::handleAction, - sendSystemAction = ::handleAction + sendSystemAction = ::handleAction, + sendDataSourcesAction = ::handleAction ) ) private var processing: Job? = null @@ -113,6 +117,13 @@ class SettingsViewModel( _uiState.update { it.copy(otherSettings = settings) } } } + viewModelScope.launch { + settingsRepo.dataSourcesSettings.collect { settings -> + _uiState.update { + it.copy(dataSourcesSettings = settings) + } + } + } } private fun handleAction(action: SettingsAction) { @@ -122,7 +133,8 @@ class SettingsViewModel( is SettingsAction.SetQthPosition -> setQthPosition(action.locator) SettingsAction.DismissPosMessages -> dismissPosMessage() SettingsAction.UpdateFromWeb -> updateFromWeb() - is SettingsAction.UpdateFromFile -> updateFromFile(action.uri) + is SettingsAction.UpdateTLEFromFile -> updateTLEFromFile(action.uri) + is SettingsAction.UpdateTransceiversFromFile -> updateTransceiversFromFile(action.uri) SettingsAction.ClearAllData -> clearAllData() is SettingsAction.ToggleUtc -> settingsRepo.setStateOfUtc(action.value) is SettingsAction.ToggleUpdate -> settingsRepo.setStateOfAutoUpdate(action.value) @@ -150,6 +162,15 @@ class SettingsViewModel( } } + private fun handleAction(action: DataSourcesAction) { + when (action) { + is DataSourcesAction.SetUseCustomTle -> settingsRepo.setUseCustomTle(action.value) + is DataSourcesAction.SetUseCustomTransceivers -> settingsRepo.setUseCustomTransceivers(action.value) + is DataSourcesAction.SetTleUrl -> settingsRepo.setTleUrl(action.value) + is DataSourcesAction.SetTransceiversUrl -> settingsRepo.setTransceiversUrl(action.value) + } + } + private fun setGpsPosition() { if (settingsRepo.setStationPosition()) { val messageResId = R.string.prefs_loc_success @@ -217,11 +238,23 @@ class SettingsViewModel( } } - private fun updateFromFile(uri: String) = viewModelScope.launch { + private fun updateTLEFromFile(uri: String) = viewModelScope.launch { try { val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = true) _uiState.update { it.copy(dataSettings = newDataSettings) } - databaseRepo.updateFromFile(uri) + databaseRepo.updateTLEFromFile(uri) + } catch (exception: Exception) { + val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = false) + _uiState.update { it.copy(dataSettings = newDataSettings) } + println(exception) + } + } + + private fun updateTransceiversFromFile(uri: String) = viewModelScope.launch { + try { + val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = true) + _uiState.update { it.copy(dataSettings = newDataSettings) } + databaseRepo.updateTransceiversFromFile(uri) } catch (exception: Exception) { val newDataSettings = _uiState.value.dataSettings.copy(isUpdating = false) _uiState.update { it.copy(dataSettings = newDataSettings) } diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 391d04bb..6e6a00fe 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -105,9 +105,15 @@ Actualizar Importar Vaciar + Custom data sources Datos eliminados con éxito Actualización completada con éxito + Custom data sources + Custom TLE URL + Custom transceivers URL + URL (https) + Salida de datos de red Habilitar salida Dirección IP diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 85e6ab95..ffe3aa6f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -104,9 +104,15 @@ Обновить Импорт Очистить + Свои источники данных Очистка прошла успешно Обновление прошло успешно + Свои источники данных + Custom TLE URL + Custom transceivers URL + URL (https) + Вывод сетевых данных Включить вывод IP адрес diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index 00728c12..1144f44a 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -105,9 +105,15 @@ නැවුම් කරන්න ආදානය පිරිසිදු ක° + Custom data sources දත්ත ඉවත් කිරීම සාර්ථකයි යාවත්කාලීනය සම්පූර්ණ කිරීම සාර්ථකයි + Custom data sources + Custom TLE URL + Custom transceivers URL + URL (https) + ජාල ප්‍රතිදානය ප්‍රතිදානය සක්‍රීය කරන්න IP ලිපිනය diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index dd8b5644..9fb4870d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -105,9 +105,15 @@ Оновити Імпорт Очистити + Custom data sources Дані успішно очищено Оновлення успішне + Custom data sources + Custom TLE URL + Custom transceivers URL + URL (https) + Вивід мережевих даних Увімкнути вихід IP адреса diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index d532cc87..b0acfb07 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -97,9 +97,15 @@ 在线更新 导入文件 清空 + Custom data sources 数据清空成功 更新成功 + Custom data sources + Custom TLE URL + Custom transceivers URL + URL (https) + 网络数据输出 启用输出 IP地址 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b1a80767..4a33e8bf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,9 +128,15 @@ Update Import Clear + Custom data sources Data was cleared successfully Update completed successfully + Custom data sources + Custom TLE URL + Custom transceivers URL + URL (https) + Network data output Enable output IP address diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt index 4a1295be..8f9a1e91 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/DatabaseRepo.kt @@ -39,23 +39,46 @@ class DatabaseRepo( private val settingsRepo: ISettingsRepo ) : IDatabaseRepo { - override suspend fun updateFromFile(uri: String) = withContext(dispatcher) { + override suspend fun updateTLEFromFile(uri: String) = withContext(dispatcher) { val importedSatellites = remoteSource.getFileStream(uri)?.let { dataParser.parseTLEStream(it) } importedSatellites?.let { localSource.insertEntries(it) } setUpdateSuccessful(System.currentTimeMillis()) } + override suspend fun updateTransceiversFromFile(uri: String) = withContext(dispatcher) { + val radios = remoteSource.getFileStream(uri)?.let { dataParser.parseJSONStream(it) } + radios?.let { + if(it.isNotEmpty()) { + localSource.deleteRadios() + localSource.insertRadios(it) + } + } + setUpdateSuccessful(System.currentTimeMillis()) + } + override suspend fun updateFromRemote() = withContext(dispatcher) { val importedEntries = mutableListOf() val importedRadios = mutableListOf() - // fetch - val jobsMap = Sources.satelliteDataUrls.mapValues { async { remoteSource.getNetworkStream(it.value) } } - val jobRadios = async { remoteSource.getNetworkStream(Sources.RADIO_DATA_URL) } + val tleUrls = Sources.satelliteDataUrls.toMutableMap().apply { + if (settingsRepo.dataSourcesSettings.value.useCustomTLE) { + this["Other"] = settingsRepo.dataSourcesSettings.value.transceiversUrl + } + } + val jobsMap = tleUrls + .filter { (_, url) -> url.isNotEmpty() } + .mapValues { (_, url) -> async { remoteSource.getNetworkStream(url) } } + val radioUrls = buildList { + add(Sources.RADIO_DATA_URL) + if (settingsRepo.dataSourcesSettings.value.useCustomTransceivers) { + add(settingsRepo.dataSourcesSettings.value.transceiversUrl) + } + } + val jobRadios = radioUrls.associateWith { url -> async { remoteSource.getNetworkStream(url) } } // parse jobsMap.mapValues { job -> job.value.await() }.forEach { entry -> entry.value?.let { stream -> when (val type = entry.key) { - "Amsat", "R4UAB" -> { + "Amsat", "R4UAB", "Other" -> { // parse tle stream val satellites = dataParser.parseTLEStream(stream) val catnums = satellites.map { it.catnum } @@ -82,7 +105,11 @@ class DatabaseRepo( } } } - jobRadios.await()?.let { importedRadios.addAll(dataParser.parseJSONStream(it)) } + jobRadios.values.forEach { job -> + job.await()?.let { + importedRadios.addAll(dataParser.parseJSONStream(it)) + } + } // insert localSource.insertEntries(importedEntries) localSource.insertRadios(importedRadios) diff --git a/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt b/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt index 1c00bb5e..c4894af8 100644 --- a/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt +++ b/data/src/main/java/com/rtbishop/look4sat/data/repository/SettingsRepo.kt @@ -27,6 +27,7 @@ import com.rtbishop.look4sat.domain.model.DatabaseState import com.rtbishop.look4sat.domain.model.OtherSettings import com.rtbishop.look4sat.domain.model.PassesSettings import com.rtbishop.look4sat.domain.model.RCSettings +import com.rtbishop.look4sat.domain.model.DataSourcesSettings import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.domain.repository.ISettingsRepo import com.rtbishop.look4sat.domain.utility.positionToQth @@ -68,6 +69,10 @@ class SettingsRepo( private val keyUpdateTimestamp = "updateTimestamp" private val keyShouldSeeWarning = "shouldSeeWarning" private val keyShouldSeeWelcome = "shouldSeeWelcome" + private val keyUseCustomTle = "useCustomTle" + private val keyUseCustomTransceivers = "useCustomTransceivers" + private val keyTleUrl = "tleUrl" + private val keyTransceiversUrl = "transceiversUrl" private val separatorComma = "," //region # Satellites selection settings @@ -330,4 +335,36 @@ class SettingsRepo( shouldSeeWelcome = preferences.getBoolean(keyShouldSeeWelcome, true) ) //endregion + + //region # Data sources settings + private val _dataSourcesSettings = MutableStateFlow(getDataSourcesSettings()) + override val dataSourcesSettings: StateFlow = _dataSourcesSettings + + override fun setUseCustomTle(value: Boolean) { + preferences.edit { putBoolean(keyUseCustomTle, value) } + _dataSourcesSettings.update { it.copy(useCustomTLE = value) } + } + + override fun setUseCustomTransceivers(value: Boolean) { + preferences.edit { putBoolean(keyUseCustomTransceivers, value) } + _dataSourcesSettings.update { it.copy(useCustomTransceivers = value) } + } + + override fun setTleUrl(value: String) { + preferences.edit { putString(keyTleUrl, value) } + _dataSourcesSettings.update { it.copy(tleUrl = value) } + } + + override fun setTransceiversUrl(value: String) { + preferences.edit { putString(keyTransceiversUrl, value) } + _dataSourcesSettings.update { it.copy(transceiversUrl = value) } + } + + 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") ?: "" + ) + //endregion } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt index 027a8ec5..dbfef841 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/model/Settings.kt @@ -48,3 +48,10 @@ data class OtherSettings( val shouldSeeWarning: Boolean, val shouldSeeWelcome: Boolean ) + +data class DataSourcesSettings( + val useCustomTLE: Boolean, + val useCustomTransceivers: Boolean, + val tleUrl: String, + val transceiversUrl: String +) diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt index 9b4bc3a8..39f1c387 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/IDatabaseRepo.kt @@ -18,7 +18,8 @@ package com.rtbishop.look4sat.domain.repository interface IDatabaseRepo { - suspend fun updateFromFile(uri: String) + suspend fun updateTLEFromFile(uri: String) + suspend fun updateTransceiversFromFile(uri: String) suspend fun updateFromRemote() suspend fun clearAllData() } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt index 8dd8c3bf..387939c1 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/repository/ISettingsRepo.kt @@ -21,6 +21,7 @@ import com.rtbishop.look4sat.domain.model.DatabaseState import com.rtbishop.look4sat.domain.model.OtherSettings import com.rtbishop.look4sat.domain.model.PassesSettings import com.rtbishop.look4sat.domain.model.RCSettings +import com.rtbishop.look4sat.domain.model.DataSourcesSettings import com.rtbishop.look4sat.domain.predict.GeoPos import kotlinx.coroutines.flow.StateFlow @@ -73,4 +74,12 @@ interface ISettingsRepo { fun setWarningDismissed() fun setWelcomeDismissed() //endregion + + //region # Transceivers settings + val dataSourcesSettings: StateFlow + fun setUseCustomTle(value: Boolean) + fun setUseCustomTransceivers(value: Boolean) + fun setTleUrl(value: String) + fun setTransceiversUrl(value: String) + //endregion } diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/source/Sources.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/source/Sources.kt index fe30d6e7..c10710e0 100644 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/source/Sources.kt +++ b/domain/src/main/java/com/rtbishop/look4sat/domain/source/Sources.kt @@ -46,6 +46,7 @@ object Sources { "Swarm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=swarm&FORMAT=csv", "Weather" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=csv", "X-Comm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=x-comm&FORMAT=csv", - "Amsat" to "https://amsat.org/tle/current/nasabare.txt" + "Amsat" to "https://amsat.org/tle/current/nasabare.txt", + "Other" to "" // key for sats filter ) }