diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c286571e..b6c72e76 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,7 +26,6 @@ android { buildTypes { debug { applicationIdSuffix = ".debug" - versionNameSuffix = "-debug" } release { isMinifyEnabled = true diff --git a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt index e1ec2f9a..e4fc2b9f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainContainer.kt @@ -17,7 +17,6 @@ import com.rtbishop.look4sat.data.repository.SettingsRepo import com.rtbishop.look4sat.data.source.LocalSource import com.rtbishop.look4sat.data.source.RemoteSource import com.rtbishop.look4sat.data.usecase.AddToCalendar -import com.rtbishop.look4sat.data.usecase.OpenWebUrl import com.rtbishop.look4sat.data.usecase.ShowToast import com.rtbishop.look4sat.domain.repository.IDatabaseRepo import com.rtbishop.look4sat.domain.repository.ISatelliteRepo @@ -27,7 +26,6 @@ import com.rtbishop.look4sat.domain.repository.ISettingsRepo import com.rtbishop.look4sat.domain.source.ILocalSource import com.rtbishop.look4sat.domain.source.IRemoteSource import com.rtbishop.look4sat.domain.usecase.IAddToCalendar -import com.rtbishop.look4sat.domain.usecase.IOpenWebUrl import com.rtbishop.look4sat.domain.usecase.IShowToast import com.rtbishop.look4sat.domain.utility.DataParser import kotlinx.coroutines.CoroutineExceptionHandler @@ -58,10 +56,6 @@ class MainContainer(private val context: Context) { return AddToCalendar(context) } - fun provideOpenWebUrl(): IOpenWebUrl { - return OpenWebUrl(context) - } - fun provideShowToast(): IShowToast { return ShowToast(context) } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/MainTheme.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/MainTheme.kt index 40b0d080..b8e2a76c 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/MainTheme.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/MainTheme.kt @@ -115,8 +115,8 @@ private val darkScheme = darkColorScheme( private val shapes = Shapes( extraSmall = RoundedCornerShape(4.dp), // menus, snackbars, text fields - small = RoundedCornerShape(6.dp), // buttons, chips - medium = RoundedCornerShape(8.dp), // cards, small FABs + small = RoundedCornerShape(8.dp), // buttons, chips + medium = RoundedCornerShape(12.dp), // cards, small FABs large = RoundedCornerShape(24.dp), // extended FABs, FABs, nav drawers extraLarge = RoundedCornerShape(32.dp) // large FABs ) 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 d3abc6a8..74d35a13 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 @@ -16,8 +16,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ElevatedButton import androidx.compose.material3.ElevatedCard @@ -207,6 +207,19 @@ fun IconCard(action: () -> Unit, resId: Int, modifier: Modifier = Modifier) { } } +@Composable +fun PrimaryIconCard(modifier: Modifier = Modifier, resId: Int, onClick: () -> Unit) { + val clickableMod = modifier.clickable { onClick() } + ElevatedCard( + modifier = Modifier.size(102.dp, 48.dp), + colors = CardDefaults.elevatedCardColors(containerColor = MaterialTheme.colorScheme.primary) + ) { + Box(modifier = clickableMod.fillMaxSize(), contentAlignment = Alignment.Center) { + Icon(painter = painterResource(id = resId), contentDescription = null) + } + } +} + @Composable fun CardLoadingIndicator() { Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) { @@ -320,7 +333,6 @@ fun ScreenColumn( floatingBar: @Composable () -> Unit = {}, content: @Composable (Boolean) -> Unit = {} ) { - val surfaceCorners = LocalSpacing.current.large val isVerticalLayout = isVerticalLayout() Surface(color = MaterialTheme.colorScheme.background) { Box(modifier = Modifier.layoutPadding(), contentAlignment = Alignment.BottomCenter) { @@ -328,7 +340,6 @@ fun ScreenColumn( topBar(isVerticalLayout) Surface( modifier = Modifier.fillMaxSize(), - shape = RoundedCornerShape(topStart = surfaceCorners, topEnd = surfaceCorners), color = MaterialTheme.colorScheme.surfaceContainer ) { content(isVerticalLayout) } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt index 208c1050..c8bd5a32 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/satellites/SatellitesScreen.kt @@ -3,19 +3,16 @@ package com.rtbishop.look4sat.presentation.satellites import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize 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.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.text.BasicTextField -import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon @@ -49,6 +46,7 @@ import com.rtbishop.look4sat.presentation.common.CardLoadingIndicator import com.rtbishop.look4sat.presentation.common.EmptyListCard import com.rtbishop.look4sat.presentation.common.IconCard import com.rtbishop.look4sat.presentation.common.InfoDialog +import com.rtbishop.look4sat.presentation.common.PrimaryIconCard import com.rtbishop.look4sat.presentation.common.TopBar import com.rtbishop.look4sat.presentation.common.infiniteMarquee import com.rtbishop.look4sat.presentation.common.isVerticalLayout @@ -88,7 +86,7 @@ private fun SatellitesScreen(uiState: SatellitesState, navigateUp: () -> Unit) { if (isVerticalLayout()) { TopBar { TypeCard(types = uiState.currentTypes, toggleDialog, modifier = Modifier.weight(1f)) - SaveButton(saveSelection = saveSelection, modifier = Modifier.height(48.dp)) + PrimaryIconCard(onClick = saveSelection, resId = R.drawable.ic_done) } TopBar { SearchBar(setQuery = { setQuery(it) }, modifier = Modifier.weight(1f)) @@ -97,7 +95,7 @@ private fun SatellitesScreen(uiState: SatellitesState, navigateUp: () -> Unit) { } } else { TopBar { - SaveButton(saveSelection = saveSelection, modifier = Modifier.height(48.dp)) + PrimaryIconCard(onClick = saveSelection, resId = R.drawable.ic_done) TypeCard(types = uiState.currentTypes, toggleDialog, modifier = Modifier.weight(1f)) SearchBar(setQuery = { setQuery(it) }, modifier = Modifier.weight(1f)) IconCard(action = unselectAll, resId = R.drawable.ic_check_off) @@ -163,19 +161,6 @@ private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier) } } -@Composable -private fun SaveButton(saveSelection: () -> Unit, modifier: Modifier = Modifier) { - val clickableModifier = modifier.clickable { saveSelection() } - ElevatedCard( - modifier = Modifier.width(102.dp), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary) - ) { - Box(modifier = clickableModifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Icon(painter = painterResource(id = R.drawable.ic_done), contentDescription = null) - } - } -} - @Composable private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifier = Modifier) { val typesText = if (types.isEmpty()) "All" else types.joinToString(", ") @@ -187,7 +172,11 @@ private fun TypeCard(types: List, onClick: () -> Unit, modifier: Modifie .height(48.dp) .clickable { onClick() }) { Spacer(Modifier) - Icon(painter = painterResource(id = R.drawable.ic_tags), contentDescription = null) + Icon( + painter = painterResource(id = R.drawable.ic_tags), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) Text( text = "Tags: $typesText", fontSize = 16.sp, 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 36743ce6..2b49fe9a 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 @@ -4,6 +4,7 @@ import android.Manifest import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -11,7 +12,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -27,6 +27,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -45,7 +46,11 @@ import com.rtbishop.look4sat.domain.predict.GeoPos import com.rtbishop.look4sat.presentation.MainTheme import com.rtbishop.look4sat.presentation.Screen import com.rtbishop.look4sat.presentation.common.CardButton -import com.rtbishop.look4sat.presentation.common.layoutPadding +import com.rtbishop.look4sat.presentation.common.IconCard +import com.rtbishop.look4sat.presentation.common.PrimaryIconCard +import com.rtbishop.look4sat.presentation.common.ScreenColumn +import com.rtbishop.look4sat.presentation.common.TopBar +import com.rtbishop.look4sat.presentation.common.infiniteMarquee import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -61,9 +66,6 @@ fun NavGraphBuilder.settingsDestination() { } } -private const val POLICY_URL = "https://sites.google.com/view/look4sat-privacy-policy/home" -private const val LICENSE_URL = "https://www.gnu.org/licenses/gpl-3.0.html" - @Composable private fun SettingsScreen(uiState: SettingsState) { // Permissions setup @@ -127,99 +129,64 @@ private fun SettingsScreen(uiState: SettingsState) { val toggleUpdate = { value: Boolean -> uiState.sendAction(SettingsAction.ToggleUpdate(value)) } val toggleSweep = { value: Boolean -> uiState.sendAction(SettingsAction.ToggleSweep(value)) } val toggleSensor = { value: Boolean -> uiState.sendAction(SettingsAction.ToggleSensor(value)) } - val toggleLightTheme = { value: Boolean -> - uiState.sendAction(SettingsAction.ToggleLightTheme(value)) - } + val uriHandler = LocalUriHandler.current - // Screen setup - LazyVerticalGrid( - columns = GridCells.Adaptive(320.dp), - modifier = Modifier.layoutPadding(), - horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalArrangement = Arrangement.spacedBy(6.dp) - ) { - item { CardAbout(uiState.appVersionName, uiState.sendSystemAction) } - item { - LocationCard( - positionSettings, - setGpsPos, - showPosDialog, - showLocDialog, - dismissPos, - uiState.sendSystemAction - ) - } - item { DataCard(dataSettings, updateFromWeb, updateFromFile, clearAllData) } - item { - OtherCard( - otherSettings, - toggleUtc, - toggleUpdate, - toggleSweep, - toggleSensor, - toggleLightTheme - ) - } - item { CardCredits() } - } -} - -@Preview(showBackground = true) -@Composable -private fun CardAboutPreview() = MainTheme { CardAbout(version = "4.0.0") } - -@Composable -private fun CardAbout(version: String, sendUrlAction: (SystemAction) -> Unit = {}) { - ElevatedCard(modifier = Modifier.fillMaxWidth()) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp) - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - painter = painterResource(id = R.drawable.ic_satellites), - tint = MaterialTheme.colorScheme.primary, - contentDescription = null, - modifier = Modifier.size(72.dp) - ) - Column { - Text( - text = stringResource(id = R.string.app_name), - fontSize = 48.sp, - color = MaterialTheme.colorScheme.primary - ) - Text( - text = stringResource(id = R.string.app_version, version), fontSize = 20.sp - ) + ScreenColumn( + topBar = { isVerticalLayout -> + if (isVerticalLayout) { + TopBar { + TopCard(onClick = { uriHandler.openUri("https://play.google.com/store/apps/details?id=com.rtbishop.look4sat") }, version = uiState.appVersionName, modifier = Modifier.weight(1f)) + PrimaryIconCard(onClick = { uriHandler.openUri("https://ko-fi.com/rt_bishop") }, resId = R.drawable.ic_pound) + } + TopBar { + Row(modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + BotCard(onClick = { uriHandler.openUri("https://www.gnu.org/licenses/gpl-3.0.html") }, R.drawable.ic_license, "Licence", modifier = Modifier.weight(1f)) + BotCard(onClick = { uriHandler.openUri("https://sites.google.com/view/look4sat-privacy-policy/home") }, R.drawable.ic_policy, "Privacy", modifier = Modifier.weight(1f)) + } + IconCard(action = { uriHandler.openUri("https://f-droid.org/en/packages/com.rtbishop.look4sat/") }, resId = R.drawable.ic_fdroid) + IconCard(action = { uriHandler.openUri("https://github.com/rt-bishop/Look4Sat/") }, resId = R.drawable.ic_github) + } + } else { + TopBar { + PrimaryIconCard(onClick = { uriHandler.openUri("https://ko-fi.com/rt_bishop") }, resId = R.drawable.ic_pound) + TopCard(onClick = { uriHandler.openUri("https://play.google.com/store/apps/details?id=com.rtbishop.look4sat") }, version = uiState.appVersionName, modifier = Modifier.weight(1f)) + Row(modifier = Modifier.weight(1f), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + BotCard(onClick = { uriHandler.openUri("https://www.gnu.org/licenses/gpl-3.0.html") }, R.drawable.ic_license, "Licence", modifier = Modifier.weight(1f)) + BotCard(onClick = { uriHandler.openUri("https://sites.google.com/view/look4sat-privacy-policy/home") }, R.drawable.ic_policy, "Privacy", modifier = Modifier.weight(1f)) + } + IconCard(action = { uriHandler.openUri("https://f-droid.org/en/packages/com.rtbishop.look4sat/") }, resId = R.drawable.ic_fdroid) + IconCard(action = { uriHandler.openUri("https://github.com/rt-bishop/Look4Sat/") }, resId = R.drawable.ic_github) } } - Text( - text = stringResource(id = R.string.app_subtitle), - fontSize = 20.sp, - modifier = Modifier.padding(top = 4.dp, bottom = 2.dp) - ) - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - CardButton( - onClick = { sendUrlAction(SystemAction.OpenGitHub) }, - text = stringResource(id = R.string.btn_github), - modifier = Modifier.weight(1f) - ) - Spacer(modifier = Modifier.width(6.dp)) - CardButton( - onClick = { sendUrlAction(SystemAction.OpenDonate) }, - text = stringResource(id = R.string.btn_donate), - modifier = Modifier.weight(1f) - ) - Spacer(modifier = Modifier.width(6.dp)) - CardButton( - onClick = { sendUrlAction(SystemAction.OpenFDroid) }, - text = stringResource(id = R.string.btn_fdroid), - modifier = Modifier.weight(1f) + } + ) { _ -> + LazyVerticalGrid( + columns = GridCells.Adaptive(320.dp), + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), + modifier = Modifier.clip(MaterialTheme.shapes.medium) + ) { + item { + LocationCard( + positionSettings, + setGpsPos, + showPosDialog, + showLocDialog, + dismissPos, + uiState.sendSystemAction ) } + item { DataCard(dataSettings, updateFromWeb, updateFromFile, clearAllData) } + item { + OtherCard( + otherSettings, + toggleUtc, + toggleUpdate, + toggleSweep, + toggleSensor + ) + } + item { CardCredits() } } } } @@ -419,7 +386,7 @@ private fun OtherCardPreview() = MainTheme { shouldSeeWarning = false, shouldSeeWelcome = false ) - OtherCard(settings = values, {}, {}, {}, {}, {}) + OtherCard(settings = values, {}, {}, {}, {}) } @Composable @@ -428,8 +395,7 @@ private fun OtherCard( toggleUtc: (Boolean) -> Unit, toggleUpdate: (Boolean) -> Unit, toggleSweep: (Boolean) -> Unit, - toggleSensor: (Boolean) -> Unit, - toggleLightTheme: (Boolean) -> Unit + toggleSensor: (Boolean) -> Unit ) { ElevatedCard(modifier = Modifier.fillMaxWidth()) { Column(modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)) { @@ -465,17 +431,6 @@ private fun OtherCard( Text(text = stringResource(id = R.string.other_switch_sensors)) Switch(checked = settings.stateOfSensors, onCheckedChange = { toggleSensor(it) }) } -// Row( -// horizontalArrangement = Arrangement.SpaceBetween, -// verticalAlignment = Alignment.CenterVertically, -// modifier = Modifier.fillMaxWidth() -// ) { -// Text(text = stringResource(id = R.string.other_switch_light_theme)) -// Switch( -// checked = settings.stateOfLightTheme, -// onCheckedChange = { toggleLightTheme(it) } -// ) -// } } } } @@ -508,7 +463,6 @@ private fun CardCreditsPreview() = MainTheme { CardCredits() } @Composable private fun CardCredits(modifier: Modifier = Modifier) { - val uriHandler = LocalUriHandler.current ElevatedCard(modifier = modifier.fillMaxWidth()) { Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -533,19 +487,58 @@ private fun CardCredits(modifier: Modifier = Modifier) { color = MaterialTheme.colorScheme.primary, modifier = modifier.padding(6.dp) ) - Row(horizontalArrangement = Arrangement.SpaceEvenly) { - CardButton( - onClick = { uriHandler.openUri(LICENSE_URL) }, - text = stringResource(id = R.string.btn_license), - modifier = Modifier.weight(1f) - ) - Spacer(modifier = Modifier.width(6.dp)) - CardButton( - onClick = { uriHandler.openUri(POLICY_URL) }, - text = stringResource(id = R.string.btn_privacy), - modifier = Modifier.weight(1f) - ) - } + } + } +} + +@Composable +private fun TopCard(onClick: () -> Unit, modifier: Modifier = Modifier, version: String) { + ElevatedCard(modifier = modifier) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(48.dp) + .clickable { onClick() }) { + Spacer(Modifier) + Icon( + painter = painterResource(id = R.drawable.ic_satellites), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary + ) + Text( + text = "Look4Sat v$version", + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier + .weight(1f) + .padding(end = 6.dp) + .infiniteMarquee() + ) + } + } +} + +@Composable +private fun BotCard(onClick: () -> Unit, resId: Int, text: String, modifier: Modifier = Modifier) { + ElevatedCard(modifier = modifier) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .height(48.dp) + .clickable { onClick() }) { + Spacer(Modifier) + Icon(painter = painterResource(id = resId), contentDescription = null) + Text( + text = text, + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier + .weight(1f) + .padding(end = 6.dp) + .infiniteMarquee() + ) } } } 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 da107a41..4c730bcc 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 @@ -2,6 +2,7 @@ package com.rtbishop.look4sat.presentation.settings import com.rtbishop.look4sat.domain.model.OtherSettings import com.rtbishop.look4sat.domain.predict.GeoPos +import com.rtbishop.look4sat.domain.predict.OrbitalPass data class PositionSettings( val isUpdating: Boolean, val stationPos: GeoPos, val messageResId: Int @@ -22,6 +23,9 @@ data class RCSettings( ) data class SettingsState( + val nextPass: OrbitalPass, + val nextTime: String, + val isNextTimeAos: Boolean, val appVersionName: String, val positionSettings: PositionSettings, val dataSettings: DataSettings, 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 32461c69..8c43a3e7 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,26 +24,31 @@ 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.predict.OrbitalPass import com.rtbishop.look4sat.domain.repository.IDatabaseRepo +import com.rtbishop.look4sat.domain.repository.ISatelliteRepo import com.rtbishop.look4sat.domain.repository.ISettingsRepo -import com.rtbishop.look4sat.domain.usecase.IOpenWebUrl import com.rtbishop.look4sat.domain.usecase.IShowToast +import com.rtbishop.look4sat.domain.utility.toTimerString +import com.rtbishop.look4sat.presentation.common.getDefaultPass +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch class SettingsViewModel( private val databaseRepo: IDatabaseRepo, + private val satelliteRepo: ISatelliteRepo, private val settingsRepo: ISettingsRepo, - private val openWebUrl: IOpenWebUrl, private val showToast: IShowToast, appVersionName: String ) : ViewModel() { - private val githubUrl = "https://github.com/rt-bishop/Look4Sat/" - private val donateUrl = "https://ko-fi.com/rt_bishop" - private val fdroidUrl = "https://f-droid.org/en/packages/com.rtbishop.look4sat/" private val defaultPosSettings = PositionSettings(false, settingsRepo.stationPosition.value, 0) private val defaultDataSettings = DataSettings(false, 0, 0, 0L) private val defaultRCSettings = RCSettings( @@ -57,6 +62,9 @@ class SettingsViewModel( ) private val _uiState = MutableStateFlow( SettingsState( + nextTime = "00:00:00", + isNextTimeAos = true, + nextPass = getDefaultPass(), appVersionName = appVersionName, positionSettings = defaultPosSettings, dataSettings = defaultDataSettings, @@ -67,9 +75,24 @@ class SettingsViewModel( sendSystemAction = ::handleAction ) ) + private var processing: Job? = null + val uiState: StateFlow = _uiState init { + viewModelScope.launch { + satelliteRepo.passes.collectLatest { passes -> + processing?.cancelAndJoin() + processing = viewModelScope.launch { + while (isActive) { + val timeNow = System.currentTimeMillis() + val newPasses = satelliteRepo.processPasses(passes, timeNow) + setPassInfo(newPasses, timeNow) + delay(1000) + } + } + } + } viewModelScope.launch { settingsRepo.stationPosition.collect { geoPos -> val newPosSettings = _uiState.value.positionSettings.copy( @@ -127,15 +150,13 @@ class SettingsViewModel( private fun handleAction(action: SystemAction) { when (action) { - SystemAction.OpenGitHub -> openWebUrl(githubUrl) - SystemAction.OpenDonate -> openWebUrl(donateUrl) - SystemAction.OpenFDroid -> openWebUrl(fdroidUrl) is SystemAction.ShowToast -> showToast(action.message) + else -> {} } } private fun setGpsPosition() { - if (settingsRepo.setStationPositionGps()) { + if (settingsRepo.setStationPosition()) { val messageResId = R.string.location_success val newPosSettings = _uiState.value.positionSettings.copy( isUpdating = true, messageResId = messageResId @@ -151,7 +172,7 @@ class SettingsViewModel( } private fun setGeoPosition(latitude: Double, longitude: Double) { - if (settingsRepo.setStationPositionGeo(latitude, longitude, 0.0)) { + if (settingsRepo.setStationPosition(latitude, longitude, 0.0)) { val messageResId = R.string.location_success val newPosSettings = _uiState.value.positionSettings.copy( isUpdating = false, messageResId = messageResId @@ -167,7 +188,7 @@ class SettingsViewModel( } private fun setQthPosition(locator: String) { - if (settingsRepo.setStationPositionQth(locator)) { + if (settingsRepo.setStationPosition(locator)) { val messageResId = R.string.location_success val newPosSettings = _uiState.value.positionSettings.copy( isUpdating = false, messageResId = messageResId @@ -213,6 +234,19 @@ class SettingsViewModel( } } + private fun setPassInfo(passes: List, timeNow: Long) { + if (passes.isEmpty()) return + try { + val nextPass = passes.first { it.aosTime.minus(timeNow) > 0 } + val time = nextPass.aosTime.minus(timeNow).toTimerString() + _uiState.update { it.copy(nextPass = nextPass, nextTime = time, isNextTimeAos = true) } + } catch (_: NoSuchElementException) { + val lastPass = passes.last() + val time = lastPass.losTime.minus(timeNow).toTimerString() + _uiState.update { it.copy(nextPass = lastPass, nextTime = time, isNextTimeAos = false) } + } + } + private fun clearAllData() = viewModelScope.launch { databaseRepo.clearAllData() } companion object { @@ -222,8 +256,8 @@ class SettingsViewModel( val container = (this[applicationKey] as MainApplication).container SettingsViewModel( container.databaseRepo, + container.satelliteRepo, container.settingsRepo, - container.provideOpenWebUrl(), container.provideShowToast(), container.provideAppVersionName() ) diff --git a/app/src/main/res/drawable/ic_license.xml b/app/src/main/res/drawable/ic_license.xml new file mode 100644 index 00000000..9574b943 --- /dev/null +++ b/app/src/main/res/drawable/ic_license.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_policy.xml b/app/src/main/res/drawable/ic_policy.xml new file mode 100644 index 00000000..4db40469 --- /dev/null +++ b/app/src/main/res/drawable/ic_policy.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33743768..359b16d6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,8 +192,6 @@ Celestrak website that provides access to the TLE data. \nLibre Space Foundation for their SatNOGS API and DB providing a huge amount of data. - \nDownloadAstro - team for their interest to the app and the interview published. The app comes with no warranty. 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 3fc244c4..b41f43d2 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 @@ -18,9 +18,10 @@ package com.rtbishop.look4sat.data.repository import android.content.SharedPreferences +import android.location.Location import android.location.LocationManager -import android.os.CancellationSignal import androidx.core.content.edit +import androidx.core.location.LocationListenerCompat import androidx.core.location.LocationManagerCompat import com.rtbishop.look4sat.domain.model.DatabaseState import com.rtbishop.look4sat.domain.model.OtherSettings @@ -30,11 +31,13 @@ import com.rtbishop.look4sat.domain.repository.ISettingsRepo import com.rtbishop.look4sat.domain.utility.positionToQth import com.rtbishop.look4sat.domain.utility.qthToPosition import com.rtbishop.look4sat.domain.utility.round -import java.util.concurrent.Executors import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -class SettingsRepo(private val manager: LocationManager, private val preferences: SharedPreferences) : ISettingsRepo { +class SettingsRepo( + private val locationManager: LocationManager, + private val preferences: SharedPreferences +) : ISettingsRepo, LocationListenerCompat { private val keyBluetoothAddress = "bluetoothAddress" private val keyBluetoothName = "bluetoothName" @@ -118,31 +121,30 @@ class SettingsRepo(private val manager: LocationManager, private val preferences //region # Station position settings private val _stationPosition = MutableStateFlow(getStationPosition()) - private val executor = Executors.newSingleThreadExecutor() private val providerDef = LocationManager.PASSIVE_PROVIDER private val providerGps = LocationManager.GPS_PROVIDER private val providerNet = LocationManager.NETWORK_PROVIDER - private val timeoutSignal = CancellationSignal().apply { - setOnCancelListener { _stationPosition.value = getStationPosition() } - } override val stationPosition: StateFlow = _stationPosition - override fun setStationPositionGeo(latitude: Double, longitude: Double, altitude: Double): Boolean { + override fun setStationPosition(latitude: Double, longitude: Double, altitude: Double): Boolean { val newLongitude = if (longitude > 180.0) longitude - 180 else longitude val locator = positionToQth(latitude, newLongitude) ?: return false setStationPosition(latitude, newLongitude, altitude, locator) return true } - override fun setStationPositionGps(): Boolean { - if (!LocationManagerCompat.isLocationEnabled(manager)) return false + override fun setStationPosition(): Boolean { + if (!LocationManagerCompat.isLocationEnabled(locationManager)) return false try { - val hasProviderGps = LocationManagerCompat.hasProvider(manager, providerGps) - val hasProviderNet = LocationManagerCompat.hasProvider(manager, providerNet) - val provider = if (hasProviderGps) providerGps else if (hasProviderNet) providerNet else providerDef - println("Requesting location for $provider provider") - LocationManagerCompat.getCurrentLocation(manager, provider, timeoutSignal, executor) { - it?.let { setStationPositionGeo(it.latitude, it.longitude, it.altitude) } + val hasGps = LocationManagerCompat.hasProvider(locationManager, providerGps) + val hasNet = LocationManagerCompat.hasProvider(locationManager, providerNet) + val provider = if (hasGps) providerGps else if (hasNet) providerNet else providerDef + val location = locationManager.getLastKnownLocation(providerDef) + if (location == null || System.currentTimeMillis() - location.time > 600_000L) { + println("Requesting location for $provider provider") + locationManager.requestLocationUpdates(provider, 0L, 0f, this) + } else { + setStationPosition(location.latitude, location.longitude, location.altitude) } } catch (exception: SecurityException) { println("No permissions were given - $exception") @@ -150,7 +152,7 @@ class SettingsRepo(private val manager: LocationManager, private val preferences return true } - override fun setStationPositionQth(locator: String): Boolean { + override fun setStationPosition(locator: String): Boolean { val position = qthToPosition(locator) ?: return false setStationPosition(position.latitude, position.longitude, 0.0, locator) return true @@ -277,7 +279,7 @@ class SettingsRepo(private val manager: LocationManager, private val preferences override fun getBluetoothAddress(): String = preferences.getString(keyBluetoothAddress, null) ?: "00:0C:BF:13:80:5D" override fun setBluetoothAddress(value: String) = preferences.edit { putString(keyBluetoothAddress, value) } - override fun getBluetoothFormat(): String = preferences.getString(keyBluetoothFormat, null) ?: "W\$AZ \$EL" + override fun getBluetoothFormat(): String = preferences.getString(keyBluetoothFormat, null) ?: $$"W$AZ $EL" override fun setBluetoothFormat(value: String) = preferences.edit { putString(keyBluetoothFormat, value) } override fun getBluetoothName(): String = preferences.getString(keyBluetoothName, null) ?: "Default" @@ -294,5 +296,8 @@ class SettingsRepo(private val manager: LocationManager, private val preferences override fun getRotatorState(): Boolean = preferences.getBoolean(keyRotatorState, false) override fun setRotatorState(value: Boolean) = preferences.edit { putBoolean(keyRotatorState, value) } + override fun onLocationChanged(location: Location) { + setStationPosition(location.latitude, location.longitude, location.altitude) + } //endregion } diff --git a/data/src/main/java/com/rtbishop/look4sat/data/usecase/OpenWebUrl.kt b/data/src/main/java/com/rtbishop/look4sat/data/usecase/OpenWebUrl.kt deleted file mode 100644 index b1da8534..00000000 --- a/data/src/main/java/com/rtbishop/look4sat/data/usecase/OpenWebUrl.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.rtbishop.look4sat.data.usecase - -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import com.rtbishop.look4sat.domain.usecase.IOpenWebUrl -import androidx.core.net.toUri - -class OpenWebUrl(private val context: Context) : IOpenWebUrl { - override fun invoke(url: String) { - try { - context.startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - }) - } catch (_: ActivityNotFoundException) { - } - } -} 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 ebdd6d99..99d28010 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 @@ -39,9 +39,9 @@ interface ISettingsRepo { //region # Station position settings val stationPosition: StateFlow - fun setStationPositionGeo(latitude: Double, longitude: Double, altitude: Double): Boolean - fun setStationPositionGps(): Boolean - fun setStationPositionQth(locator: String): Boolean + fun setStationPosition(latitude: Double, longitude: Double, altitude: Double): Boolean + fun setStationPosition(): Boolean + fun setStationPosition(locator: String): Boolean //endregion //region # Database update settings diff --git a/domain/src/main/java/com/rtbishop/look4sat/domain/usecase/IOpenWebUrl.kt b/domain/src/main/java/com/rtbishop/look4sat/domain/usecase/IOpenWebUrl.kt deleted file mode 100644 index 332dfa89..00000000 --- a/domain/src/main/java/com/rtbishop/look4sat/domain/usecase/IOpenWebUrl.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.rtbishop.look4sat.domain.usecase - -interface IOpenWebUrl { - operator fun invoke(url: String) -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d5b94caf..4ebaac98 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,16 @@ [versions] -android-gradle-plugin = "8.13.0" +android-gradle-plugin = "8.13.1" google-ksp = "2.3.0" kotlin = "2.2.21" androidx-core-ktx = "1.17.0" -androidx-core-splashscreen = "1.0.1" +androidx-core-splashscreen = "1.2.0" androidx-room = "2.8.3" -compose-bom = "2025.10.01" +compose-bom = "2025.11.00" compose-activity = "1.11.0" compose-lifecycle = "2.9.4" -compose-navigation = "2.9.5" +compose-navigation = "2.9.6" other-coroutines = "1.10.2" other-json = "20250517"