kopia lustrzana https://github.com/rt-bishop/Look4Sat
Updated dependencies and gradle plugin, minor fixes
rodzic
b75a0dcddc
commit
1d0d538367
|
@ -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>
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
Ładowanie…
Reference in New Issue