Updated dependencies and gradle plugin, minor fixes

pull/153/head
Arty Bishop 2024-10-27 14:00:59 +00:00
rodzic b75a0dcddc
commit 1d0d538367
19 zmienionych plików z 370 dodań i 325 usunięć

Wyświetl plik

@ -7,16 +7,15 @@
<application
android:name=".MainApplication"
android:enableOnBackInvokedCallback="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.Look4Sat.Main">
android:roundIcon="@mipmap/ic_launcher_round">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Look4Sat.SplashScreen">
android:theme="@style/Theme.Look4Sat.SplashScreen"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@ -24,5 +23,4 @@
</activity>
</application>
</manifest>

Wyświetl plik

@ -20,21 +20,23 @@ package com.rtbishop.look4sat
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.LocalContext
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.rtbishop.look4sat.presentation.MainScreen
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.MainScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContent {
val container = (LocalContext.current.applicationContext as MainApplication).container
val otherSettings = container.settingsRepo.otherSettings.collectAsState().value
MainTheme(isLightTheme = otherSettings.stateOfLightTheme) { MainScreen() }
MainTheme(isDarkTheme = !otherSettings.stateOfLightTheme) { MainScreen() }
}
}
}

Wyświetl plik

@ -37,19 +37,13 @@ class MainApplication : Application() {
}
private suspend fun checkAutoUpdate(timeNow: Long = System.currentTimeMillis()) {
val settingsRepo = container.settingsRepo
if (settingsRepo.otherSettings.value.stateOfAutoUpdate) {
val timeDelta = timeNow - settingsRepo.databaseState.value.updateTimestamp
if (timeDelta > AUTO_UPDATE_DELTA_MS) {
if (container.settingsRepo.otherSettings.value.stateOfAutoUpdate) {
val timeDelta = timeNow - container.settingsRepo.databaseState.value.updateTimestamp
if (timeDelta > 172_800_000L) { // 48 hours in ms
val sdf = SimpleDateFormat("d MMM yyyy - HH:mm:ss", Locale.getDefault())
println("Started periodic data update on ${sdf.format(Date())}")
container.databaseRepo.updateFromRemote()
}
}
}
companion object {
private const val AUTO_UPDATE_DELTA_MS = 172_800_000L // 48 hours in ms
const val MAX_OKHTTP_CACHE_SIZE = 10_000_000L // 10 Megabytes
}
}

Wyświetl plik

@ -28,7 +28,6 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import okhttp3.Cache
import okhttp3.OkHttpClient
import org.osmdroid.config.Configuration
@ -65,16 +64,13 @@ class MainContainer(private val context: Context) {
private fun provideLocalSource(): ILocalSource {
val database = Room.databaseBuilder(context, Look4SatDb::class.java, "Look4SatDBv313").apply {
// addMigrations(MIGRATION_1_2)
fallbackToDestructiveMigration()
}.build()
return LocalSource(database.look4SatDao())
}
private fun provideRemoteSource(): IRemoteSource {
val cache = Cache(context.cacheDir, MainApplication.MAX_OKHTTP_CACHE_SIZE)
val httpClient = OkHttpClient.Builder().cache(cache).build()
return RemoteSource(context.contentResolver, httpClient, Dispatchers.IO)
return RemoteSource(Dispatchers.IO, context.contentResolver, OkHttpClient.Builder().build())
}
private fun provideSatelliteRepo(): ISatelliteRepo {

Wyświetl plik

@ -1,16 +1,15 @@
package com.rtbishop.look4sat.presentation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
@ -21,69 +20,89 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.presentation.entries.EntriesScreen
import com.rtbishop.look4sat.presentation.entries.EntriesViewModel
import com.rtbishop.look4sat.presentation.info.InfoScreen
import com.rtbishop.look4sat.presentation.map.MapScreen
import com.rtbishop.look4sat.presentation.passes.PassesScreen
import com.rtbishop.look4sat.presentation.passes.PassesViewModel
import com.rtbishop.look4sat.presentation.radar.RadarScreen
import com.rtbishop.look4sat.presentation.satellites.SatellitesScreen
import com.rtbishop.look4sat.presentation.satellites.SatellitesViewModel
import com.rtbishop.look4sat.presentation.settings.SettingsScreen
sealed class Screen(var title: String, var icon: Int, var route: String) {
data object Entries : Screen("Entries", R.drawable.ic_sputnik, "entries")
data object Main : Screen("Main", R.drawable.ic_sputnik, "main")
data object Radar : Screen("Radar", R.drawable.ic_sputnik, "radar")
data object Satellites : Screen("Satellites", R.drawable.ic_sputnik, "satellites")
data object Passes : Screen("Passes", R.drawable.ic_passes, "passes")
data object Radar : Screen("Radar", R.drawable.ic_passes, "passes.radar")
data object Map : Screen("World Map", R.drawable.ic_world_map, "world_map")
data object Map : Screen("Map", R.drawable.ic_map, "map")
data object Settings : Screen("Settings", R.drawable.ic_settings, "settings")
data object Info : Screen("Info", R.drawable.ic_info, "info")
}
@Composable
fun MainScreen(navController: NavHostController = rememberNavController()) {
Scaffold(bottomBar = { MainNavBar(navController = navController) }) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) { MainNavGraph(navController) }
fun MainScreen() {
val outerNavController: NavHostController = rememberNavController()
val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}"
val radarArgs = listOf(navArgument("catNum") { defaultValue = 0 },
navArgument("aosTime") { defaultValue = 0L })
val navToRadar = { catNum: Int, aosTime: Long ->
val navRoute = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}"
outerNavController.navigate(navRoute)
}
NavHost(navController = outerNavController, startDestination = Screen.Main.route) {
composable(Screen.Main.route) { NavBarScreen(navToRadar) }
composable(radarRoute, radarArgs) { RadarScreen() }
}
}
@Composable
private fun NavBarScreen(navToRadar: (Int, Long) -> Unit) {
val innerNavController: NavHostController = rememberNavController()
val navToPasses = { innerNavController.navigate(Screen.Passes.route) }
Scaffold(bottomBar = { MainNavBar(navController = innerNavController) }) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
NavHost(innerNavController, startDestination = Screen.Passes.route) {
composable(Screen.Satellites.route) {
val viewModel = viewModel(
SatellitesViewModel::class.java, factory = SatellitesViewModel.Factory
)
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
SatellitesScreen(uiState, navToPasses)
}
composable(Screen.Passes.route) {
val viewModel = viewModel(
PassesViewModel::class.java, factory = PassesViewModel.Factory
)
val uiState = viewModel.uiState.value
PassesScreen(uiState, navToRadar)
}
composable(Screen.Map.route) { MapScreen() }
composable(Screen.Settings.route) { SettingsScreen() }
composable(Screen.Info.route) { InfoScreen() }
}
}
}
}
@Composable
private fun MainNavBar(navController: NavController) {
val items = listOf(Screen.Entries, Screen.Passes, Screen.Map, Screen.Settings, Screen.Info)
NavigationBar(modifier = Modifier.height(48.dp), tonalElevation = 0.dp) {
val navBackStackEntry = navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry.value?.destination?.route
items.forEach { screen ->
NavigationBarItem(selected = currentRoute?.contains(screen.route) ?: false, onClick = {
navController.navigate(screen.route) {
navController.graph.startDestinationRoute?.let { popUpTo(it) { saveState = false } }
launchSingleTop = true
restoreState = false
}
}, icon = { Icon(painterResource(id = screen.icon), contentDescription = screen.title) })
val items = listOf(Screen.Satellites, Screen.Passes, Screen.Map, Screen.Settings, Screen.Info)
val currentBackStackEntry = navController.currentBackStackEntryAsState()
val currentRoute = currentBackStackEntry.value?.destination?.route
NavigationBar {
items.forEach { item ->
NavigationBarItem(selected = currentRoute?.contains(item.route) ?: false,
onClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let {
popUpTo(it) { saveState = false }
}
launchSingleTop = true
restoreState = false
}
},
icon = { Icon(painterResource(id = item.icon), contentDescription = item.title) },
label = { Text(item.title) })
}
}
}
@Composable
private fun MainNavGraph(navController: NavHostController) {
val radarRoute = "${Screen.Radar.route}?catNum={catNum}&aosTime={aosTime}"
val radarArgs = listOf(navArgument("catNum") { defaultValue = 0 }, navArgument("aosTime") { defaultValue = 0L })
NavHost(navController, startDestination = Screen.Passes.route) {
composable(Screen.Entries.route) {
val viewModel = viewModel(EntriesViewModel::class.java, factory = EntriesViewModel.Factory)
val navToPasses = { navController.navigate(Screen.Passes.route) }
EntriesScreen(viewModel.uiState.collectAsStateWithLifecycle().value, navToPasses)
}
composable(Screen.Passes.route) {
val viewModel = viewModel(PassesViewModel::class.java, factory = PassesViewModel.Factory)
val navToRadar = { catNum: Int, aosTime: Long ->
navController.navigate("${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}")
}
PassesScreen(viewModel.uiState.value, navToRadar)
}
composable(radarRoute, radarArgs) { RadarScreen() }
composable(Screen.Map.route) { MapScreen() }
composable(Screen.Settings.route) { SettingsScreen() }
composable(Screen.Info.route) { InfoScreen() }
}
}

