Compare commits

...
9 Commits
32 changed files with 1005 additions and 382 deletions

No files matched your search

-2
View File
@@ -17,8 +17,6 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v5
+2 -2
View File
@@ -13,8 +13,8 @@ android {
defaultConfig {
applicationId = "com.rtbishop.look4sat"
minSdk = 24
versionCode = 402
versionName = "4.0.2"
versionCode = 410
versionName = "4.1.0"
}
buildFeatures {
compose = true
@@ -299,7 +299,7 @@ fun SharedDialog(
@Composable
fun InfoDialog(title: String, text: String, onDismiss: () -> Unit) {
val padding = LocalSpacing.current.large
Dialog(onDismissRequest = onDismiss) {
Dialog(onDismissRequest = {}) {
ElevatedCard {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
@@ -110,12 +110,12 @@ private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> U
uiState.takeAction(PassesAction.FilterRadios(modes))
}
}
if (uiState.shouldSeeWelcome) {
if (uiState.shouldSeeWhatsNew) {
InfoDialog(
title = stringResource(R.string.pass_welcome_title),
text = stringResource(R.string.pass_welcome_message)
title = stringResource(R.string.pass_whatsnew_title),
text = stringResource(R.string.pass_whatsnew_message)
) {
uiState.takeAction(PassesAction.DismissWelcome)
uiState.takeAction(PassesAction.DismissWhatsNew)
}
}
val gridState = rememberLazyGridState()
@@ -31,12 +31,12 @@ data class PassesState(
val elevation: Double,
val modes: List<String>,
val itemsList: List<OrbitalPass>,
val shouldSeeWelcome: Boolean,
val shouldSeeWhatsNew: Boolean,
val takeAction: (PassesAction) -> Unit
)
sealed class PassesAction {
data object DismissWelcome : PassesAction()
data object DismissWhatsNew : PassesAction()
data class FilterPasses(val hoursAhead: Int, val minElevation: Double) : PassesAction()
data class FilterRadios(val modes: List<String>) : PassesAction()
data object RefreshPasses : PassesAction()
@@ -40,7 +40,8 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
class PassesViewModel(
private val satelliteRepo: ISatelliteRepo, private val settingsRepo: ISettingsRepo
private val satelliteRepo: ISatelliteRepo,
private val settingsRepo: ISettingsRepo
) : ViewModel() {
private val _uiState = MutableStateFlow(
@@ -56,7 +57,7 @@ class PassesViewModel(
elevation = settingsRepo.passesSettings.value.minElevation,
modes = settingsRepo.passesSettings.value.selectedModes,
itemsList = emptyList(),
shouldSeeWelcome = settingsRepo.otherSettings.value.shouldSeeWelcome,
shouldSeeWhatsNew = settingsRepo.otherSettings.value.shouldSeeWhatsNew,
takeAction = ::handleAction
)
)
@@ -81,14 +82,14 @@ class PassesViewModel(
}
viewModelScope.launch {
settingsRepo.otherSettings.collectLatest { settings ->
_uiState.update { it.copy(shouldSeeWelcome = settings.shouldSeeWelcome) }
_uiState.update { it.copy(shouldSeeWhatsNew = settings.shouldSeeWhatsNew) }
}
}
}
private fun handleAction(action: PassesAction) {
when (action) {
PassesAction.DismissWelcome -> settingsRepo.setWelcomeDismissed()
PassesAction.DismissWhatsNew -> settingsRepo.setWhatsNewDismissed()
is PassesAction.FilterPasses -> applyFilter(action.hoursAhead, action.minElevation, uiState.value.modes)
is PassesAction.FilterRadios -> applyFilter(uiState.value.hours, uiState.value.elevation, action.modes)
PassesAction.RefreshPasses -> refreshPasses()
@@ -25,6 +25,7 @@ import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -45,6 +46,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.keepScreenOn
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
@@ -97,7 +99,7 @@ private fun RadarScreen(uiState: RadarState, navigateUp: () -> Unit) {
}
val upcomingPass = uiState.currentPass ?: getDefaultPass()
if (upcomingPass.losTime < System.currentTimeMillis()) navigateUp()
Column(modifier = Modifier.layoutPadding(), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Column(modifier = Modifier.layoutPadding().keepScreenOn(), verticalArrangement = Arrangement.spacedBy(6.dp)) {
if (isVerticalLayout()) {
TopBar {
IconCard(action = navigateUp, resId = R.drawable.ic_back)
@@ -193,7 +195,15 @@ private fun RadarScreen(uiState: RadarState, navigateUp: () -> Unit) {
}
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
TransmittersList(transmitters = uiState.transmitters)
TransmittersList(
transmitters = uiState.transmitters,
selectedUuid = uiState.selectedTransmitterUuid,
onSelect = { uuid ->
if (uiState.selectedTransmitterUuid != null) {
uiState.sendAction(RadarAction.SelectTransmitter(uuid))
}
}
)
if (uiState.orbitalPos?.eclipsed == true) {
EclipsedIndicator()
}
@@ -281,7 +291,15 @@ private fun RadarScreen(uiState: RadarState, navigateUp: () -> Unit) {
}
} else {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
TransmittersList(transmitters = uiState.transmitters)
TransmittersList(
transmitters = uiState.transmitters,
selectedUuid = uiState.selectedTransmitterUuid,
onSelect = { uuid ->
if (uiState.selectedTransmitterUuid != null) {
uiState.sendAction(RadarAction.SelectTransmitter(uuid))
}
}
)
if (uiState.orbitalPos?.eclipsed == true) {
EclipsedIndicator()
}
@@ -314,10 +332,18 @@ private fun RadarTextBottom(value: Double, text: String, isLeft: Boolean) {
}
@Composable
private fun TransmittersList(transmitters: List<SatRadio>) {
private fun TransmittersList(
transmitters: List<SatRadio>,
selectedUuid: String?,
onSelect: (String) -> Unit
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(items = transmitters, key = { item -> item.uuid }) { radio ->
TransmitterItem(radio)
TransmitterItem(
radio,
(selectedUuid != null),
(radio.uuid == selectedUuid)
) { onSelect(radio.uuid) }
}
}
}
@@ -329,7 +355,7 @@ private fun TransmitterItemPreview() {
"", "Extremely powerful transmitter", true, 10000000000L, 10000000000L,
null, 10000000000L, 10000000000L, "FSK AX.100 Mode 5", true, 0
)
MainTheme { TransmitterItem(transmitter) }
MainTheme { TransmitterItem(transmitter, isClickable = true, isSelected = true, onClick = {}) }
}
@Composable
@@ -369,36 +395,46 @@ private fun EclipsedIndicator() {
}
@Composable
private fun TransmitterItem(radio: SatRadio) {
// LaunchedEffect(radio) {
// BluetoothCIV.updateOnce(radio)
// }
private fun TransmitterItem(
radio: SatRadio,
isClickable: Boolean,
isSelected: Boolean,
onClick: () -> Unit
) {
val title = if (radio.isInverted) "INVERTED: ${radio.info}" else radio.info
val fullTitle = "$title - (${radio.downlinkMode ?: "--"}/${radio.uplinkMode ?: "--"})"
Surface(color = MaterialTheme.colorScheme.background,
// modifier = Modifier.clickable(onClick = {
// Log.d("BluetoothCivManager", radio.toString())
// if(radio.uuid == BluetoothCIV.selected) BluetoothCIV.selected = "NONE" else
// {
// BluetoothCIV.connect(radio)
// BluetoothCIV.updateOnce(radio)
// }
// })
Surface(
color = MaterialTheme.colorScheme.background,
modifier = Modifier
.fillMaxWidth()
.then(if (isClickable) Modifier.clickable { onClick() } else Modifier)
) {
Surface(modifier = Modifier.padding(bottom = 2.dp)) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.padding(horizontal = 8.dp, vertical = 6.dp)
) {
Text(
text = fullTitle,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.infiniteMarquee()
)
Box {
Text(
text = fullTitle,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 6.dp)
.infiniteMarquee()
)
if (isSelected) {
Icon(
painter = painterResource(id = R.drawable.ic_radios),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.background(color = MaterialTheme.colorScheme.surface)
)
}
}
FrequencyRow(radio = radio, isDownlink = true)
FrequencyRow(radio = radio, isDownlink = false)
}
@@ -31,9 +31,12 @@ data class RadarState(
val shouldShowSweep: Boolean,
val shouldUseCompass: Boolean,
val transmitters: List<SatRadio>,
val selectedTransmitterUuid: String?,
val selectedFrequency: Long?,
val sendAction: (RadarAction) -> Unit
)
sealed class RadarAction {
data class AddToCalendar(val name: String, val aosTime: Long, val losTime: Long) : RadarAction()
data class SelectTransmitter(val uuid: String) : RadarAction()
}
@@ -25,7 +25,10 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.rtbishop.look4sat.MainApplication
import com.rtbishop.look4sat.data.framework.WithoutExtParams
import com.rtbishop.look4sat.data.framework.BluetoothReporter
import com.rtbishop.look4sat.data.framework.BtService
import com.rtbishop.look4sat.data.framework.ExtendedParams
import com.rtbishop.look4sat.data.framework.NetworkReporter
import com.rtbishop.look4sat.domain.model.SatRadio
import com.rtbishop.look4sat.domain.predict.OrbitalObject
@@ -65,6 +68,8 @@ class RadarViewModel(
satTrack = emptyList(),
shouldShowSweep = settingsRepo.otherSettings.value.stateOfSweep,
shouldUseCompass = settingsRepo.otherSettings.value.stateOfSensors,
selectedTransmitterUuid = null,
selectedFrequency = null,
transmitters = emptyList(),
sendAction = ::handleAction,
)
@@ -109,9 +114,9 @@ class RadarViewModel(
_uiState.update { it.copy(currentTime = time, isCurrentTimeAos = false) }
}
}
processRadios(transmitters, satPass.orbitalObject, timeNow)
sendPassData(satPass, pos, satPass.orbitalObject)
sendPassDataBT(pos)
processRadios(transmitters, satPass.orbitalObject, timeNow)
delay(1000)
}
}
@@ -126,6 +131,7 @@ class RadarViewModel(
private fun handleAction(action: RadarAction) {
when (action) {
is RadarAction.AddToCalendar -> addToCalendar(action.name, action.aosTime, action.losTime)
is RadarAction.SelectTransmitter -> { _uiState.update { it.copy(selectedTransmitterUuid = action.uuid) } }
}
}
@@ -141,25 +147,47 @@ class RadarViewModel(
if (settingsRepo.rcSettings.value.rotatorState) {
val server = settingsRepo.rcSettings.value.rotatorAddress
val port = settingsRepo.rcSettings.value.rotatorPort.toInt()
val format = settingsRepo.rcSettings.value.rotatorFormat
val azimuth = orbitalPos.azimuth.toDegrees().round(2)
val elevation = orbitalPos.elevation.toDegrees().round(2)
networkReporter.reportRotation(server, port, azimuth, elevation)
networkReporter.reportRotation(format, azimuth, elevation, ExtendedParams(server, port))
}
if (settingsRepo.rcSettings.value.frequencyState) {
_uiState.value.selectedFrequency?.let { freq ->
val server = settingsRepo.rcSettings.value.frequencyAddress
val port = settingsRepo.rcSettings.value.frequencyPort.toInt()
val format = settingsRepo.rcSettings.value.frequencyFormat
networkReporter.reportFrequency(format,freq, ExtendedParams(server, port))
}
}
}
}
private fun sendPassDataBT(orbitalPos: OrbitalPos) {
viewModelScope.launch {
if (settingsRepo.rcSettings.value.bluetoothState) {
val btDevice = settingsRepo.rcSettings.value.bluetoothAddress
if (bluetoothReporter.isConnected()) {
val format = settingsRepo.rcSettings.value.bluetoothFormat
val azimuth = orbitalPos.azimuth.toDegrees().round(0).toInt()
val elevation = orbitalPos.elevation.toDegrees().round(0).toInt()
bluetoothReporter.reportRotation(format, azimuth, elevation)
} else if (!bluetoothReporter.isConnecting()) {
// Log.i("BTReporter", "BTReporter: Attempting to connect...")
bluetoothReporter.connectBTDevice(btDevice)
if (settingsRepo.rcSettings.value.bluetoothRotatorState) {
val btRotatorDevice = settingsRepo.rcSettings.value.bluetoothRotatorAddress
if (bluetoothReporter.isConnected(BtService.ROTATOR)) {
val format = settingsRepo.rcSettings.value.bluetoothRotatorFormat
val azimuth = orbitalPos.azimuth.toDegrees().round(0)
val elevation = orbitalPos.elevation.toDegrees().round(0)
bluetoothReporter.reportRotation(format, azimuth, elevation, WithoutExtParams(0))
} else if (!bluetoothReporter.isConnecting(BtService.ROTATOR)) {
// Log.i("BTReporter", "BTReporter (rotator): Attempting to connect...")
bluetoothReporter.connect(BtService.ROTATOR,btRotatorDevice)
}
}
if (settingsRepo.rcSettings.value.bluetoothFrequencyState) {
val btFrequencyDevice = settingsRepo.rcSettings.value.bluetoothFrequencyAddress
if (bluetoothReporter.isConnected(BtService.FREQUENCY)) {
val format = settingsRepo.rcSettings.value.bluetoothFrequencyFormat
val frequency = _uiState.value.selectedFrequency
frequency?.let { freq ->
bluetoothReporter.reportFrequency(format, freq, WithoutExtParams(0))
}
} else if (!bluetoothReporter.isConnecting(BtService.FREQUENCY)) {
// Log.i("BTReporter", "BTReporter (frequency): Attempting to connect...")
bluetoothReporter.connect(BtService.FREQUENCY, btFrequencyDevice)
}
}
}
@@ -167,7 +195,33 @@ class RadarViewModel(
private suspend fun processRadios(radios: List<SatRadio>, orbitalObject: OrbitalObject, time: Long) {
val transmitters = satelliteRepo.getRadios(orbitalObject, stationPos, radios, time)
_uiState.update { it.copy(transmitters = transmitters) }
_uiState.update { state ->
if (!settingsRepo.rcSettings.value.frequencyState && !settingsRepo.rcSettings.value.bluetoothFrequencyState) {
return@update state.copy(
transmitters = transmitters,
selectedTransmitterUuid = null,
selectedFrequency = null
)
}
val selectedUuid = state.selectedTransmitterUuid ?: transmitters.firstOrNull()?.uuid
val selectedRadio = transmitters.firstOrNull { it.uuid == selectedUuid }
val freq = selectedRadio?.let { radio ->
val low = radio.downlinkLow
val high = radio.downlinkHigh
when {
low != null && high != null ->
(low + high) / 2
low != null ->
low
else -> null
}
}
state.copy(
transmitters = transmitters,
selectedTransmitterUuid = selectedUuid,
selectedFrequency = freq
)
}
}
companion object {
@@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.rtbishop.look4sat.presentation.LocalSpacing
import com.rtbishop.look4sat.domain.model.RCSettings
@Preview(showBackground = true)
@Composable
@@ -199,3 +200,285 @@ fun DataSourcesDialog(
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewNetworkOutputDialog() {
MainTheme {
NetworkOutputDialog(
initialSettings = RCSettings(
rotatorState = false,
rotatorAddress = "127.0.0.1",
rotatorPort = "4533",
rotatorFormat = $$"P $AZ $EL",
frequencyState = false,
frequencyAddress = "127.0.0.1",
frequencyPort = "4532",
frequencyFormat = $$"set_freq $FREQ",
bluetoothRotatorState = false,
bluetoothRotatorFormat = $$"W$AZ $EL",
bluetoothRotatorName = "Default",
bluetoothRotatorAddress = "00:0C:BF:13:80:5D",
bluetoothFrequencyState = false,
bluetoothFrequencyAddress = "00:0C:BF:13:80:5D",
bluetoothFrequencyFormat = $$"FA$FREQ"
),
onDismiss = {},
onSave = { _, _, _, _, _, _, _, _ -> }
)
}
}
@Composable
fun NetworkOutputDialog(
initialSettings: RCSettings,
onDismiss: () -> Unit,
onSave: (
Boolean, String, String, String,
Boolean, String, String, String
) -> Unit
) {
val padding = LocalSpacing.current.large
val rotatorState = rememberSaveable { mutableStateOf(initialSettings.rotatorState) }
val rotatorAddress = rememberSaveable { mutableStateOf(initialSettings.rotatorAddress) }
val rotatorPort = rememberSaveable { mutableStateOf(initialSettings.rotatorPort) }
val rotatorFormat = rememberSaveable { mutableStateOf(initialSettings.rotatorFormat) }
val frequencyState = rememberSaveable { mutableStateOf(initialSettings.frequencyState) }
val frequencyAddress = rememberSaveable { mutableStateOf(initialSettings.frequencyAddress) }
val frequencyPort = rememberSaveable { mutableStateOf(initialSettings.frequencyPort) }
val frequencyFormat = rememberSaveable { mutableStateOf(initialSettings.frequencyFormat) }
val onAccept = {
onSave(
rotatorState.value,
rotatorAddress.value,
rotatorPort.value,
rotatorFormat.value,
frequencyState.value,
frequencyAddress.value,
frequencyPort.value,
frequencyFormat.value
)
onDismiss()
}
SharedDialog(
title = stringResource(R.string.prefs_net_title),
onCancel = onDismiss,
onAccept = onAccept
) {
Column(modifier = Modifier.padding(horizontal = padding)) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.prefs_net_rotator_switch))
Switch(
checked = rotatorState.value,
onCheckedChange = { rotatorState.value = it }
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = rotatorAddress.value,
onValueChange = { rotatorAddress.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_net_rotator_ip_hint)) },
modifier = Modifier.weight(1.5f),
enabled = rotatorState.value
)
OutlinedTextField(
value = rotatorPort.value,
onValueChange = { rotatorPort.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_net_rotator_port_hint)) },
modifier = Modifier.weight(1f),
enabled = rotatorState.value
)
}
Spacer(modifier = Modifier.height(6.dp))
OutlinedTextField(
value = rotatorFormat.value,
onValueChange = { rotatorFormat.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_net_rotator_format_hint)) },
modifier = Modifier.fillMaxWidth(),
enabled = rotatorState.value
)
Spacer(modifier = Modifier.height(6.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.prefs_net_frequency_switch))
Switch(
checked = frequencyState.value,
onCheckedChange = { frequencyState.value = it }
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = frequencyAddress.value,
onValueChange = { frequencyAddress.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_net_frequency_ip_hint)) },
modifier = Modifier.weight(1.5f),
enabled = frequencyState.value
)
OutlinedTextField(
value = frequencyPort.value,
onValueChange = { frequencyPort.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_net_frequency_port_hint)) },
modifier = Modifier.weight(1f),
enabled = frequencyState.value
)
}
Spacer(modifier = Modifier.height(6.dp))
OutlinedTextField(
value = frequencyFormat.value,
onValueChange = { frequencyFormat.value = it },
maxLines = 2,
label = { Text(stringResource(R.string.prefs_net_frequency_format_hint)) },
modifier = Modifier.fillMaxWidth(),
enabled = frequencyState.value
)
}
}
}
@Preview(showBackground = true)
@Composable
fun PreviewBluetoothOutputDialog() {
MainTheme {
BluetoothOutputDialog(
initialSettings = RCSettings(
rotatorState = false,
rotatorAddress = "127.0.0.1",
rotatorPort = "4533",
rotatorFormat = $$"P $AZ $EL",
frequencyState = false,
frequencyAddress = "127.0.0.1",
frequencyPort = "4532",
frequencyFormat = $$"set_freq $FREQ",
bluetoothRotatorState = false,
bluetoothRotatorFormat = $$"W$AZ $EL",
bluetoothRotatorName = "Default",
bluetoothRotatorAddress = "00:0C:BF:13:80:5D",
bluetoothFrequencyState = false,
bluetoothFrequencyAddress = "00:0C:BF:13:80:5D",
bluetoothFrequencyFormat = $$"FA$FREQ"
),
onDismiss = {},
onSave = { _, _, _, _, _, _ -> }
)
}
}
@Composable
fun BluetoothOutputDialog(
initialSettings: RCSettings,
onDismiss: () -> Unit,
onSave: (
Boolean, String, String,
Boolean, String, String
) -> Unit
) {
val padding = LocalSpacing.current.large
val rotatorState = rememberSaveable { mutableStateOf(initialSettings.bluetoothRotatorState) }
val rotatorAddress = rememberSaveable { mutableStateOf(initialSettings.bluetoothRotatorAddress) }
val rotatorFormat = rememberSaveable { mutableStateOf(initialSettings.bluetoothRotatorFormat) }
val frequencyState = rememberSaveable { mutableStateOf(initialSettings.bluetoothFrequencyState) }
val frequencyAddress = rememberSaveable { mutableStateOf(initialSettings.bluetoothFrequencyAddress) }
val frequencyFormat = rememberSaveable { mutableStateOf(initialSettings.bluetoothFrequencyFormat) }
val onAccept = {
onSave(
rotatorState.value,
rotatorAddress.value,
rotatorFormat.value,
frequencyState.value,
frequencyAddress.value,
frequencyFormat.value
)
onDismiss()
}
SharedDialog(
title = stringResource(R.string.prefs_bt_title),
onCancel = onDismiss,
onAccept = onAccept
) {
Column(modifier = Modifier.padding(horizontal = padding)) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.prefs_bt_rotator_switch))
Switch(
checked = rotatorState.value,
onCheckedChange = { rotatorState.value = it }
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = rotatorAddress.value,
onValueChange = { rotatorAddress.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_bt_rotator_device_hint)) },
modifier = Modifier.weight(1.5f),
enabled = rotatorState.value
)
OutlinedTextField(
value = rotatorFormat.value,
onValueChange = { rotatorFormat.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_bt_rotator_output_hint)) },
modifier = Modifier.weight(1f),
enabled = rotatorState.value
)
}
Spacer(modifier = Modifier.height(6.dp))
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.prefs_bt_frequency_switch))
Switch(
checked = frequencyState.value,
onCheckedChange = { frequencyState.value = it }
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = frequencyAddress.value,
onValueChange = { frequencyAddress.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_bt_frequency_device_hint)) },
modifier = Modifier.weight(1.5f),
enabled = frequencyState.value
)
OutlinedTextField(
value = frequencyFormat.value,
onValueChange = { frequencyFormat.value = it },
singleLine = true,
label = { Text(stringResource(R.string.prefs_bt_frequency_output_hint)) },
modifier = Modifier.weight(1f),
enabled = frequencyState.value
)
}
}
}
}
@@ -32,12 +32,12 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.LinearProgressIndicator
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
@@ -60,7 +60,6 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.model.OtherSettings
import com.rtbishop.look4sat.domain.model.RCSettings
import com.rtbishop.look4sat.domain.predict.GeoPos
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.Screen
@@ -87,19 +86,6 @@ fun NavGraphBuilder.settingsDestination() {
@Composable
private fun SettingsScreen(uiState: SettingsState) {
// Permissions setup
val bluetoothContract = ActivityResultContracts.RequestPermission()
val bluetoothError = stringResource(R.string.prefs_bt_perm_error)
val bluetoothPerm = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH
else -> Manifest.permission.BLUETOOTH_CONNECT
}
val bluetoothRequest = rememberLauncherForActivityResult(bluetoothContract) { isGranted ->
if (!isGranted) {
uiState.sendRCAction(RCAction.SetBluetoothState(false))
uiState.sendSystemAction(SystemAction.ShowToast(bluetoothError))
}
}
val locationContract = ActivityResultContracts.RequestMultiplePermissions()
val locationError = stringResource(R.string.prefs_loc_gps_error)
val locationPermCoarse = Manifest.permission.ACCESS_COARSE_LOCATION
@@ -180,12 +166,84 @@ private fun SettingsScreen(uiState: SettingsState) {
val setRotatorState = { value: Boolean -> uiState.sendRCAction(RCAction.SetRotatorState(value)) }
val setRotatorAddress = { value: String -> uiState.sendRCAction(RCAction.SetRotatorAddress(value)) }
val setRotatorPort = { value: String -> uiState.sendRCAction(RCAction.SetRotatorPort(value)) }
val setBluetoothState = { value: Boolean ->
bluetoothRequest.launch(bluetoothPerm)
uiState.sendRCAction(RCAction.SetBluetoothState(value))
val setRotatorFormat = { value: String -> uiState.sendRCAction(RCAction.SetRotatorFormat(value)) }
val setFrequencyState = { value: Boolean -> uiState.sendRCAction(RCAction.SetFrequencyState(value)) }
val setFrequencyAddress = { value: String -> uiState.sendRCAction(RCAction.SetFrequencyAddress(value)) }
val setFrequencyPort = { value: String -> uiState.sendRCAction(RCAction.SetFrequencyPort(value)) }
val setFrequencyFormat = { value: String -> uiState.sendRCAction(RCAction.SetFrequencyFormat(value)) }
val setBluetoothRotatorState = { value: Boolean -> uiState.sendRCAction(RCAction.SetBluetoothRotatorState(value)) }
val setBluetoothRotatorAddress = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothRotatorAddress(value)) }
val setBluetoothRotatorFormat = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothRotatorFormat(value)) }
val setBluetoothFrequencyState = { value: Boolean -> uiState.sendRCAction(RCAction.SetBluetoothFrequencyState(value)) }
val setBluetoothFrequencyAddress = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothFrequencyAddress(value)) }
val setBluetoothFrequencyFormat = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothFrequencyFormat(value)) }
// Network data output
val networkDialogState = rememberSaveable { mutableStateOf(false) }
val showNetworkDialog = { networkDialogState.value = true }
val dismissNetworkDialog = { networkDialogState.value = false }
if (networkDialogState.value) {
NetworkOutputDialog(
initialSettings = rcSettings,
onDismiss = dismissNetworkDialog,
onSave = { rotatorState,
rotatorAddress,
rotatorPort,
rotatorFormat,
frequencyState,
frequencyAddress,
frequencyPort,
frequencyFormat ->
setRotatorState(rotatorState)
setRotatorAddress(rotatorAddress)
setRotatorPort(rotatorPort)
setRotatorFormat(rotatorFormat)
setFrequencyState(frequencyState)
setFrequencyAddress(frequencyAddress)
setFrequencyPort(frequencyPort)
setFrequencyFormat(frequencyFormat)
}
)
}
// Bluetooth data output
val bluetoothDialogState = rememberSaveable { mutableStateOf(false) }
val bluetoothContract = ActivityResultContracts.RequestPermission()
val bluetoothError = stringResource(R.string.prefs_bt_perm_error)
val bluetoothPerm = when {
Build.VERSION.SDK_INT < Build.VERSION_CODES.S -> Manifest.permission.BLUETOOTH
else -> Manifest.permission.BLUETOOTH_CONNECT
}
val bluetoothRequest = rememberLauncherForActivityResult(bluetoothContract) { isGranted ->
if (!isGranted)
{
uiState.sendRCAction(RCAction.SetBluetoothRotatorState(false))
uiState.sendRCAction(RCAction.SetBluetoothFrequencyState(false))
uiState.sendSystemAction(SystemAction.ShowToast(bluetoothError))
}
else { bluetoothDialogState.value = true }
}
val showBluetoothDialog = { bluetoothRequest.launch(bluetoothPerm) }
val dismissBluetoothDialog = { bluetoothDialogState.value = false }
if (bluetoothDialogState.value) {
BluetoothOutputDialog(
initialSettings = rcSettings,
onDismiss = dismissBluetoothDialog,
onSave = { rotatorState,
rotatorAddress,
rotatorFormat,
frequencyState,
frequencyAddress,
frequencyFormat ->
setBluetoothRotatorState(rotatorState)
setBluetoothRotatorAddress(rotatorAddress)
setBluetoothRotatorFormat(rotatorFormat)
setBluetoothFrequencyState(frequencyState)
setBluetoothFrequencyAddress(frequencyAddress)
setBluetoothFrequencyFormat(frequencyFormat)
}
)
}
val setBluetoothAddress = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothAddress(value)) }
val setBluetoothFormat = { value: String -> uiState.sendRCAction(RCAction.SetBluetoothFormat(value)) }
// Other settings
val otherSettings = uiState.otherSettings
@@ -242,8 +300,7 @@ private fun SettingsScreen(uiState: SettingsState) {
LocationCard(posSettings, setGpsPos, showPosDialog, showLocDialog, dismissPos, uiState.sendSystemAction)
}
item { DataCard(dataSettings, updateFromWeb, clearAllData, showDataSourcesDialog) }
item { NetworkOutputCard(rcSettings, setRotatorState, setRotatorAddress, setRotatorPort) }
item { BluetoothOutputCard(rcSettings, setBluetoothState, setBluetoothAddress, setBluetoothFormat) }
item(span = { GridItemSpan(maxLineSpan) }) { OutputCard({ showNetworkDialog() }, { showBluetoothDialog() }) }
item { OtherCard(otherSettings, toggleUtc, toggleUpdate, toggleSweep, toggleSensor) }
item { CardCredits() }
}
@@ -375,126 +432,32 @@ private fun DataCard(
@Preview(showBackground = true)
@Composable
private fun NetworkOutputCardPreview() = MainTheme {
val settings = RCSettings(
rotatorState = false,
rotatorAddress = "127.0.0.1",
rotatorPort = "4533",
bluetoothState = false,
bluetoothFormat = $$"W$AZ $EL",
bluetoothName = "Name",
bluetoothAddress = "00:0C:BF:13:80:5D"
)
NetworkOutputCard(settings)
}
private fun OutputCardPreview() = MainTheme { OutputCard({}, {}) }
@Composable
private fun NetworkOutputCard(
settings: RCSettings,
setRotatorState: (Boolean) -> Unit = {},
setRotatorAddress: (String) -> Unit = {},
setRotatorPort: (String) -> Unit = {}
private fun OutputCard(
onNetworkClick: () -> Unit,
onBluetoothClick: () -> Unit
) {
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
Text(
text = stringResource(id = R.string.prefs_net_title),
text = stringResource(id = R.string.prefs_data_output_title),
color = MaterialTheme.colorScheme.primary
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.prefs_net_switch))
Switch(checked = settings.rotatorState, onCheckedChange = { setRotatorState(it) })
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = settings.rotatorAddress,
singleLine = true,
label = { Text(text = stringResource(R.string.prefs_net_ip_hint)) },
onValueChange = { setRotatorAddress(it) },
modifier = Modifier.weight(1.5f),
enabled = settings.rotatorState
Spacer(modifier = Modifier.height(6.dp))
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
CardButton(
onClick = onNetworkClick,
text = stringResource(id = R.string.prefs_net_output),
modifier = Modifier.weight(1f)
)
OutlinedTextField(
value = settings.rotatorPort,
onValueChange = { setRotatorPort(it) },
label = { Text(text = stringResource(R.string.prefs_net_port_hint)) },
singleLine = true,
modifier = Modifier.weight(1f),
enabled = settings.rotatorState
CardButton(
onClick = onBluetoothClick,
text = stringResource(id = R.string.prefs_bt_output),
modifier = Modifier.weight(1f)
)
}
Spacer(modifier = Modifier.height(4.dp))
}
}
}
@Preview(showBackground = true)
@Composable
private fun BluetoothOutputCardPreview() = MainTheme {
val settings = RCSettings(
rotatorState = false,
rotatorAddress = "127.0.0.1",
rotatorPort = "4533",
bluetoothState = false,
bluetoothFormat = $$"W$AZ $EL",
bluetoothName = "Name",
bluetoothAddress = "00:0C:BF:13:80:5D"
)
BluetoothOutputCard(settings)
}
@Composable
private fun BluetoothOutputCard(
settings: RCSettings,
setBluetoothState: (Boolean) -> Unit = {},
setBluetoothAddress: (String) -> Unit = {},
setBluetoothFormat: (String) -> Unit = {}
) {
ElevatedCard(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)) {
Text(
text = stringResource(id = R.string.prefs_bt_title),
color = MaterialTheme.colorScheme.primary
)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.prefs_bt_switch))
Switch(checked = settings.bluetoothState, onCheckedChange = { setBluetoothState(it) })
}
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
value = settings.bluetoothAddress,
singleLine = true,
label = { Text(text = stringResource(R.string.prefs_bt_device_hint)) },
onValueChange = { setBluetoothAddress(it) },
modifier = Modifier.weight(1.5f),
enabled = settings.bluetoothState
)
OutlinedTextField(
value = settings.bluetoothFormat,
onValueChange = { setBluetoothFormat(it) },
label = { Text(text = stringResource(R.string.prefs_bt_output_hint)) },
singleLine = true,
modifier = Modifier.weight(1f),
enabled = settings.bluetoothState
)
}
Spacer(modifier = Modifier.height(4.dp))
}
}
}
@@ -509,7 +472,7 @@ private fun OtherCardPreview() = MainTheme {
stateOfUtc = false,
stateOfLightTheme = false,
shouldSeeWarning = false,
shouldSeeWelcome = false
shouldSeeWhatsNew = false
)
OtherCard(settings = values, {}, {}, {}, {})
}
@@ -71,10 +71,18 @@ sealed class RCAction {
data class SetRotatorState(val value: Boolean) : RCAction()
data class SetRotatorAddress(val value: String) : RCAction()
data class SetRotatorPort(val value: String) : RCAction()
data class SetBluetoothState(val value: Boolean) : RCAction()
data class SetBluetoothFormat(val value: String) : RCAction()
data class SetBluetoothName(val value: String) : RCAction()
data class SetBluetoothAddress(val value: String) : RCAction()
data class SetRotatorFormat(val value: String) : RCAction()
data class SetFrequencyState(val value: Boolean) : RCAction()
data class SetFrequencyAddress(val value: String) : RCAction()
data class SetFrequencyPort(val value: String) : RCAction()
data class SetFrequencyFormat(val value: String) : RCAction()
data class SetBluetoothRotatorState(val value: Boolean) : RCAction()
data class SetBluetoothRotatorFormat(val value: String) : RCAction()
data class SetBluetoothRotatorName(val value: String) : RCAction()
data class SetBluetoothRotatorAddress(val value: String) : RCAction()
data class SetBluetoothFrequencyState(val value: Boolean) : RCAction()
data class SetBluetoothFrequencyFormat(val value: String) : RCAction()
data class SetBluetoothFrequencyAddress(val value: String) : RCAction()
}
sealed class DataSourcesAction {
@@ -46,8 +46,7 @@ class SettingsViewModel(
private val databaseRepo: IDatabaseRepo,
private val satelliteRepo: ISatelliteRepo,
private val settingsRepo: ISettingsRepo,
private val showToast: IShowToast,
appVersionName: String
private val showToast: IShowToast
) : ViewModel() {
private val defaultPosSettings = PositionSettings(false, settingsRepo.stationPosition.value, 0)
@@ -63,7 +62,7 @@ class SettingsViewModel(
nextTime = "00:00:00",
isNextTimeAos = true,
nextPass = getDefaultPass(),
appVersionName = appVersionName,
appVersionName = settingsRepo.appVersionName,
positionSettings = defaultPosSettings,
dataSettings = defaultDataSettings,
otherSettings = settingsRepo.otherSettings.value,
@@ -152,10 +151,18 @@ class SettingsViewModel(
is RCAction.SetRotatorState -> settingsRepo.setRotatorState(action.value)
is RCAction.SetRotatorAddress -> settingsRepo.setRotatorAddress(action.value)
is RCAction.SetRotatorPort -> settingsRepo.setRotatorPort(action.value)
is RCAction.SetBluetoothState -> settingsRepo.setBluetoothState(action.value)
is RCAction.SetBluetoothAddress -> settingsRepo.setBluetoothAddress(action.value)
is RCAction.SetBluetoothFormat -> settingsRepo.setBluetoothFormat(action.value)
is RCAction.SetBluetoothName -> settingsRepo.setBluetoothName(action.value)
is RCAction.SetRotatorFormat -> settingsRepo.setRotatorFormat(action.value)
is RCAction.SetFrequencyState -> settingsRepo.setFrequencyState(action.value)
is RCAction.SetFrequencyAddress -> settingsRepo.setFrequencyAddress(action.value)
is RCAction.SetFrequencyPort -> settingsRepo.setFrequencyPort(action.value)
is RCAction.SetFrequencyFormat -> settingsRepo.setFrequencyFormat(action.value)
is RCAction.SetBluetoothRotatorState -> settingsRepo.setBluetoothRotatorState(action.value)
is RCAction.SetBluetoothRotatorAddress -> settingsRepo.setBluetoothRotatorAddress(action.value)
is RCAction.SetBluetoothRotatorFormat -> settingsRepo.setBluetoothRotatorFormat(action.value)
is RCAction.SetBluetoothRotatorName -> settingsRepo.setBluetoothRotatorName(action.value)
is RCAction.SetBluetoothFrequencyState -> settingsRepo.setBluetoothFrequencyState(action.value)
is RCAction.SetBluetoothFrequencyFormat -> settingsRepo.setBluetoothFrequencyFormat(action.value)
is RCAction.SetBluetoothFrequencyAddress -> settingsRepo.setBluetoothFrequencyAddress(action.value)
}
}
@@ -289,8 +296,7 @@ class SettingsViewModel(
container.databaseRepo,
container.satelliteRepo,
container.settingsRepo,
container.provideShowToast(),
container.provideAppVersionName()
container.provideShowToast()
)
}
}
+18 -6
View File
@@ -112,15 +112,27 @@
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_data_output_title">Data output</string>
<string name="prefs_net_output">Network</string>
<string name="prefs_bt_output">Bluetooth</string>
<string name="prefs_net_title">Salida de datos de red</string>
<string name="prefs_net_switch">Habilitar salida</string>
<string name="prefs_net_ip_hint">Dirección IP</string>
<string name="prefs_net_port_hint">Puerto</string>
<string name="prefs_net_rotator_switch">Enable rotation output</string>
<string name="prefs_net_rotator_ip_hint">Dirección IP</string>
<string name="prefs_net_rotator_port_hint">Puerto</string>
<string name="prefs_net_rotator_format_hint">Formato de datos</string>
<string name="prefs_net_frequency_switch">Enable frequency output</string>
<string name="prefs_net_frequency_ip_hint">Dirección IP</string>
<string name="prefs_net_frequency_port_hint">Puerto</string>
<string name="prefs_net_frequency_format_hint">Formato de datos</string>
<string name="prefs_bt_title">Salida por Bluetooth</string>
<string name="prefs_bt_switch">Habilitar salida</string>
<string name="prefs_bt_device_hint">Id dispositivo</string>
<string name="prefs_bt_output_hint">Formato de datos</string>
<string name="prefs_bt_rotator_switch">Enable rotation output</string>
<string name="prefs_bt_rotator_device_hint">Id dispositivo</string>
<string name="prefs_bt_rotator_output_hint">Formato de datos</string>
<string name="prefs_bt_frequency_switch">Enable frequency output</string>
<string name="prefs_bt_frequency_device_hint">Id dispositivo</string>
<string name="prefs_bt_frequency_output_hint">Formato de datos</string>
<string name="prefs_bt_perm_error">Revisa los permisos de Bluetooth</string>
<string name="prefs_other_title">Otros ajustes</string>
+18 -6
View File
@@ -111,15 +111,27 @@
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom Transceivers URL</string>
<string name="prefs_data_output_title">Вывод данных</string>
<string name="prefs_net_output">Сеть</string>
<string name="prefs_bt_output">Bluetooth</string>
<string name="prefs_net_title">Вывод сетевых данных</string>
<string name="prefs_net_switch">Включить вывод</string>
<string name="prefs_net_ip_hint">IP адрес</string>
<string name="prefs_net_port_hint">Порт</string>
<string name="prefs_net_rotator_switch">Включить вывод ротатора</string>
<string name="prefs_net_rotator_ip_hint">IP адрес</string>
<string name="prefs_net_rotator_port_hint">Порт</string>
<string name="prefs_net_rotator_format_hint">Формат</string>
<string name="prefs_net_frequency_switch">Включить вывод частоты</string>
<string name="prefs_net_frequency_ip_hint">IP адрес</string>
<string name="prefs_net_frequency_port_hint">Порт</string>
<string name="prefs_net_frequency_format_hint">Формат</string>
<string name="prefs_bt_title">Вывод данных Bluetooth</string>
<string name="prefs_bt_switch">Включить вывод</string>
<string name="prefs_bt_device_hint">Id устройства</string>
<string name="prefs_bt_output_hint">Формат данных</string>
<string name="prefs_bt_rotator_switch">Включить вывод ротатора</string>
<string name="prefs_bt_rotator_device_hint">Id устройства</string>
<string name="prefs_bt_rotator_output_hint">Формат</string>
<string name="prefs_bt_frequency_switch">Включить вывод частоты</string>
<string name="prefs_bt_frequency_device_hint">Id устройства</string>
<string name="prefs_bt_frequency_output_hint">Формат</string>
<string name="prefs_bt_perm_error">Нет разрешения использовать bluetooth</string>
<string name="prefs_other_title">Другие настройки</string>
+18 -6
View File
@@ -112,15 +112,27 @@
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_data_output_title">Data output</string>
<string name="prefs_net_output">Network</string>
<string name="prefs_bt_output">Bluetooth</string>
<string name="prefs_net_title">ජාල ප්‍රතිදානය</string>
<string name="prefs_net_switch">ප්‍රතිදානය සක්‍රීය කරන්න</string>
<string name="prefs_net_ip_hint">IP ලිපිනය</string>
<string name="prefs_net_port_hint">Port</string>
<string name="prefs_net_rotator_switch">Enable rotation output</string>
<string name="prefs_net_rotator_ip_hint">IP ලිපිනය</string>
<string name="prefs_net_rotator_port_hint">Port</string>
<string name="prefs_net_rotator_format_hint">දත්ත අකාරය</string>
<string name="prefs_net_frequency_switch">Enable frequency output</string>
<string name="prefs_net_frequency_ip_hint">IP ලිපිනය</string>
<string name="prefs_net_frequency_port_hint">Port</string>
<string name="prefs_net_frequency_format_hint">දත්ත අකාරය</string>
<string name="prefs_bt_title">Bluetooth දත්ත ප්‍රතිදානය</string>
<string name="prefs_bt_switch">ප්‍රතිදානය සක්‍රීය කරන්න</string>
<string name="prefs_bt_device_hint">උපාංග id</string>
<string name="prefs_bt_output_hint">දත්ත අකාරය</string>
<string name="prefs_bt_rotator_switch">Enable rotation output</string>
<string name="prefs_bt_rotator_device_hint">උපාංග id</string>
<string name="prefs_bt_rotator_output_hint">දත්ත අකාරය</string>
<string name="prefs_bt_frequency_switch">Enable frequency output</string>
<string name="prefs_bt_frequency_device_hint">උපාංග id</string>
<string name="prefs_bt_frequency_output_hint">දත්ත අකාරය</string>
<string name="prefs_bt_perm_error">Bluetooth අවසර පරික්‍ෂා ක°</string>
<string name="prefs_other_title">වෙනත් සැකසුම්</string>
+18 -6
View File
@@ -112,15 +112,27 @@
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_data_output_title">Data output</string>
<string name="prefs_net_output">Network</string>
<string name="prefs_bt_output">Bluetooth</string>
<string name="prefs_net_title">Вивід мережевих даних</string>
<string name="prefs_net_switch">Увімкнути вихід</string>
<string name="prefs_net_ip_hint">IP адреса</string>
<string name="prefs_net_port_hint">Порт</string>
<string name="prefs_net_rotator_switch">Enable rotation output</string>
<string name="prefs_net_rotator_ip_hint">IP адреса</string>
<string name="prefs_net_rotator_port_hint">Порт</string>
<string name="prefs_net_rotator_format_hint">Формат даних</string>
<string name="prefs_net_frequency_switch">Enable frequency output</string>
<string name="prefs_net_frequency_ip_hint">IP адреса</string>
<string name="prefs_net_frequency_port_hint">Порт</string>
<string name="prefs_net_frequency_format_hint">Формат даних</string>
<string name="prefs_bt_title">Вивід даних Bluetooth</string>
<string name="prefs_bt_switch">Увімкнути вихід</string>
<string name="prefs_bt_device_hint">Id пристрою</string>
<string name="prefs_bt_output_hint">Формат даних</string>
<string name="prefs_bt_rotator_switch">Enable rotation output</string>
<string name="prefs_bt_rotator_device_hint">Id пристрою</string>
<string name="prefs_bt_rotator_output_hint">Формат даних</string>
<string name="prefs_bt_frequency_switch">Enable frequency output</string>
<string name="prefs_bt_frequency_device_hint">Id пристрою</string>
<string name="prefs_bt_frequency_output_hint">Формат даних</string>
<string name="prefs_bt_perm_error">Перевірте дозвіл Bluetooth</string>
<string name="prefs_other_title">Інші налаштування</string>
+22 -10
View File
@@ -80,7 +80,7 @@
<string name="prefs_loc_title">站位</string>
<string name="prefs_loc_gps_title">GPS 定位</string>
<string name="prefs_loc_gps_error">检查您的位置权限</string>
<string name="prefs_loc_input_title">进入</string>
<string name="prefs_loc_input_title">输入位置</string>
<string name="prefs_loc_input_error">输入的位置无效</string>
<string name="prefs_loc_qth_title">QTH 网格</string>
<string name="prefs_loc_qth_error">无效的 QTH 网格</string>
@@ -100,19 +100,31 @@
<string name="prefs_data_clear_success">数据清空成功</string>
<string name="prefs_data_update_success">更新成功</string>
<string name="prefs_data_sources_title">Custom data sources</string>
<string name="prefs_data_sources_tle_switch">Custom TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_data_sources_title">自定义数据源</string>
<string name="prefs_data_sources_tle_switch">自定义TLE URL</string>
<string name="prefs_data_sources_transceivers_switch">自定义收发器URL</string>
<string name="prefs_data_output_title">数据输出</string>
<string name="prefs_net_output">网络</string>
<string name="prefs_bt_output">蓝牙</string>
<string name="prefs_net_title">网络数据输出</string>
<string name="prefs_net_switch">启用输出</string>
<string name="prefs_net_ip_hint">IP地址</string>
<string name="prefs_net_port_hint">端口</string>
<string name="prefs_net_rotator_switch">启用方位俯仰角输出</string>
<string name="prefs_net_rotator_ip_hint">IP地址</string>
<string name="prefs_net_rotator_port_hint">端口</string>
<string name="prefs_net_rotator_format_hint">数据格式</string>
<string name="prefs_net_frequency_switch">启用多普勒频率输出</string>
<string name="prefs_net_frequency_ip_hint">IP地址</string>
<string name="prefs_net_frequency_port_hint">端口</string>
<string name="prefs_net_frequency_format_hint">数据格式</string>
<string name="prefs_bt_title">蓝牙数据输出</string>
<string name="prefs_bt_switch">启用输出</string>
<string name="prefs_bt_device_hint">蓝牙设备 ID</string>
<string name="prefs_bt_output_hint">数据格式</string>
<string name="prefs_bt_rotator_switch">启用方位俯仰角输出</string>
<string name="prefs_bt_rotator_device_hint">蓝牙设备 ID</string>
<string name="prefs_bt_rotator_output_hint">数据格式</string>
<string name="prefs_bt_frequency_switch">启用多普勒频率输出</string>
<string name="prefs_bt_frequency_device_hint">蓝牙设备 ID</string>
<string name="prefs_bt_frequency_output_hint">数据格式</string>
<string name="prefs_bt_perm_error">请检查蓝牙权限</string>
<string name="prefs_other_title">其他设置</string>
+23 -6
View File
@@ -46,6 +46,11 @@
<string name="pass_welcome_message">
Make sure to set your position via GPS, Lat/Lon or QTH in Settings.
\n\nPlease update the database at least weekly to get accurate predictions.</string>
<string name="pass_whatsnew_title" translatable="false">What\'s new in Look4Sat</string>
<string name="pass_whatsnew_message" translatable="false">
• Londre7 added a remote frequency control feature (Hamlib/SDR++) by sending compatible messages via Network/BT.
Enable it in Settings and tap the transceiver (Radar Screen).
</string>
<!-- Radar screen -->
<string name="radar_back">Back</string>
@@ -136,15 +141,27 @@
<string name="prefs_data_sources_transceivers_switch">Custom transceivers URL</string>
<string name="prefs_data_sources_url_title" translatable="false">URL (HTTPS)</string>
<string name="prefs_data_output_title">Data output</string>
<string name="prefs_net_output">Network</string>
<string name="prefs_bt_output">Bluetooth</string>
<string name="prefs_net_title">Network data output</string>
<string name="prefs_net_switch">Enable output</string>
<string name="prefs_net_ip_hint">IP address</string>
<string name="prefs_net_port_hint">Port</string>
<string name="prefs_net_rotator_switch">Enable rotation output</string>
<string name="prefs_net_rotator_ip_hint">IP address</string>
<string name="prefs_net_rotator_port_hint">Port</string>
<string name="prefs_net_rotator_format_hint">Format</string>
<string name="prefs_net_frequency_switch">Enable frequency output</string>
<string name="prefs_net_frequency_ip_hint">IP address</string>
<string name="prefs_net_frequency_port_hint">Port</string>
<string name="prefs_net_frequency_format_hint">Format</string>
<string name="prefs_bt_title">Bluetooth data output</string>
<string name="prefs_bt_switch">Enable output</string>
<string name="prefs_bt_device_hint">Device id</string>
<string name="prefs_bt_output_hint">Data format</string>
<string name="prefs_bt_rotator_switch">Enable rotation output</string>
<string name="prefs_bt_rotator_device_hint">Device id</string>
<string name="prefs_bt_rotator_output_hint">Data format</string>
<string name="prefs_bt_frequency_switch">Enable frequency output</string>
<string name="prefs_bt_frequency_device_hint">Device id</string>
<string name="prefs_bt_frequency_output_hint">Data format</string>
<string name="prefs_bt_perm_error">Check your bluetooth permission</string>
<string name="prefs_other_title">Other settings</string>
@@ -18,6 +18,7 @@
package com.rtbishop.look4sat.data.framework
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothSocket
import android.util.Log
import java.io.OutputStream
import java.util.*
@@ -25,69 +26,110 @@ import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import com.rtbishop.look4sat.domain.repository.IReporterParams
import com.rtbishop.look4sat.domain.repository.IReporterRepo
data class WithoutExtParams(
val dummy: Int
) : IReporterParams
enum class BtService { ROTATOR, FREQUENCY }
data class DeviceConnection(
var socket: BluetoothSocket? = null,
var outputStream: OutputStream? = null,
var connected: Boolean = false,
var connecting: Boolean = false,
var connectionJob: Job? = null
)
class BluetoothReporter(
private val bluetoothManager: BluetoothManager,
private val reporterScope: CoroutineScope
) {
) : IReporterRepo<WithoutExtParams> {
private val tag = "BTReporter"
private val sppid: UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb")
private lateinit var outputStream: OutputStream
private var rotationConnectionJob: Job? = null
private var rotationReportingJob: Job? = null
private var connected = false
private var connecting = false
fun isConnected(): Boolean = connected
private val serviceToDevice = mutableMapOf<BtService, String>()
private val deviceConnections = mutableMapOf<String, DeviceConnection>()
private val writeMutex = Mutex()
fun isConnecting(): Boolean = connecting
fun isConnected(service: BtService): Boolean {
val deviceId = serviceToDevice[service] ?: return false
return deviceConnections[deviceId]?.connected == true
}
fun isConnecting(service: BtService): Boolean {
val deviceId = serviceToDevice[service] ?: return false
return deviceConnections[deviceId]?.connecting == true
}
fun connectBTDevice(deviceId: String) {
if (!connected) {
rotationConnectionJob = reporterScope.launch {
try {
bluetoothManager.adapter.getRemoteDevice(deviceId)?.let { device ->
device.createInsecureRfcommSocketToServiceRecord(sppid)?.let { socket ->
connecting = true
socket.connect()
outputStream = socket.outputStream
connected = true
connecting = false
Log.i(tag, "$tag: Connected!")
}
}
} catch (e: SecurityException) {
Log.e(tag, "$tag: ${e.message}")
} catch (e: Exception) {
Log.e(tag, "$tag: ${e.message}")
}
fun connect(service: BtService, deviceId: String) {
serviceToDevice[service] = deviceId
val connection = deviceConnections.getOrPut(deviceId) {
DeviceConnection()
}
if (connection.connected || connection.connecting) return
connection.connectionJob = reporterScope.launch {
try {
connection.connecting = true
val device = bluetoothManager.adapter.getRemoteDevice(deviceId)
val socket = device.createInsecureRfcommSocketToServiceRecord(sppid)
socket.connect()
connection.socket = socket
connection.outputStream = socket.outputStream
connection.connected = true
Log.i(tag, "$tag: Connected to $deviceId")
} catch (e: Exception) {
Log.e(tag, "$tag: ${e.message}")
connection.connected = false
} finally {
connection.connecting = false
}
}
}
fun reportRotation(format: String, azimuth: Int, elevation: Int) {
if (connected) {
rotationReportingJob = reporterScope.launch {
val newElevation = if (elevation > 0) elevation else 0
try {
val azimuthString = intToStringWithLeadingZeroes(azimuth)
val elevationString = intToStringWithLeadingZeroes(newElevation)
val crChar = '\r'
val nlChar = '\n'
val tbChar = '\t'
var buffer = format.replace("\$AZ", azimuthString)
buffer = buffer.replace("\$EL", elevationString)
buffer = buffer.replace("\\r", crChar.toString())
buffer = buffer.replace("\\n", nlChar.toString())
buffer = buffer.replace("\\t", tbChar.toString())
Log.i(tag, "$tag: Sending $buffer")
if (connected) outputStream.write(buffer.toByteArray())
} catch (e: Exception) {
Log.e(tag, "$tag: ${e.message}")
connected = false
}
private suspend fun write(service: BtService, buffer: String) {
val deviceId = serviceToDevice[service] ?: return
val connection = deviceConnections[deviceId] ?: return
if (!connection.connected) return
try {
writeMutex.withLock {
connection.outputStream?.write(buffer.toByteArray())
}
} catch (e: Exception) {
Log.e(tag, "$tag: Write failed ${e.message}")
connection.connected = false
}
}
override fun reportRotation(format: String, azimuth: Double, elevation: Double, params: WithoutExtParams) {
reporterScope.launch {
if (!isConnected(BtService.ROTATOR)) return@launch
val newElevation = if (elevation > 0.0) elevation else 0.0
val azimuthString = intToStringWithLeadingZeroes(azimuth.toInt())
val elevationString = intToStringWithLeadingZeroes(newElevation.toInt())
var buffer = format
.replace("\$AZ", azimuthString)
.replace("\$EL", elevationString)
.replace("\\r", "\r")
.replace("\\n", "\n")
.replace("\\t", "\t")
write(BtService.ROTATOR, buffer)
}
}
override fun reportFrequency(format: String, frequency: Long, params: WithoutExtParams) {
reporterScope.launch {
if (!isConnected(BtService.FREQUENCY)) return@launch
var buffer = format
.replace("\$FREQ", frequency.toString())
.replace("\\r", "\r")
.replace("\\n", "\n")
.replace("\\t", "\t")
write(BtService.FREQUENCY, buffer)
}
}
@@ -22,49 +22,94 @@ import java.nio.ByteBuffer
import java.nio.channels.SocketChannel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.launch
import com.rtbishop.look4sat.domain.repository.IReporterParams
import com.rtbishop.look4sat.domain.repository.IReporterRepo
class NetworkReporter(private val reporterScope: CoroutineScope) {
data class ExtendedParams(
val server: String,
val port: Int
) : IReporterParams
private var rotationSocketChannel: SocketChannel? = null
private var rotationReporting: Job? = null
enum class NetworkService { ROTATOR, FREQUENCY }
// private var frequencySocketChannel: SocketChannel? = null
// private var frequencyReporting: Job? = null
//
// fun reportFrequency(server: String, port: Int, frequency: Long) {
// frequencyReporting = reporterScope.launch {
// runCatching {
// if (frequencySocketChannel == null) {
// frequencySocketChannel = SocketChannel.open(InetSocketAddress(server, port))
// } else {
// val buffer = ByteBuffer.wrap("\\set_freq $frequency\n".toByteArray())
// frequencySocketChannel?.write(buffer)
// }
// }.onFailure { error ->
// println(error.localizedMessage)
// frequencySocketChannel = null
// frequencyReporting?.cancelAndJoin()
// }
// }
// }
data class SocketConnection(
var socket: SocketChannel? = null,
var connected: Boolean = false,
var connecting: Boolean = false,
var connectionJob: Job? = null
)
fun reportRotation(server: String, port: Int, azimuth: Double, elevation: Double) {
rotationReporting = reporterScope.launch {
val newElevation = if (elevation > 0.0) elevation else 0.0
runCatching {
if (rotationSocketChannel == null) {
rotationSocketChannel = SocketChannel.open(InetSocketAddress(server, port))
} else {
val buffer = ByteBuffer.wrap("\\P $azimuth $newElevation\n".toByteArray())
rotationSocketChannel?.write(buffer)
}
}.onFailure { error ->
println(error.localizedMessage)
rotationSocketChannel = null
rotationReporting?.cancelAndJoin()
class NetworkReporter(private val reporterScope: CoroutineScope) : IReporterRepo<ExtendedParams> {
private val serviceToAddress = mutableMapOf<NetworkService, String>()
private val connections = mutableMapOf<String, SocketConnection>()
private val writeMutex = Mutex()
fun isConnected(service: NetworkService): Boolean {
val addr = serviceToAddress[service] ?: return false
return connections[addr]?.connected == true
}
private fun getOrCreateConnection(addr: String): SocketConnection {
return connections.getOrPut(addr) { SocketConnection() }
}
fun connect(service: NetworkService, server: String, port: Int) {
val addr = "$server:$port"
serviceToAddress[service] = addr
val connection = getOrCreateConnection(addr)
if (connection.connected || connection.connecting) return
connection.connectionJob = reporterScope.launch {
try {
connection.connecting = true
val socket = SocketChannel.open(InetSocketAddress(server, port))
connection.socket = socket
connection.connected = true
println("NetworkReporter: $service connected to $addr")
} catch (e: Exception) {
println("NetworkReporter connect error: ${e.message}")
connection.connected = false
} finally {
connection.connecting = false
}
}
}
private suspend fun write(service: NetworkService, command: String) {
val addr = serviceToAddress[service] ?: return
val connection = connections[addr] ?: return
if (!connection.connected) return
try {
writeMutex.withLock {
val buffer = ByteBuffer.wrap("\\$command\n".toByteArray())
connection.socket?.write(buffer)
}
} catch (e: Exception) {
println("NetworkReporter write error: ${e.message}")
connection.connected = false
}
}
override fun reportRotation(format: String, azimuth: Double, elevation: Double, params: ExtendedParams) {
reporterScope.launch {
connect(NetworkService.ROTATOR, params.server, params.port)
if (!isConnected(NetworkService.ROTATOR)) return@launch
val el = if (elevation > 0.0) elevation else 0.0
val command = format
.replace("\$AZ", azimuth.toString())
.replace("\$EL", el.toString())
write(NetworkService.ROTATOR, command)
}
}
override fun reportFrequency(format: String, frequency: Long, params: ExtendedParams) {
reporterScope.launch {
connect(NetworkService.FREQUENCY, params.server, params.port)
if (!isConnected(NetworkService.FREQUENCY)) return@launch
val command = format.replace("\$FREQ", frequency.toString())
write(NetworkService.FREQUENCY, command)
}
}
}
@@ -61,10 +61,6 @@ class MainContainer(private val context: Context) {
val satelliteRepo = provideSatelliteRepo()
val databaseRepo = provideDatabaseRepo()
fun provideAppVersionName(): String {
return context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "4.0.0"
}
fun provideAddToCalendar(): IAddToCalendar = AddToCalendar(context)
fun provideShowToast(): IShowToast = ShowToast(context)
@@ -111,6 +107,7 @@ class MainContainer(private val context: Context) {
val manager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val appPrefsFileName = "${context.packageName}_preferences"
val appPreferences = context.getSharedPreferences(appPrefsFileName, Context.MODE_PRIVATE)
return SettingsRepo(manager, appPreferences)
val appVersionName = context.packageManager.getPackageInfo(context.packageName, 0).versionName ?: "4.0.4"
return SettingsRepo(manager, appPreferences, appVersionName)
}
}
@@ -39,13 +39,17 @@ import kotlinx.coroutines.flow.update
class SettingsRepo(
private val locationManager: LocationManager,
private val preferences: SharedPreferences
private val preferences: SharedPreferences,
override val appVersionName: String
) : ISettingsRepo, LocationListenerCompat {
private val keyBluetoothAddress = "bluetoothAddress"
private val keyBluetoothName = "bluetoothName"
private val keyBluetoothFormat = "bluetoothFormat"
private val keyBluetoothState = "bluetoothState"
private val keyBluetoothRotatorAddress = "bluetoothAddress"
private val keyBluetoothRotatorName = "bluetoothName"
private val keyBluetoothRotatorFormat = "bluetoothFormat"
private val keyBluetoothRotatorState = "bluetoothState"
private val keyBluetoothFrequencyState = "bluetoothFrequencyState"
private val keyBluetoothFrequencyAddress = "bluetoothFrequencyAddress"
private val keyBluetoothFrequencyFormat = "bluetoothFrequencyFormat"
private val keyFilterHoursAhead = "filterHoursAhead"
private val keyFilterMinElevation = "filterMinElevation"
private val keyNumberOfRadios = "numberOfRadios"
@@ -53,6 +57,11 @@ class SettingsRepo(
private val keyRotatorAddress = "rotatorAddress"
private val keyRotatorPort = "rotatorPort"
private val keyRotatorState = "rotatorState"
private val keyRotatorFormat = "rotatorFormat"
private val keyFrequencyState = "frequencyState"
private val keyFrequencyAddress = "frequencyAddress"
private val keyFrequencyPort = "frequencyPort"
private val keyFrequencyFormat = "frequencyFormat"
private val keySelectedIds = "selectedIds"
private val keySelectedTypes = "selectedTypes"
private val keySelectedModes = "selectedModes"
@@ -68,7 +77,7 @@ class SettingsRepo(
private val keyStationTimestamp = "stationTimestamp"
private val keyUpdateTimestamp = "updateTimestamp"
private val keyShouldSeeWarning = "shouldSeeWarning"
private val keyShouldSeeWelcome = "shouldSeeWelcome"
private val keyShouldSeeWhatsNew = "shouldSeeWhatsNew_v$appVersionName"
private val keyUseCustomTle = "useCustomTle"
private val keyUseCustomTransceivers = "useCustomTransceivers"
private val keyTleUrl = "tleUrl"
@@ -240,24 +249,39 @@ class SettingsRepo(
private val _rcSettings = MutableStateFlow(getRCSettings())
override val rcSettings: StateFlow<RCSettings> = _rcSettings
override fun setBluetoothAddress(value: String) {
preferences.edit { putString(keyBluetoothAddress, value) }
_rcSettings.update { it.copy(bluetoothAddress = value) }
override fun setBluetoothRotatorAddress(value: String) {
preferences.edit { putString(keyBluetoothRotatorAddress, value) }
_rcSettings.update { it.copy(bluetoothRotatorAddress = value) }
}
override fun setBluetoothFormat(value: String) {
preferences.edit { putString(keyBluetoothFormat, value) }
_rcSettings.update { it.copy(bluetoothFormat = value) }
override fun setBluetoothRotatorFormat(value: String) {
preferences.edit { putString(keyBluetoothRotatorFormat, value) }
_rcSettings.update { it.copy(bluetoothRotatorFormat = value) }
}
override fun setBluetoothName(value: String) {
preferences.edit { putString(keyBluetoothName, value) }
_rcSettings.update { it.copy(bluetoothName = value) }
override fun setBluetoothRotatorName(value: String) {
preferences.edit { putString(keyBluetoothRotatorName, value) }
_rcSettings.update { it.copy(bluetoothRotatorName = value) }
}
override fun setBluetoothState(value: Boolean) {
preferences.edit { putBoolean(keyBluetoothState, value) }
_rcSettings.update { it.copy(bluetoothState = value) }
override fun setBluetoothRotatorState(value: Boolean) {
preferences.edit { putBoolean(keyBluetoothRotatorState, value) }
_rcSettings.update { it.copy(bluetoothRotatorState = value) }
}
override fun setBluetoothFrequencyState(value: Boolean) {
preferences.edit { putBoolean(keyBluetoothFrequencyState, value) }
_rcSettings.update { it.copy(bluetoothFrequencyState = value) }
}
override fun setBluetoothFrequencyAddress(value: String) {
preferences.edit { putString(keyBluetoothFrequencyAddress, value) }
_rcSettings.update { it.copy(bluetoothFrequencyAddress = value) }
}
override fun setBluetoothFrequencyFormat(value: String) {
preferences.edit { putString(keyBluetoothFrequencyFormat, value) }
_rcSettings.update { it.copy(bluetoothFrequencyFormat = value) }
}
override fun setRotatorAddress(value: String) {
@@ -275,14 +299,47 @@ class SettingsRepo(
_rcSettings.update { it.copy(rotatorState = value) }
}
override fun setRotatorFormat(value: String) {
preferences.edit { putString(keyRotatorFormat, value) }
_rcSettings.update { it.copy(rotatorFormat = value) }
}
override fun setFrequencyState(value: Boolean) {
preferences.edit { putBoolean(keyFrequencyState, value) }
_rcSettings.update { it.copy(frequencyState = value) }
}
override fun setFrequencyAddress(value: String) {
preferences.edit { putString(keyFrequencyAddress, value) }
_rcSettings.update { it.copy(frequencyAddress = value) }
}
override fun setFrequencyPort(value: String) {
preferences.edit { putString(keyFrequencyPort, value) }
_rcSettings.update { it.copy(frequencyPort = value) }
}
override fun setFrequencyFormat(value: String) {
preferences.edit { putString(keyFrequencyFormat, value) }
_rcSettings.update { it.copy(frequencyFormat = value) }
}
private fun getRCSettings(): RCSettings = RCSettings(
rotatorState = preferences.getBoolean(keyRotatorState, false),
rotatorAddress = preferences.getString(keyRotatorAddress, null) ?: "127.0.0.1",
rotatorPort = preferences.getString(keyRotatorPort, null) ?: "4533",
bluetoothState = preferences.getBoolean(keyBluetoothState, false),
bluetoothFormat = preferences.getString(keyBluetoothFormat, null) ?: $$"W$AZ $EL",
bluetoothName = preferences.getString(keyBluetoothName, null) ?: "Default",
bluetoothAddress = preferences.getString(keyBluetoothAddress, null) ?: "00:0C:BF:13:80:5D"
rotatorFormat = preferences.getString(keyRotatorFormat, null) ?: $$"P $AZ $EL",
frequencyState = preferences.getBoolean(keyFrequencyState, false),
frequencyAddress = preferences.getString(keyFrequencyAddress, null) ?: "127.0.0.1",
frequencyPort = preferences.getString(keyFrequencyPort, null) ?: "4532",
frequencyFormat = preferences.getString(keyFrequencyFormat, null) ?: $$"set_freq $FREQ",
bluetoothRotatorState = preferences.getBoolean(keyBluetoothRotatorState, false),
bluetoothRotatorFormat = preferences.getString(keyBluetoothRotatorFormat, null) ?: $$"W$AZ $EL",
bluetoothRotatorName = preferences.getString(keyBluetoothRotatorName, null) ?: "Default",
bluetoothRotatorAddress = preferences.getString(keyBluetoothRotatorAddress, null) ?: "00:0C:BF:13:80:5D",
bluetoothFrequencyState = preferences.getBoolean(keyBluetoothFrequencyState, false),
bluetoothFrequencyAddress = preferences.getString(keyBluetoothFrequencyAddress, null) ?: "00:0C:BF:13:80:5D",
bluetoothFrequencyFormat = preferences.getString(keyBluetoothFrequencyFormat, null) ?: $$"FA$FREQ"
)
//endregion
@@ -320,9 +377,9 @@ class SettingsRepo(
_otherSettings.update { it.copy(shouldSeeWarning = false) }
}
override fun setWelcomeDismissed() {
preferences.edit { putBoolean(keyShouldSeeWelcome, false) }
_otherSettings.update { it.copy(shouldSeeWelcome = false) }
override fun setWhatsNewDismissed() {
preferences.edit { putBoolean(keyShouldSeeWhatsNew, false) }
_otherSettings.update { it.copy(shouldSeeWhatsNew = false) }
}
private fun getOtherSettings(): OtherSettings = OtherSettings(
@@ -332,7 +389,7 @@ class SettingsRepo(
stateOfUtc = preferences.getBoolean(keyStateOfUtc, false),
stateOfLightTheme = preferences.getBoolean(keyStateOfLightTheme, false),
shouldSeeWarning = preferences.getBoolean(keyShouldSeeWarning, true),
shouldSeeWelcome = preferences.getBoolean(keyShouldSeeWelcome, true)
shouldSeeWhatsNew = preferences.getBoolean(keyShouldSeeWhatsNew, true)
)
//endregion
@@ -33,10 +33,18 @@ data class RCSettings(
val rotatorState: Boolean,
val rotatorAddress: String,
val rotatorPort: String,
val bluetoothState: Boolean,
val bluetoothFormat: String,
val bluetoothName: String,
val bluetoothAddress: String,
val rotatorFormat: String,
val frequencyState: Boolean,
val frequencyAddress: String,
val frequencyPort: String,
val frequencyFormat: String,
val bluetoothRotatorState: Boolean,
val bluetoothRotatorFormat: String,
val bluetoothRotatorName: String,
val bluetoothRotatorAddress: String,
val bluetoothFrequencyState: Boolean,
val bluetoothFrequencyFormat: String,
val bluetoothFrequencyAddress: String
)
data class OtherSettings(
@@ -46,7 +54,7 @@ data class OtherSettings(
val stateOfUtc: Boolean,
val stateOfLightTheme: Boolean,
val shouldSeeWarning: Boolean,
val shouldSeeWelcome: Boolean
val shouldSeeWhatsNew: Boolean
)
data class DataSourcesSettings(
@@ -0,0 +1,25 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2026 Arty Bishop and contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.domain.repository
interface IReporterParams
interface IReporterRepo<T : IReporterParams> {
fun reportRotation(format: String, azimuth: Double, elevation: Double, params: T)
fun reportFrequency(format: String, frequency: Long, params: T)
}
@@ -27,6 +27,8 @@ import kotlinx.coroutines.flow.StateFlow
interface ISettingsRepo {
val appVersionName: String
//region # Satellites selection settings
val selectedIds: StateFlow<List<Int>>
val selectedTypes: StateFlow<List<String>>
@@ -55,13 +57,21 @@ interface ISettingsRepo {
//region # RC settings
val rcSettings: StateFlow<RCSettings>
fun setBluetoothAddress(value: String)
fun setBluetoothFormat(value: String)
fun setBluetoothName(value: String)
fun setBluetoothState(value: Boolean)
fun setBluetoothRotatorAddress(value: String)
fun setBluetoothRotatorFormat(value: String)
fun setBluetoothRotatorName(value: String)
fun setBluetoothRotatorState(value: Boolean)
fun setBluetoothFrequencyAddress(value: String)
fun setBluetoothFrequencyFormat(value: String)
fun setBluetoothFrequencyState(value: Boolean)
fun setRotatorAddress(value: String)
fun setRotatorPort(value: String)
fun setRotatorState(value: Boolean)
fun setRotatorFormat(value: String)
fun setFrequencyAddress(value: String)
fun setFrequencyPort(value: String)
fun setFrequencyState(value: Boolean)
fun setFrequencyFormat(value: String)
//endregion
//region # Other settings
@@ -72,7 +82,7 @@ interface ISettingsRepo {
fun setStateOfUtc(value: Boolean)
fun setStateOfLightTheme(value: Boolean)
fun setWarningDismissed()
fun setWelcomeDismissed()
fun setWhatsNewDismissed()
//endregion
//region # Transceivers settings
@@ -23,7 +23,6 @@ object Sources {
"All" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=csv",
"Amateur" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=csv",
"Brightest" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=visual&FORMAT=csv",
"Classified" to "https://www.mmccants.org/tles/classfd.zip",
"Cubesat" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=cubesat&FORMAT=csv",
"Education" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=education&FORMAT=csv",
"Engineer" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=engineering&FORMAT=csv",
@@ -32,12 +31,10 @@ object Sources {
"GNSS" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=gnss&FORMAT=csv",
"Intelsat" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=intelsat&FORMAT=csv",
"Iridium" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=iridium-NEXT&FORMAT=csv",
"McCants" to "https://www.mmccants.org/tles/inttles.zip",
"Military" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=military&FORMAT=csv",
"New" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=last-30-days&FORMAT=csv",
"OneWeb" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=oneweb&FORMAT=csv",
"Orbcomm" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=orbcomm&FORMAT=csv",
"R4UAB" to "https://r4uab.ru/satonline.txt",
"Resource" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=resource&FORMAT=csv",
"SatNOGS" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=satnogs&FORMAT=csv",
"Science" to "https://celestrak.org/NORAD/elements/gp.php?GROUP=science&FORMAT=csv",
@@ -47,6 +44,9 @@ object Sources {
"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",
"Classified" to "https://www.mmccants.org/tles/classfd.zip",
"McCants" to "https://www.mmccants.org/tles/inttles.zip",
"R4UAB" to "https://r4uab.ru/satonline.txt",
"Other" to "" // key for sats filter
)
}
@@ -1,3 +1,3 @@
Added minor tweaks for UK translation, by Eugene #188
Added custom data sources import, by londre7 #189
Added various UI fixes, updated dependencies
Added What's New dialog to announce changes and features
Added remote frequency control feature, by londre7 #195
Updated the Chinese translation, by wty2019wty #196
@@ -1,3 +1,3 @@
Added minor tweaks for UK translation, by Eugene #188
Added custom data sources import, by londre7 #189
Added various UI fixes, updated dependencies
Added What's New dialog to announce changes and features
Added remote frequency control feature, by londre7 #195
Updated the Chinese translation, by wty2019wty #196
+13
View File
@@ -0,0 +1,13 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/10fc3bf1ee0001078a473afe6e43cfdb/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/658299a896470fbb3103ba3a430ee227/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/67a0fee3c4236b6397dcbe8575ca2011/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/23adb857f3cb3cbe28750bc7faa7abc0/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/932015f6361ccaead0c6d9b8717ed96e/redirect
toolchainVendor=JETBRAINS
toolchainVersion=21
+5 -5
View File
@@ -1,14 +1,14 @@
[versions]
android-gradle-plugin = "9.0.0"
google-ksp = "2.3.2"
android-gradle-plugin = "9.0.1"
google-ksp = "2.3.5"
kotlin = "2.3.10"
androidx-core-ktx = "1.17.0"
androidx-core-splashscreen = "1.2.0"
androidx-room = "2.8.4"
compose-bom = "2026.01.01"
compose-activity = "1.12.3"
compose-bom = "2026.02.00"
compose-activity = "1.12.4"
compose-lifecycle = "2.10.0"
compose-navigation = "2.9.7"
@@ -34,7 +34,7 @@ compose-animation = { group = "androidx.compose.animation", name = "animation" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-material3-navigation = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite" }
compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
compose-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
compose-tooling = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "compose-activity" }
compose-lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "compose-lifecycle" }
-3
View File
@@ -5,9 +5,6 @@ pluginManagement {
gradlePluginPortal()
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {