mirror of
https://github.com/atsunatsu/Look4Sat.git
synced 2026-10-04 11:37:22 +00:00
Switched back to bottom navigation, tweaked components
This commit is contained in:
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,
|
||||
|
||||
Reference in new issue
Block a user