diff --git a/app/src/main/java/com/rtbishop/look4sat/MainActivity.kt b/app/src/main/java/com/rtbishop/look4sat/MainActivity.kt index 392d7a06..686b9918 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainActivity.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainActivity.kt @@ -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) + } } diff --git a/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt b/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt index 317d0b91..bff62d58 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainApplication.kt @@ -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) + } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt index 585464b7..c608789a 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/MainScreen.kt @@ -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() + } } } diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt index 79b43f5a..1ce7998b 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/common/Components.kt @@ -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)) - } -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt index 38d024b4..e1e7109e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/map/MapScreen.kt @@ -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() diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt index 7fd4bdc6..d11ad32e 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/passes/PassesScreen.kt @@ -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 -} diff --git a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt index 91b880b7..0ab5630b 100644 --- a/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/presentation/settings/SettingsScreen.kt @@ -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, diff --git a/app/src/main/res/drawable/launcher_bg.xml b/app/src/main/res/drawable/launcher_bg.xml index ad787e03..3b097662 100644 --- a/app/src/main/res/drawable/launcher_bg.xml +++ b/app/src/main/res/drawable/launcher_bg.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/drawable/launcher_fg.xml b/app/src/main/res/drawable/launcher_fg.xml index a9e2f0cd..450472b4 100644 --- a/app/src/main/res/drawable/launcher_fg.xml +++ b/app/src/main/res/drawable/launcher_fg.xml @@ -10,10 +10,10 @@ android:translateX="70.26573" android:translateY="70.26573"> diff --git a/app/src/main/res/drawable/launcher_mc.xml b/app/src/main/res/drawable/launcher_mc.xml index a9e2f0cd..450472b4 100644 --- a/app/src/main/res/drawable/launcher_mc.xml +++ b/app/src/main/res/drawable/launcher_mc.xml @@ -10,10 +10,10 @@ android:translateX="70.26573" android:translateY="70.26573"> diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 4c9c101b..0167df7c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -5,10 +5,10 @@ Версия: %s Трекер спутников для Android - Спутники - Пролеты - Карта - Настройки + Спутники + Пролеты + Карта + Настройки Инфо diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index 56968caa..d4d6b809 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -5,10 +5,10 @@ අනුවාදය: %s චන්ද්‍රිකා tracker හා නිසැක පෙරැයින්නා - චන්ද්‍රිකා - පසුකර යෑම් - ලෝක සිතියම - පසුතල + චන්ද්‍රිකා + පසුකර යෑම් + ලෝක සිතියම + පසුතල ගැන diff --git a/app/src/main/res/values-sw600dp/themes.xml b/app/src/main/res/values-sw600dp/themes.xml new file mode 100644 index 00000000..3dd57b65 --- /dev/null +++ b/app/src/main/res/values-sw600dp/themes.xml @@ -0,0 +1,3 @@ + + true + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 2e06447d..a59dc6bf 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -5,10 +5,10 @@ Версія: %s Трекер супутників та прогноз прольотів - Супутники - Прольоти - Мапа - Налаштування + Супутники + Прольоти + Мапа + Налаштування Інфо diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 928f4e4b..638ef8a0 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -5,10 +5,10 @@ 版本:%s 人造卫星追踪和过境预测 - 卫星 - 过境 - 世界地图 - 设置 + 卫星 + 过境 + 世界地图 + 设置 关于 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a8d7faef..f98d4288 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,11 +5,11 @@ Version: %s Satellite tracker and pass predictor - Satellites - Passes - World Map - Settings - About + Satellites + Passes + Radar + World Map + Settings Add diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index c73371e0..494a7038 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,12 +1,10 @@ - + false #FFE082 #121212 - - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15d3c07c..041b9089 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = " compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } compose-animation = { group = "androidx.compose.animation", name = "animation" } compose-material3 = { group = "androidx.compose.material3", name = "material3" } -compose-material3-adaptive = { group = "androidx.compose.material3.adaptive", name = "adaptive" } +compose-material3-navigation = { group = "androidx.compose.material3", name = "material3-adaptive-navigation-suite" } compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } compose-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } @@ -69,7 +69,7 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } composeAll = [ "compose-animation", "compose-runtime", "compose-tooling", "compose-activity", "compose-lifecycle", "compose-material3", - "compose-material3-adaptive", "compose-navigation", "compose-viewmodel" + "compose-material3-navigation", "compose-navigation", "compose-viewmodel" ] composeDebug = ["compose-debug-manifest", "compose-debug-tooling"] unitTest = ["test-coroutines", "test-junit4", "test-mockk"]