Switched back to bottom navigation, tweaked components

This commit is contained in:
Arty Bishop committed 2025-12-27 15:02:15 +00:00
1 parent 529a68f291
commit 574ffe09c2
18 files changed
+125 -146

No files matched your search

@@ -17,6 +17,8 @@
*/
package com.rtbishop.look4sat
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -29,6 +31,11 @@ import com.rtbishop.look4sat.presentation.MainScreen
class MainActivity : ComponentActivity() {
override fun attachBaseContext(newBase: Context?) {
overrideSystemFontAndDisplaySizeScaling(newBase)
super.attachBaseContext(newBase)
}
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
enableEdgeToEdge()
@@ -39,4 +46,9 @@ class MainActivity : ComponentActivity() {
MainTheme(isDarkTheme = !otherSettings.stateOfLightTheme) { MainScreen() }
}
}
private fun overrideSystemFontAndDisplaySizeScaling(newBase: Context?) {
val newConfig = Configuration(newBase?.resources?.configuration).apply { fontScale = 1.0f }
applyOverrideConfiguration(newConfig)
}
}
@@ -17,7 +17,11 @@
*/
package com.rtbishop.look4sat
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.content.pm.ActivityInfo
import android.os.Bundle
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@@ -28,6 +32,7 @@ class MainApplication : Application() {
lateinit var container: MainContainer
override fun onCreate() {
disableMobileLandscapeLayout()
super.onCreate()
container = MainContainer(this)
// trigger automatic update every 48 hours
@@ -46,4 +51,23 @@ class MainApplication : Application() {
}
}
}
@SuppressLint("SourceLockedOrientationActivity")
private fun disableMobileLandscapeLayout() {
val isTablet = applicationContext.resources?.getBoolean(R.bool.isTablet) ?: true
val lifecycleAdapter = object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (!isTablet) {
activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
}
registerActivityLifecycleCallbacks(lifecycleAdapter)
}
}
@@ -1,46 +1,67 @@
package com.rtbishop.look4sat.presentation
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold
import androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.presentation.common.isVerticalLayout
import com.rtbishop.look4sat.presentation.map.mapDestination
import com.rtbishop.look4sat.presentation.passes.passesDestination
import com.rtbishop.look4sat.presentation.radar.radarDestination
import com.rtbishop.look4sat.presentation.satellites.satellitesDestination
import com.rtbishop.look4sat.presentation.settings.settingsDestination
sealed class Screen(val route: String) {
data object Satellites : Screen("satellites")
data object Passes : Screen("passes")
data object Radar : Screen("radar")
data object Map : Screen("map")
data object Settings : Screen("settings")
sealed class Screen(val route: String, val iconResId: Int, val titleResId: Int) {
data object Satellites : Screen("satellites", R.drawable.ic_sputnik, R.string.nav_satellites)
data object Passes : Screen("passes", R.drawable.ic_passes, R.string.nav_passes)
data object Radar : Screen("radar", R.drawable.ic_satellite, R.string.nav_radar)
data object Map : Screen("map", R.drawable.ic_map, R.string.nav_map)
data object Settings : Screen("settings", R.drawable.ic_settings, R.string.nav_settings)
}
data class NavActions(
val openSatellites: () -> Unit,
val openRadar: (Int, Long) -> Unit,
val openMap: () -> Unit,
val openSettings: () -> Unit
)
@Composable
fun MainScreen(navController: NavHostController = rememberNavController()) {
val navActions = NavActions(
openSatellites = { navController.navigate(Screen.Satellites.route) },
openRadar = { catNum: Int, aosTime: Long ->
val routeWithParams = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}"
navController.navigate(routeWithParams)
},
openMap = { navController.navigate(Screen.Map.route) },
openSettings = { navController.navigate(Screen.Settings.route) }
)
NavHost(navController = navController, startDestination = Screen.Passes.route) {
satellitesDestination { navController.navigateUp() }
passesDestination(navActions)
radarDestination { navController.navigateUp() }
mapDestination { navController.navigateUp() }
settingsDestination { navController.navigateUp() }
val items = listOf(Screen.Satellites, Screen.Passes, Screen.Radar, Screen.Map, Screen.Settings)
val currentDestination = navController.currentBackStackEntryAsState().value?.destination?.route
val startDestination = Screen.Passes.route
NavigationSuiteScaffold(
navigationSuiteItems = {
items.forEach {
item(
icon = { Icon(painterResource(it.iconResId), stringResource(it.titleResId)) },
label = { Text(stringResource(it.titleResId)) },
selected = currentDestination?.contains(it.route) ?: false,
onClick = {
if (currentDestination?.contains(it.route) ?: false) return@item
navController.navigate(it.route) {
popUpTo(startDestination) { saveState = false }
launchSingleTop = true
restoreState = false
}
})
}
}, layoutType = when {
isVerticalLayout() -> NavigationSuiteType.ShortNavigationBarCompact
else -> NavigationSuiteType.ShortNavigationBarMedium
}
) {
NavHost(navController = navController, startDestination = startDestination) {
satellitesDestination { navController.navigateUp() }
passesDestination { catNum: Int, aosTime: Long ->
val radarRoute = "${Screen.Radar.route}?catNum=${catNum}&aosTime=${aosTime}"
navController.navigate(radarRoute)
}
radarDestination { navController.navigateUp() }
mapDestination()
settingsDestination()
}
}
}
@@ -1,10 +1,5 @@
package com.rtbishop.look4sat.presentation.common
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.MarqueeSpacing
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.clickable
@@ -21,15 +16,12 @@ 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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
@@ -46,7 +38,6 @@ 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 androidx.window.core.layout.WindowWidthSizeClass
import com.rtbishop.look4sat.R
import com.rtbishop.look4sat.domain.predict.NearEarthObject
import com.rtbishop.look4sat.domain.predict.OrbitalData
@@ -308,8 +299,7 @@ fun InfoDialog(title: String, text: String, onDismiss: () -> Unit) {
@Composable
fun isVerticalLayout(): Boolean {
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
return windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT
return currentWindowAdaptiveInfo().windowSizeClass.isWidthAtLeastBreakpoint(600).not()
}
@Composable
@@ -331,8 +321,7 @@ fun ScreenColumn(
content: @Composable (Boolean) -> Unit = {}
) {
val surfaceCorners = LocalSpacing.current.large
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
val isVerticalLayout = windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT
val isVerticalLayout = isVerticalLayout()
Surface(color = MaterialTheme.colorScheme.background) {
Box(modifier = Modifier.layoutPadding(), contentAlignment = Alignment.BottomCenter) {
Column(verticalArrangement = Arrangement.spacedBy(LocalSpacing.current.extraSmall)) {
@@ -370,41 +359,3 @@ fun TopBar(
topBarRow { startAction().also { topInfo() }.also { bottomInfo() }.also { endAction() } }
}
}
@Composable
fun FloatingBar(
visible: Boolean,
startAction: @Composable () -> Unit,
centerAction: @Composable () -> Unit,
endAction: @Composable () -> Unit
) {
val spacing = LocalSpacing.current
AnimatedVisibility(visible = visible, enter = fadeIn(tween(250)), exit = fadeOut(tween(250))) {
Surface(
modifier = Modifier.padding(bottom = spacing.large),
shape = CircleShape,
color = MaterialTheme.colorScheme.background,
border = BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.surfaceVariant)
) {
Row(
modifier = Modifier.padding(horizontal = spacing.medium, vertical = spacing.small),
horizontalArrangement = Arrangement.spacedBy(spacing.extraSmall),
verticalAlignment = Alignment.CenterVertically
) { startAction().also { centerAction() }.also { endAction() } }
}
}
}
@Composable
fun PrimaryAction(action: () -> Unit, resId: Int, descResId: Int) {
FilledIconButton(onClick = action, modifier = Modifier.width(72.dp)) {
Icon(painterResource(resId), stringResource(descResId))
}
}
@Composable
fun SecondaryAction(action: () -> Unit, resId: Int, descResId: Int) {
IconButton(onClick = action) {
Icon(painterResource(resId), stringResource(descResId))
}
}
@@ -85,7 +85,7 @@ private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
}
private val labelRect = Rect()
fun NavGraphBuilder.mapDestination(function: () -> Boolean) {
fun NavGraphBuilder.mapDestination() {
composable(Screen.Map.route) {
val viewModel = viewModel(MapViewModel::class.java, factory = MapViewModel.Factory)
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
@@ -27,8 +27,6 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
@@ -48,16 +46,12 @@ import com.rtbishop.look4sat.domain.predict.NearEarthObject
import com.rtbishop.look4sat.domain.predict.OrbitalData
import com.rtbishop.look4sat.domain.predict.OrbitalPass
import com.rtbishop.look4sat.presentation.MainTheme
import com.rtbishop.look4sat.presentation.NavActions
import com.rtbishop.look4sat.presentation.Screen
import com.rtbishop.look4sat.presentation.common.EmptyListCard
import com.rtbishop.look4sat.presentation.common.FloatingBar
import com.rtbishop.look4sat.presentation.common.IconCard
import com.rtbishop.look4sat.presentation.common.InfoDialog
import com.rtbishop.look4sat.presentation.common.NextPassRow
import com.rtbishop.look4sat.presentation.common.PrimaryAction
import com.rtbishop.look4sat.presentation.common.ScreenColumn
import com.rtbishop.look4sat.presentation.common.SecondaryAction
import com.rtbishop.look4sat.presentation.common.TimerRow
import com.rtbishop.look4sat.presentation.common.TopBar
import com.rtbishop.look4sat.presentation.common.infiniteMarquee
@@ -69,19 +63,19 @@ import java.util.Locale
private val sdfDate = SimpleDateFormat("EEE dd MMM", Locale.ENGLISH)
private val sdfTime = SimpleDateFormat("HH:mm:ss", Locale.ENGLISH)
fun NavGraphBuilder.passesDestination(navActions: NavActions) {
fun NavGraphBuilder.passesDestination(navigateToRadar: (Int, Long) -> Unit) {
composable(Screen.Passes.route) {
val viewModel = viewModel(
modelClass = PassesViewModel::class.java,
factory = PassesViewModel.Factory
)
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
PassesScreen(uiState, navActions)
PassesScreen(uiState, navigateToRadar)
}
}
@Composable
private fun PassesScreen(uiState: PassesState, navActions: NavActions) {
private fun PassesScreen(uiState: PassesState, navigateToRadar: (Int, Long) -> Unit) {
val refreshList = { uiState.takeAction(PassesAction.RefreshPasses) }
val showPassesDialog = { uiState.takeAction(PassesAction.TogglePassesDialog) }
val showRadiosDialog = { uiState.takeAction(PassesAction.ToggleRadiosDialog) }
@@ -125,37 +119,18 @@ private fun PassesScreen(uiState: PassesState, navActions: NavActions) {
IconCard(action = showRadiosDialog, resId = R.drawable.ic_satellite)
}
)
},
floatingBar = {
PassesFloatingBar(visible = gridState.isScrolling(), actions = navActions)
}
) { isVerticalLayout ->
PassesList(
isRefreshing = uiState.isRefreshing,
passes = uiState.itemsList,
navigateToRadar = navActions.openRadar,
navigateToRadar = navigateToRadar,
refreshPasses = refreshList,
gridState = gridState
)
}
}
@Composable
private fun PassesFloatingBar(visible: Boolean, actions: NavActions) {
FloatingBar(
visible = visible,
startAction = {
SecondaryAction(actions.openSettings, R.drawable.ic_settings, R.string.btn_settings)
},
centerAction = {
PrimaryAction(actions.openSatellites, R.drawable.ic_sputnik, R.string.btn_satellites)
},
endAction = {
SecondaryAction(actions.openMap, R.drawable.ic_map, R.string.btn_map)
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PassesList(
@@ -342,8 +317,3 @@ private fun NearEarthPass(
}
}
}
@Composable
private fun LazyGridState.isScrolling(): Boolean {
return remember { derivedStateOf { !isScrollInProgress } }.value
}
@@ -50,7 +50,7 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
fun NavGraphBuilder.settingsDestination(function: () -> Boolean) {
fun NavGraphBuilder.settingsDestination() {
composable(Screen.Settings.route) {
val viewModel = viewModel(
modelClass = SettingsViewModel::class.java,
+1 -1
View File
@@ -1,4 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/background" />
<solid android:color="@color/accent" />
</shape>
+2 -2
View File
@@ -10,10 +10,10 @@
android:translateX="70.26573"
android:translateY="70.26573">
<path
android:fillColor="@color/accent"
android:fillColor="@color/background"
android:pathData="m96.64,31.5c7.91,8 17.14,13.58 25.36,15.58l1.75,-4C123.78,43.05 123.79,43.01 123.8,42.97 125.83,38.99 127,34.49 127,29.72 127,13.88 114.38,1 98.87,1c-5.13,0 -9.92,1.43 -14.06,3.88l-3.4,1.6c0.08,0.31 0.13,0.6 0.22,0.91 2.32,8.1 7.65,16.67 15.02,24.11z" />
<path
android:fillColor="@color/accent"
android:fillColor="@color/background"
android:pathData="M93.88,34.3C86.05,26.39 80.37,17.23 77.87,8.49 77.84,8.39 77.82,8.28 77.79,8.18L2.63,43.51c-1.4,0.66 -2.02,2.36 -1.37,3.79 0.47,1.05 1.49,1.66 2.55,1.66 0.39,0 0.79,-0.08 1.17,-0.26L74.04,16.24c-1.87,3.58 -3,7.61 -3.23,11.89L35.57,51.73c-1.29,0.87 -1.65,2.64 -0.81,3.96 0.54,0.84 1.43,1.29 2.34,1.29 0.53,0 1.06,-0.15 1.53,-0.47L71.19,34.71c1.4,8.11 6.14,15.04 12.72,19.28l-54.94,68.34c-0.98,1.22 -0.81,3.02 0.39,4.02 0.52,0.44 1.15,0.65 1.77,0.65 0.81,0 1.61,-0.36 2.17,-1.04L89.06,56.6c3.06,1.17 6.36,1.84 9.81,1.84 4.93,0 9.57,-1.31 13.6,-3.6L84.6,118.32c-0.63,1.44 0,3.13 1.41,3.78 0.37,0.17 0.76,0.25 1.14,0.25 1.07,0 2.09,-0.63 2.56,-1.69L120.4,50.74C111.64,48.42 102.04,42.56 93.88,34.3Z" />
</group>
</group>
+2 -2
View File
@@ -10,10 +10,10 @@
android:translateX="70.26573"
android:translateY="70.26573">
<path
android:fillColor="@color/accent"
android:fillColor="@color/background"
android:pathData="m96.64,31.5c7.91,8 17.14,13.58 25.36,15.58l1.75,-4C123.78,43.05 123.79,43.01 123.8,42.97 125.83,38.99 127,34.49 127,29.72 127,13.88 114.38,1 98.87,1c-5.13,0 -9.92,1.43 -14.06,3.88l-3.4,1.6c0.08,0.31 0.13,0.6 0.22,0.91 2.32,8.1 7.65,16.67 15.02,24.11z" />
<path
android:fillColor="@color/accent"
android:fillColor="@color/background"
android:pathData="M93.88,34.3C86.05,26.39 80.37,17.23 77.87,8.49 77.84,8.39 77.82,8.28 77.79,8.18L2.63,43.51c-1.4,0.66 -2.02,2.36 -1.37,3.79 0.47,1.05 1.49,1.66 2.55,1.66 0.39,0 0.79,-0.08 1.17,-0.26L74.04,16.24c-1.87,3.58 -3,7.61 -3.23,11.89L35.57,51.73c-1.29,0.87 -1.65,2.64 -0.81,3.96 0.54,0.84 1.43,1.29 2.34,1.29 0.53,0 1.06,-0.15 1.53,-0.47L71.19,34.71c1.4,8.11 6.14,15.04 12.72,19.28l-54.94,68.34c-0.98,1.22 -0.81,3.02 0.39,4.02 0.52,0.44 1.15,0.65 1.77,0.65 0.81,0 1.61,-0.36 2.17,-1.04L89.06,56.6c3.06,1.17 6.36,1.84 9.81,1.84 4.93,0 9.57,-1.31 13.6,-3.6L84.6,118.32c-0.63,1.44 0,3.13 1.41,3.78 0.37,0.17 0.76,0.25 1.14,0.25 1.07,0 2.09,-0.63 2.56,-1.69L120.4,50.74C111.64,48.42 102.04,42.56 93.88,34.3Z" />
</group>
</group>
+4 -4
View File
@@ -5,10 +5,10 @@
<string name="app_version">Версия: %s</string>
<string name="app_subtitle">Трекер спутников для Android</string>
<string name="menu_satellites">Спутники</string>
<string name="menu_passes">Пролеты</string>
<string name="menu_map">Карта</string>
<string name="menu_settings">Настройки</string>
<string name="nav_satellites">Спутники</string>
<string name="nav_passes">Пролеты</string>
<string name="nav_map">Карта</string>
<string name="nav_settings">Настройки</string>
<string name="menu_about">Инфо</string>
<!--Buttons-->
+4 -4
View File
@@ -5,10 +5,10 @@
<string name="app_version">අනුවාදය: %s</string>
<string name="app_subtitle">චන්ද්‍රිකා tracker හා නිසැක පෙරැයින්නා</string>
<string name="menu_satellites">චන්ද්‍රිකා</string>
<string name="menu_passes">පසුකර යෑම්</string>
<string name="menu_map">ලෝක සිතියම</string>
<string name="menu_settings">පසුතල</string>
<string name="nav_satellites">චන්ද්‍රිකා</string>
<string name="nav_passes">පසුකර යෑම්</string>
<string name="nav_map">ලෝක සිතියම</string>
<string name="nav_settings">පසුතල</string>
<string name="menu_about">ගැන</string>
<!--Buttons-->
@@ -0,0 +1,3 @@
<resources>
<bool name="isTablet">true</bool>
</resources>
+4 -4
View File
@@ -5,10 +5,10 @@
<string name="app_version">Версія: %s</string>
<string name="app_subtitle">Трекер супутників та прогноз прольотів</string>
<string name="menu_satellites">Супутники</string>
<string name="menu_passes">Прольоти</string>
<string name="menu_map">Мапа</string>
<string name="menu_settings">Налаштування</string>
<string name="nav_satellites">Супутники</string>
<string name="nav_passes">Прольоти</string>
<string name="nav_map">Мапа</string>
<string name="nav_settings">Налаштування</string>
<string name="menu_about">Інфо</string>
<!--Buttons-->
+4 -4
View File
@@ -5,10 +5,10 @@
<string name="app_version">版本:%s</string>
<string name="app_subtitle">人造卫星追踪和过境预测</string>
<string name="menu_satellites">卫星</string>
<string name="menu_passes">过境</string>
<string name="menu_map">世界地图</string>
<string name="menu_settings">设置</string>
<string name="nav_satellites">卫星</string>
<string name="nav_passes">过境</string>
<string name="nav_map">世界地图</string>
<string name="nav_settings">设置</string>
<string name="menu_about">关于</string>
<!--Buttons-->
+5 -5
View File
@@ -5,11 +5,11 @@
<string name="app_version">Version: %s</string>
<string name="app_subtitle">Satellite tracker and pass predictor</string>
<string name="menu_satellites">Satellites</string>
<string name="menu_passes">Passes</string>
<string name="menu_map">World Map</string>
<string name="menu_settings">Settings</string>
<string name="menu_about">About</string>
<string name="nav_satellites">Satellites</string>
<string name="nav_passes">Passes</string>
<string name="nav_radar">Radar</string>
<string name="nav_map">World Map</string>
<string name="nav_settings">Settings</string>
<!--Buttons-->
<string name="btn_add">Add</string>
+1 -3
View File
@@ -1,12 +1,10 @@
<resources>
<bool name="isTablet">false</bool>
<color name="accent">#FFE082</color>
<color name="background">#121212</color>
<style name="Theme.Look4Sat.SplashScreen" parent="Theme.SplashScreen">
<item name="postSplashScreenTheme">@android:style/Theme.Material.NoActionBar</item>
<item name="windowSplashScreenBackground">@color/background</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/launcher_fg</item>
</style>
</resources>