Wyświetl plik

@ -1,6 +1,7 @@
package com.rtbishop.look4sat.presentation
import androidx.activity.ComponentActivity
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Shapes
@ -10,7 +11,6 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
@ -20,18 +20,16 @@ import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
@Composable
fun MainTheme(isLightTheme: Boolean = false, content: @Composable () -> Unit) {
fun MainTheme(isDarkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val view = LocalView.current
if (view.isInEditMode) {
MaterialTheme(darkScheme, shapes, typography, content)
} else {
val colorScheme = if (isLightTheme) lightScheme else darkScheme
val colorScheme = if (isDarkTheme) darkScheme else lightScheme
SideEffect {
val window = (view.context as ComponentActivity).window
window.statusBarColor = colorScheme.background.toArgb()
window.navigationBarColor = colorScheme.surface.toArgb()
val insetsController = WindowCompat.getInsetsController(window, view)
insetsController.isAppearanceLightStatusBars = isLightTheme
insetsController.isAppearanceLightStatusBars = false
}
MaterialTheme(colorScheme, shapes, typography, content)
}
@ -57,22 +55,42 @@ private val lightScheme = lightColorScheme(
)
private val darkScheme = darkColorScheme(
primary = Color(0xFFFFE082),
onPrimary = Color(0xFF000000),
primary = Color(0xFFFFE082), // main accent, switch background, active progress
onPrimary = Color(0xFF000000), // on main accent, switch knob
// primaryContainer = Color(0xFF121212),
// onPrimaryContainer = Color(0xFF121212),
// inversePrimary = Color(0xFF121212),
secondary = Color(0xFFE0E0E0),
onSecondary = Color(0xFF000000),
secondaryContainer = Color(0xFFFFE082), // navBar indicator
onSecondaryContainer = Color(0xFF000000), // navBar active icon
secondaryContainer = Color(0xFF484848), // navBar indicator,
onSecondaryContainer = Color(0xFFE0E0E0), // navBar active icon
// tertiary = Color(0xFF121212),
// onTertiary = Color(0xFF121212),
// tertiaryContainer = Color(0xFF121212),
// onTertiaryContainer = Color(0xFF121212),
background = Color(0xFF121212),
onBackground = Color(0xFFE0E0E0),
surface = Color(0xFF242424),
onSurface = Color(0xFFE0E0E0),
surfaceTint = Color(0x00000000),
surface = Color(0xFF242424), // card background
onSurface = Color(0xFFE0E0E0), // on card background
surfaceVariant = Color(0xFF484848), // buttons background
onSurfaceVariant = Color(0xFFE0E0E0), // navBar inactive icon
surfaceTint = Color(0x00000000),
// inverseSurface = Color(0xFF121212),
// inverseOnSurface = Color(0xFF121212),
error = Color(0xFFDC0000),
// onError = Color(0xFFFFFFFF),
// errorContainer = Color(0xFF121212),
// onErrorContainer = Color(0xFF121212),
outline = Color(0xA3E0E0E0),
scrim = Color(0xFF000000)
// outlineVariant = Color(0xFF121212),
scrim = Color(0xFF000000),
// surfaceBright = Color(0xFF121212),
surfaceContainer = Color(0xFF242424), // navBar background
// surfaceContainerHigh = Color(0xFF121212),
surfaceContainerHighest = Color(0xFF242424), // filled card background
surfaceContainerLow = Color(0xFF242424), // elevated card background
// surfaceContainerLowest = Color(0xFF121212),
// surfaceDim = Color(0xFF121212),
)
private val shapes = Shapes(

Wyświetl plik

@ -1,78 +0,0 @@
package com.rtbishop.look4sat.presentation.entries
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.rtbishop.look4sat.presentation.MainTheme
@Preview(showBackground = true)
@Composable
private fun TypeDialogPreview() {
val types = listOf("All", "Amateur", "Geostationary", "Military", "Weather")
MainTheme { TypesDialog(types, "All", {}) {} }
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TypesDialog(items: List<String>, selected: String, dismiss: () -> Unit, select: (String) -> Unit) {
ModalBottomSheet(
onDismissRequest = { dismiss() },
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
modifier = Modifier.fillMaxHeight(0.80f)
) {
LazyVerticalGrid(
columns = GridCells.Fixed(1),
modifier = Modifier.background(MaterialTheme.colorScheme.background),
horizontalArrangement = Arrangement.spacedBy(1.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
item { HorizontalDivider(thickness = 0.dp, color = MaterialTheme.colorScheme.surface) }
itemsIndexed(items) { index, item ->
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.clickable { select(item) }) {
Text(
text = "$index).",
modifier = Modifier.padding(start = 12.dp, end = 6.dp),
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.primary
)
Text(
text = item,
modifier = Modifier.weight(1f),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
RadioButton(
selected = item == selected,
onClick = null,
modifier = Modifier.padding(start = 8.dp, top = 8.dp, end = 12.dp, bottom = 8.dp)
)
}
}
item { HorizontalDivider(thickness = 24.dp, color = MaterialTheme.colorScheme.surface) }
}
}
}

Wyświetl plik

@ -1,22 +0,0 @@
package com.rtbishop.look4sat.presentation.entries
import com.rtbishop.look4sat.domain.model.SatItem
data class EntriesState(
val isDialogShown: Boolean,
val isLoading: Boolean,
val itemsList: List<SatItem>,
val currentType: String,
val typesList: List<String>,
val takeAction: (EntriesAction) -> Unit
)
sealed class EntriesAction {
data object SaveSelection : EntriesAction()
data class SearchFor(val query: String) : EntriesAction()
data object SelectAll : EntriesAction()
data class SelectSingle(val id: Int, val isTicked: Boolean) : EntriesAction()
data class SelectType(val type: String) : EntriesAction()
data object ToggleTypesDialog : EntriesAction()
data object UnselectAll : EntriesAction()
}

Wyświetl plik

@ -1,6 +1,5 @@
package com.rtbishop.look4sat.presentation.passes
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -27,7 +26,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -80,7 +78,6 @@ fun PassesScreen(uiState: PassesState, navToRadar: (Int, Long) -> Unit) {
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun PassesList(
refreshState: PullRefreshState,
@ -93,7 +90,7 @@ private fun PassesList(
Box(modifier = Modifier.pullRefresh(refreshState), contentAlignment = Alignment.TopCenter) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(items = passes, key = { item -> item.catNum + item.aosTime }) { pass ->
PassItem(pass = pass, navToRadar = navToRadar, modifier = Modifier.animateItemPlacement())
PassItem(pass = pass, navToRadar = navToRadar, modifier = Modifier.animateItem())
}
}
PullRefreshIndicator(refreshing = isRefreshing, state = refreshState, backgroundColor = backgroundColor)

Wyświetl plik

@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -34,27 +35,32 @@ fun RadarScreen() {
val currentPass = viewModel.getPass().collectAsState(null).value
val id = currentPass?.catNum ?: 99999
val name = currentPass?.name ?: "Satellite"
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
Scaffold { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
// TimerBarNew(id, name, "88:88:88", R.drawable.ic_notifications) {}
ElevatedCard(
modifier = Modifier
.fillMaxSize()
.weight(1f)
) {
viewModel.radarData.value?.let { data ->
RadarViewCompose(
item = data.orbitalPos,
items = data.satTrack,
azimElev = viewModel.orientation.value
)
ElevatedCard(
modifier = Modifier
.fillMaxSize()
.weight(1f)
) {
viewModel.radarData.value?.let { data ->
RadarViewCompose(
item = data.orbitalPos,
items = data.satTrack,
azimElev = viewModel.orientation.value
)
}
}
ElevatedCard(
modifier = Modifier
.fillMaxSize()
.weight(1f)
) {
TransmittersList(transmitters = viewModel.transmitters.value)
}
}
ElevatedCard(
modifier = Modifier
.fillMaxSize()
.weight(1f)
) {
TransmittersList(transmitters = viewModel.transmitters.value)
}
}
}
@ -78,7 +84,10 @@ private fun TransmitterItem(radio: SatRadio) {
.background(MaterialTheme.colorScheme.surface)
.padding(6.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Icon(
painter = painterResource(id = R.drawable.ic_arrow),
contentDescription = null, modifier = Modifier.rotate(180f)
@ -93,7 +102,10 @@ private fun TransmitterItem(radio: SatRadio) {
contentDescription = null
)
}
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.radio_downlink, radio.downlinkLow ?: 0L),
textAlign = TextAlign.Center,

Wyświetl plik

@ -70,66 +70,66 @@ fun RadarViewCompose(item: OrbitalPos, items: List<OrbitalPos>, azimElev: Pair<F
val beepSoundId = remember { mutableIntStateOf(0) }
val aimTargetDifference = remember { mutableFloatStateOf(0f) }
LaunchedEffect(item.azimuth, item.elevation, azimElev.first, azimElev.second) {
val aimAzimuthRadians = azimElev.first.toDouble().toRadians()
val aimElevationRadians = abs(min(azimElev.second, 0f)).toDouble().toRadians()
// radius of 0.5 makes the aimTargetDifference range 0.0 to 1.0
val radius = 0.5
val aimX = sph2CartX(aimAzimuthRadians, aimElevationRadians, radius)
val aimY = sph2CartY(aimAzimuthRadians, aimElevationRadians, radius)
val satX = sph2CartX(item.azimuth, item.elevation, radius)
val satY = sph2CartY(item.azimuth, item.elevation, radius)
aimTargetDifference.floatValue = sqrt((satX - aimX).pow(2) + (satY - aimY).pow(2))
val minPlaybackRate = 0.5f
val maxPlaybackRate = 2.0f
val playbackRate =
maxPlaybackRate - (aimTargetDifference.floatValue / (maxPlaybackRate - minPlaybackRate))
soundPool.value?.setRate(beepSoundId.intValue, playbackRate)
}
LaunchedEffect(aimTargetDifference.floatValue < aimThreshold) {
if (aimTargetDifference.floatValue < aimThreshold) {
view.performHapticFeedback(HapticFeedbackConstantsCompat.VIRTUAL_KEY)
soundPool.value?.pause(beepSoundId.intValue)
} else {
soundPool.value?.resume(beepSoundId.intValue)
}
}
LaunchedEffect(item.elevation > 0) {
if(item.elevation > 0) {
soundPool.value?.resume(beepSoundId.intValue)
} else {
soundPool.value?.pause(beepSoundId.intValue)
}
}
DisposableEffect(Unit) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build()
soundPool.value = SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(audioAttributes)
.build()
beepSoundId.intValue = soundPool.value?.load(context, R.raw.beep, 1) ?: 0
soundPool.value?.setOnLoadCompleteListener { soundPool, _, status ->
if (status == 0) {
soundPool.play(beepSoundId.intValue, 0.5f, 0.5f, 0, -1, 1f)
}
}
onDispose {
soundPool.value?.release()
}
}
// LaunchedEffect(item.azimuth, item.elevation, azimElev.first, azimElev.second) {
// val aimAzimuthRadians = azimElev.first.toDouble().toRadians()
// val aimElevationRadians = abs(min(azimElev.second, 0f)).toDouble().toRadians()
// // radius of 0.5 makes the aimTargetDifference range 0.0 to 1.0
// val radius = 0.5
// val aimX = sph2CartX(aimAzimuthRadians, aimElevationRadians, radius)
// val aimY = sph2CartY(aimAzimuthRadians, aimElevationRadians, radius)
// val satX = sph2CartX(item.azimuth, item.elevation, radius)
// val satY = sph2CartY(item.azimuth, item.elevation, radius)
// aimTargetDifference.floatValue = sqrt((satX - aimX).pow(2) + (satY - aimY).pow(2))
//
//
// val minPlaybackRate = 0.5f
// val maxPlaybackRate = 2.0f
// val playbackRate =
// maxPlaybackRate - (aimTargetDifference.floatValue / (maxPlaybackRate - minPlaybackRate))
//
// soundPool.value?.setRate(beepSoundId.intValue, playbackRate)
// }
//
// LaunchedEffect(aimTargetDifference.floatValue < aimThreshold) {
// if (aimTargetDifference.floatValue < aimThreshold) {
// view.performHapticFeedback(HapticFeedbackConstantsCompat.VIRTUAL_KEY)
// soundPool.value?.pause(beepSoundId.intValue)
// } else {
// soundPool.value?.resume(beepSoundId.intValue)
// }
// }
//
// LaunchedEffect(item.elevation > 0) {
// if(item.elevation > 0) {
// soundPool.value?.resume(beepSoundId.intValue)
// } else {
// soundPool.value?.pause(beepSoundId.intValue)
// }
// }
//
// DisposableEffect(Unit) {
// val audioAttributes = AudioAttributes.Builder()
// .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
// .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
// .build()
//
// soundPool.value = SoundPool.Builder()
// .setMaxStreams(1)
// .setAudioAttributes(audioAttributes)
// .build()
//
// beepSoundId.intValue = soundPool.value?.load(context, R.raw.beep, 1) ?: 0
//
// soundPool.value?.setOnLoadCompleteListener { soundPool, _, status ->
// if (status == 0) {
// soundPool.play(beepSoundId.intValue, 0.5f, 0.5f, 0, -1, 1f)
// }
// }
//
// onDispose {
// soundPool.value?.release()
// }
// }
Canvas(modifier = Modifier.fillMaxSize()) {
val radius = (size.minDimension / 2f) * 0.95f

Wyświetl plik

@ -0,0 +1,92 @@
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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import com.rtbishop.look4sat.presentation.MainTheme
@Preview(showBackground = true)
@Composable
private fun TypeDialogPreview() {
val types = listOf("All", "Amateur", "Geostationary", "Military", "Weather")
MainTheme { TypesDialog(types, "All", {}) {} }
}
@Composable
fun TypesDialog(
items: List<String>, selected: String, dismiss: () -> Unit, select: (String) -> Unit
) {
val height = LocalConfiguration.current.screenHeightDp
Dialog(onDismissRequest = { dismiss() }) {
ElevatedCard {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.heightIn(max = height.times(0.80).dp)
) {
Text(
text = "Select category",
fontSize = 18.sp,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
)
LazyVerticalGrid(
columns = GridCells.Adaptive(240.dp),
modifier = Modifier.background(MaterialTheme.colorScheme.background),
horizontalArrangement = Arrangement.spacedBy(1.dp),
verticalArrangement = Arrangement.spacedBy(1.dp)
) {
itemsIndexed(items) { index, item ->
Row(verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.background(MaterialTheme.colorScheme.surface)
.clickable { select(item) }) {
Text(
text = "$index).",
modifier = Modifier.padding(start = 12.dp, end = 6.dp),
fontWeight = FontWeight.Normal,
color = MaterialTheme.colorScheme.primary
)
Text(
text = item,
modifier = Modifier.weight(1f),
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
RadioButton(
selected = item == selected,
onClick = null,
modifier = Modifier.padding(
start = 8.dp, top = 8.dp, end = 12.dp, bottom = 8.dp
)
)
}
}
}
}
}
}
}

Wyświetl plik

@ -1,6 +1,5 @@
package com.rtbishop.look4sat.presentation.entries
package com.rtbishop.look4sat.presentation.satellites
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -44,29 +43,29 @@ import com.rtbishop.look4sat.presentation.components.CardIcon
import com.rtbishop.look4sat.presentation.components.CardLoadingIndicator
@Composable
fun EntriesScreen(uiState: EntriesState, navToPasses: () -> Unit) {
val toggleDialog = { uiState.takeAction(EntriesAction.ToggleTypesDialog) }
fun SatellitesScreen(uiState: SatellitesState, navToPasses: () -> Unit) {
val toggleDialog = { uiState.takeAction(SatellitesAction.ToggleTypesDialog) }
if (uiState.isDialogShown) {
TypesDialog(items = uiState.typesList, selected = uiState.currentType, toggleDialog) {
uiState.takeAction(EntriesAction.SelectType(it))
uiState.takeAction(SatellitesAction.SelectType(it))
}
}
val unselectAll = { uiState.takeAction(EntriesAction.UnselectAll) }
val selectAll = { uiState.takeAction(EntriesAction.SelectAll) }
val unselectAll = { uiState.takeAction(SatellitesAction.UnselectAll) }
val selectAll = { uiState.takeAction(SatellitesAction.SelectAll) }
Column(modifier = Modifier.padding(6.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) {
TopBar(
setQuery = { newQuery: String -> uiState.takeAction(EntriesAction.SearchFor(newQuery)) },
saveSelection = {
uiState.takeAction(EntriesAction.SaveSelection)
navToPasses()
})
TopBar(setQuery = { newQuery: String ->
uiState.takeAction(SatellitesAction.SearchFor(newQuery))
}, saveSelection = {
uiState.takeAction(SatellitesAction.SaveSelection)
navToPasses()
})
MiddleBar(uiState.currentType, { toggleDialog() }, { unselectAll() }, { selectAll() })
ElevatedCard(modifier = Modifier.fillMaxSize()) {
if (uiState.isLoading) {
CardLoadingIndicator()
} else {
EntriesCard(uiState.itemsList) { id, isTicked ->
uiState.takeAction(EntriesAction.SelectSingle(id, isTicked))
SatellitesCard(uiState.itemsList) { id, isTicked ->
uiState.takeAction(SatellitesAction.SelectSingle(id, isTicked))
}
}
}
@ -105,7 +104,9 @@ private fun SearchBar(setQuery: (String) -> Unit, modifier: Modifier = Modifier)
singleLine = true,
modifier = modifier.padding(start = 12.dp),
textStyle = TextStyle(
fontSize = 16.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.onSurface
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
),
decorationBox = { innerTextField ->
if (currentQuery.value.isEmpty()) {
@ -147,14 +148,14 @@ private fun MiddleBarPreview() = MainTheme { MiddleBar("Amateur", {}, {}, {}) }
@Composable
private fun MiddleBar(type: String, navigate: () -> Unit, uncheck: () -> Unit, check: () -> Unit) {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.height(48.dp)) {
EntryTypeCard(type = type, { navigate() }, modifier = Modifier.weight(1f))
TypeCard(type = type, { navigate() }, modifier = Modifier.weight(1f))
CardIcon(onClick = { uncheck() }, iconId = R.drawable.ic_check_off)
CardIcon(onClick = { check() }, iconId = R.drawable.ic_check_on)
}
}
@Composable
private fun EntryTypeCard(type: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
private fun TypeCard(type: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
ElevatedCard(modifier = modifier) {
Row(horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
@ -180,13 +181,13 @@ private fun EntryTypeCard(type: String, onClick: () -> Unit, modifier: Modifier
@Preview(showBackground = true)
@Composable
private fun EntryPreview() {
private fun SatellitePreview() {
val satItem = SatItem(44444, "Ultra Super Mega long satellite name", true)
MainTheme { Entry(item = satItem, onSelected = { _, _ -> run {} }, modifier = Modifier) }
MainTheme { Satellite(item = satItem, onSelected = { _, _ -> run {} }, modifier = Modifier) }
}
@Composable
private fun Entry(item: SatItem, onSelected: (Int, Boolean) -> Unit, modifier: Modifier) {
private fun Satellite(item: SatItem, onSelected: (Int, Boolean) -> Unit, modifier: Modifier) {
val passSatId = stringResource(id = R.string.pass_satId, item.catnum)
Surface(color = MaterialTheme.colorScheme.background,
modifier = modifier.clickable { onSelected(item.catnum, item.isSelected) }) {
@ -198,8 +199,7 @@ private fun Entry(item: SatItem, onSelected: (Int, Boolean) -> Unit, modifier: M
.padding(start = 14.dp, top = 8.dp, end = 12.dp, bottom = 8.dp)
) {
Text(
text = "$passSatId - ",
color = MaterialTheme.colorScheme.primary
text = "$passSatId - ", color = MaterialTheme.colorScheme.primary
)
Text(
text = item.name,
@ -208,7 +208,11 @@ private fun Entry(item: SatItem, onSelected: (Int, Boolean) -> Unit, modifier: M
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Checkbox(checked = item.isSelected, onCheckedChange = null, modifier = Modifier.padding(start = 6.dp))
Checkbox(
checked = item.isSelected,
onCheckedChange = null,
modifier = Modifier.padding(start = 6.dp)
)
}
}
}
@ -216,21 +220,20 @@ private fun Entry(item: SatItem, onSelected: (Int, Boolean) -> Unit, modifier: M
@Preview(showBackground = true)
@Composable
private fun EntriesPreview() {
private fun SatellitesPreview() {
val entries = listOf(
SatItem(8888, "Meteor", false),
SatItem(44444, "ISS", true),
SatItem(88888, "Starlink", false)
)
MainTheme { EntriesCard(entries) { _, _ -> run {} } }
MainTheme { SatellitesCard(entries) { _, _ -> run {} } }
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun EntriesCard(items: List<SatItem>, onSelected: (Int, Boolean) -> Unit) {
fun SatellitesCard(items: List<SatItem>, onSelected: (Int, Boolean) -> Unit) {
LazyColumn {
items(items = items, key = { item -> item.catnum }) { entry ->
Entry(entry, onSelected, Modifier.animateItemPlacement())
Satellite(entry, onSelected, Modifier.animateItem())
}
}
}

Wyświetl plik

@ -0,0 +1,22 @@
package com.rtbishop.look4sat.presentation.satellites
import com.rtbishop.look4sat.domain.model.SatItem
data class SatellitesState(
val isDialogShown: Boolean,
val isLoading: Boolean,
val itemsList: List<SatItem>,
val currentType: String,
val typesList: List<String>,
val takeAction: (SatellitesAction) -> Unit
)
sealed class SatellitesAction {
data object SaveSelection : SatellitesAction()
data class SearchFor(val query: String) : SatellitesAction()
data object SelectAll : SatellitesAction()
data class SelectSingle(val id: Int, val isTicked: Boolean) : SatellitesAction()
data class SelectType(val type: String) : SatellitesAction()
data object ToggleTypesDialog : SatellitesAction()
data object UnselectAll : SatellitesAction()
}

Wyświetl plik

@ -15,7 +15,7 @@
* 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.presentation.entries
package com.rtbishop.look4sat.presentation.satellites
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
@ -29,11 +29,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() {
class SatellitesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel() {
private val defaultType = "All"
private val _uiState = MutableStateFlow(
EntriesState(
SatellitesState(
isDialogShown = false,
isLoading = true,
itemsList = emptyList(),
@ -42,7 +42,7 @@ class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel()
takeAction = ::handleAction
)
)
val uiState: StateFlow<EntriesState> = _uiState
val uiState: StateFlow<SatellitesState> = _uiState
init {
viewModelScope.launch {
@ -54,15 +54,15 @@ class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel()
}
}
private fun handleAction(action: EntriesAction) {
private fun handleAction(action: SatellitesAction) {
when (action) {
EntriesAction.SaveSelection -> saveSelection()
is EntriesAction.SearchFor -> searchFor(action.query)
EntriesAction.SelectAll -> selectAll(true)
is EntriesAction.SelectSingle -> selectSingle(action.id, action.isTicked)
is EntriesAction.SelectType -> selectType(action.type)
EntriesAction.ToggleTypesDialog -> toggleTypesDialog()
EntriesAction.UnselectAll -> selectAll(false)
SatellitesAction.SaveSelection -> saveSelection()
is SatellitesAction.SearchFor -> searchFor(action.query)
SatellitesAction.SelectAll -> selectAll(true)
is SatellitesAction.SelectSingle -> selectSingle(action.id, action.isTicked)
is SatellitesAction.SelectType -> selectType(action.type)
SatellitesAction.ToggleTypesDialog -> toggleTypesDialog()
SatellitesAction.UnselectAll -> selectAll(false)
}
}
@ -93,7 +93,7 @@ class EntriesViewModel(private val selectionRepo: ISelectionRepo) : ViewModel()
val applicationKey = ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY
initializer {
val container = (this[applicationKey] as MainApplication).container
EntriesViewModel(container.selectionRepo)
SatellitesViewModel(container.selectionRepo)
}
}
}

Wyświetl plik

@ -1,20 +1,12 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<color name="accent">#FFE082</color>
<color name="background">#121212</color>
<style name="Theme.Look4Sat.SplashScreen" parent="Theme.SplashScreen">
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
<item name="postSplashScreenTheme">@style/Theme.Look4Sat.Main</item>
<item name="postSplashScreenTheme">@android:style/Theme.Material.NoActionBar</item>
<item name="windowSplashScreenBackground">@color/background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/launcher_fg</item>
</style>
<style name="Theme.Look4Sat.Main" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:forceDarkAllowed" tools:targetApi="q">false</item>
<item name="android:navigationBarColor">@color/background</item>
<item name="android:statusBarColor">@color/background</item>
<item name="android:windowBackground">@color/background</item>
</style>
</resources>

Wyświetl plik

@ -27,9 +27,9 @@ import okhttp3.OkHttpClient
import okhttp3.Request
class RemoteSource(
private val dispatcher: CoroutineDispatcher,
private val contentResolver: ContentResolver,
private val httpClient: OkHttpClient,
private val dispatcher: CoroutineDispatcher
private val httpClient: OkHttpClient
) : IRemoteSource {
override suspend fun getFileStream(uri: String): InputStream? = withContext(dispatcher) {

Wyświetl plik

@ -1,16 +1,16 @@
[versions]
android-gradle-plugin = "8.7.1"
android-gradle-plugin = "8.7.2"
google-ksp = "2.0.20-1.0.25"
kotlin = "2.0.21"
androidx-core-ktx = "1.13.1"
androidx-core-ktx = "1.15.0"
androidx-core-splashscreen = "1.0.1"
androidx-room = "2.6.1"
composeBom = "2024.10.00"
composeBom = "2024.10.01"
compose-activity = "1.9.3"
compose-lifecycle = "2.8.6"
compose-material3 = "1.3.0"
compose-lifecycle = "2.8.7"
compose-material3 = "1.3.1"
compose-navigation = "2.8.3"
other-coroutines = "1.9.0"