mirror of
https://github.com/rt-bishop/Look4Sat.git
synced 2026-10-02 03:15:37 +00:00
Restructured SettingsScreen layout, tweaked components
This commit is contained in:
1 parent
bfbe437ab1
commit
a68e466040
16 files changed
+236
-214
No files matched your search
@@ -26,7 +26,6 @@ android {
|
||||
buildTypes {
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
versionNameSuffix = "-debug"
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
+8
-19
@@ -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<String>, onClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
val typesText = if (types.isEmpty()) "All" else types.joinToString(", ")
|
||||
@@ -187,7 +172,11 @@ private fun TypeCard(types: List<String>, 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,
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
+46
-12
@@ -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<SettingsState> = _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<OrbitalPass>, 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()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,520Q430,520 395,485Q360,450 360,400Q360,350 395,315Q430,280 480,280Q530,280 565,315Q600,350 600,400Q600,450 565,485Q530,520 480,520ZM240,920L240,611Q202,569 181,515Q160,461 160,400Q160,266 253,173Q346,80 480,80Q614,80 707,173Q800,266 800,400Q800,461 779,515Q758,569 720,611L720,920L480,840L240,920ZM480,640Q580,640 650,570Q720,500 720,400Q720,300 650,230Q580,160 480,160Q380,160 310,230Q240,300 240,400Q240,500 310,570Q380,640 480,640ZM320,801L480,760L640,801L640,677Q605,697 564.5,708.5Q524,720 480,720Q436,720 395.5,708.5Q355,697 320,677L320,801ZM480,739L480,739Q480,739 480,739Q480,739 480,739Q480,739 480,739Q480,739 480,739L480,739L480,739Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,880Q341,845 250.5,720.5Q160,596 160,444L160,200L480,80L800,200L800,444Q800,529 771,607.5Q742,686 688,746L560,618Q542,629 521.5,634.5Q501,640 480,640Q414,640 367,593Q320,546 320,480Q320,414 367,367Q414,320 480,320Q546,320 593,367Q640,414 640,480Q640,502 634.5,522.5Q629,543 618,562L678,622Q698,581 709,536Q720,491 720,444L720,255L480,165L240,255L240,444Q240,565 308,664Q376,763 480,796Q506,788 529.5,775.5Q553,763 576,746L632,802Q599,829 560.5,849Q522,869 480,880ZM480,560Q513,560 536.5,536.5Q560,513 560,480Q560,447 536.5,423.5Q513,400 480,400Q447,400 423.5,423.5Q400,447 400,480Q400,513 423.5,536.5Q447,560 480,560ZM488,483L488,483Q488,483 488,483Q488,483 488,483L488,483Q488,483 488,483Q488,483 488,483L488,483L488,483L488,483L488,483Q488,483 488,483Q488,483 488,483Q488,483 488,483Q488,483 488,483Z" />
|
||||
</vector>
|
||||
@@ -192,8 +192,6 @@
|
||||
<a href="https://celestrak.com/">Celestrak</a> website that provides access to the TLE data.
|
||||
\n<a href="https://libre.space/">Libre Space Foundation</a> for their
|
||||
<a href="https://db.satnogs.org/">SatNOGS</a> API and DB providing a huge amount of data.
|
||||
\n<a href="https://appoftheday.downloadastro.com/app/look4sat-satellite-tracker/">DownloadAstro</a>
|
||||
team for their interest to the app and the interview published.
|
||||
</string>
|
||||
<string name="outro_license">The app comes with no warranty.</string>
|
||||
</resources>
|
||||
@@ -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<GeoPos> = _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
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,9 @@ interface ISettingsRepo {
|
||||
|
||||
//region # Station position settings
|
||||
val stationPosition: StateFlow<GeoPos>
|
||||
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
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.rtbishop.look4sat.domain.usecase
|
||||
|
||||
interface IOpenWebUrl {
|
||||
operator fun invoke(url: String)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user