From cacbb0d98d5e595771d636f0c6fc284a92eea5f3 Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 10 Mar 2026 02:37:26 -0400 Subject: [PATCH] feat: add Material 3 theme and screen navigation Made-with: Cursor --- app/build.gradle.kts | 2 + .../xyz/cottongin/radio247/MainActivity.kt | 45 +++++--- .../radio247/ui/navigation/Screen.kt | 7 ++ .../ui/screens/nowplaying/NowPlayingScreen.kt | 23 ++++ .../ui/screens/settings/SettingsScreen.kt | 23 ++++ .../screens/stationlist/StationListScreen.kt | 21 ++++ .../xyz/cottongin/radio247/ui/theme/Color.kt | 23 ++++ .../xyz/cottongin/radio247/ui/theme/Theme.kt | 70 ++++++++++++ .../xyz/cottongin/radio247/ui/theme/Type.kt | 100 ++++++++++++++++++ gradle/libs.versions.toml | 2 + 10 files changed, 301 insertions(+), 15 deletions(-) create mode 100644 app/src/main/java/xyz/cottongin/radio247/ui/navigation/Screen.kt create mode 100644 app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt create mode 100644 app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt create mode 100644 app/src/main/java/xyz/cottongin/radio247/ui/screens/stationlist/StationListScreen.kt create mode 100644 app/src/main/java/xyz/cottongin/radio247/ui/theme/Color.kt create mode 100644 app/src/main/java/xyz/cottongin/radio247/ui/theme/Theme.kt create mode 100644 app/src/main/java/xyz/cottongin/radio247/ui/theme/Type.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e78f6d4..eeb0cd0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -55,6 +55,8 @@ dependencies { implementation(libs.datastore.preferences) implementation(libs.okhttp) implementation(libs.lifecycle.viewmodel.compose) + implementation(libs.lifecycle.viewmodel.ktx) + implementation(libs.material.icons.extended) implementation(libs.lifecycle.runtime.compose) implementation(libs.lifecycle.service) implementation(libs.coroutines.core) diff --git a/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt b/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt index b551a5c..46ef152 100644 --- a/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt +++ b/app/src/main/java/xyz/cottongin/radio247/MainActivity.kt @@ -2,27 +2,42 @@ package xyz.cottongin.radio247 import android.os.Bundle import androidx.activity.ComponentActivity +import androidx.activity.compose.BackHandler import androidx.activity.compose.setContent -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import xyz.cottongin.radio247.ui.navigation.Screen +import xyz.cottongin.radio247.ui.screens.nowplaying.NowPlayingScreen +import xyz.cottongin.radio247.ui.screens.settings.SettingsScreen +import xyz.cottongin.radio247.ui.screens.stationlist.StationListScreen +import xyz.cottongin.radio247.ui.theme.Radio247Theme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - Radio247Placeholder() + Radio247Theme { + var currentScreen by remember { mutableStateOf(Screen.StationList) } + + BackHandler(enabled = currentScreen != Screen.StationList) { + currentScreen = Screen.StationList + } + + when (currentScreen) { + Screen.StationList -> StationListScreen( + onNavigateToNowPlaying = { currentScreen = Screen.NowPlaying }, + onNavigateToSettings = { currentScreen = Screen.Settings } + ) + Screen.NowPlaying -> NowPlayingScreen( + onBack = { currentScreen = Screen.StationList } + ) + Screen.Settings -> SettingsScreen( + onBack = { currentScreen = Screen.StationList } + ) + } + } } } } - -@Composable -private fun Radio247Placeholder() { - Text("24/7 Radio") -} - -@Preview(showBackground = true) -@Composable -private fun Radio247PlaceholderPreview() { - Radio247Placeholder() -} diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/navigation/Screen.kt b/app/src/main/java/xyz/cottongin/radio247/ui/navigation/Screen.kt new file mode 100644 index 0000000..666cd3b --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/ui/navigation/Screen.kt @@ -0,0 +1,7 @@ +package xyz.cottongin.radio247.ui.navigation + +sealed class Screen { + data object StationList : Screen() + data object NowPlaying : Screen() + data object Settings : Screen() +} diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt b/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt new file mode 100644 index 0000000..42ab819 --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/ui/screens/nowplaying/NowPlayingScreen.kt @@ -0,0 +1,23 @@ +package xyz.cottongin.radio247.ui.screens.nowplaying + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun NowPlayingScreen( + onBack: () -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier) { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + Text("Now Playing") + } +} diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt b/app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt new file mode 100644 index 0000000..0f88261 --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/ui/screens/settings/SettingsScreen.kt @@ -0,0 +1,23 @@ +package xyz.cottongin.radio247.ui.screens.settings + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun SettingsScreen( + onBack: () -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier) { + IconButton(onClick = onBack) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + Text("Settings") + } +} diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/screens/stationlist/StationListScreen.kt b/app/src/main/java/xyz/cottongin/radio247/ui/screens/stationlist/StationListScreen.kt new file mode 100644 index 0000000..850c0ce --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/ui/screens/stationlist/StationListScreen.kt @@ -0,0 +1,21 @@ +package xyz.cottongin.radio247.ui.screens.stationlist + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun StationListScreen( + onNavigateToNowPlaying: () -> Unit, + onNavigateToSettings: () -> Unit, + modifier: Modifier = Modifier +) { + // Placeholder - full implementation in Task 10 + Column(modifier) { + Text("Station List") + Button(onClick = onNavigateToNowPlaying) { Text("Now Playing") } + Button(onClick = onNavigateToSettings) { Text("Settings") } + } +} diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/theme/Color.kt b/app/src/main/java/xyz/cottongin/radio247/ui/theme/Color.kt new file mode 100644 index 0000000..0bee5bc --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/ui/theme/Color.kt @@ -0,0 +1,23 @@ +package xyz.cottongin.radio247.ui.theme + +import androidx.compose.ui.graphics.Color + +// Radio/broadcast themed palette - dark blues, amber accents +// Light scheme +val BlueGray900 = Color(0xFF0D1B2A) +val BlueGray800 = Color(0xFF1B263B) +val BlueGray700 = Color(0xFF415A77) +val BlueGray600 = Color(0xFF778DA9) +val BlueGray500 = Color(0xFFE0E1DD) +val Amber500 = Color(0xFFFFB300) +val Amber400 = Color(0xFFFFC107) +val Amber300 = Color(0xFFFFD54F) + +// Dark scheme +val BlueGray100 = Color(0xFFE8EDF2) +val BlueGray200 = Color(0xFFC5CBD3) +val BlueGray300 = Color(0xFF778DA9) +val BlueGray400 = Color(0xFF415A77) +val BlueGray950 = Color(0xFF0A0F14) +val Amber200 = Color(0xFFFFE082) +val Amber100 = Color(0xFFFFECB3) diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/theme/Theme.kt b/app/src/main/java/xyz/cottongin/radio247/ui/theme/Theme.kt new file mode 100644 index 0000000..c3d8d1c --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +package xyz.cottongin.radio247.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Amber400, + onPrimary = BlueGray950, + primaryContainer = BlueGray700, + onPrimaryContainer = BlueGray100, + secondary = BlueGray400, + onSecondary = BlueGray100, + secondaryContainer = BlueGray800, + onSecondaryContainer = BlueGray200, + tertiary = Amber300, + onTertiary = BlueGray950, + background = BlueGray950, + onBackground = BlueGray100, + surface = BlueGray900, + onSurface = BlueGray100, + surfaceVariant = BlueGray800, + onSurfaceVariant = BlueGray300, + outline = BlueGray600, + outlineVariant = BlueGray700 +) + +private val LightColorScheme = lightColorScheme( + primary = Amber500, + onPrimary = BlueGray900, + primaryContainer = Amber300, + onPrimaryContainer = BlueGray900, + secondary = BlueGray600, + onSecondary = BlueGray100, + secondaryContainer = BlueGray200, + onSecondaryContainer = BlueGray800, + tertiary = Amber400, + onTertiary = BlueGray900, + background = BlueGray100, + onBackground = BlueGray900, + surface = BlueGray500, + onSurface = BlueGray900, + surfaceVariant = BlueGray200, + onSurfaceVariant = BlueGray700, + outline = BlueGray600, + outlineVariant = BlueGray400 +) + +@Composable +fun Radio247Theme(content: @Composable () -> Unit) { + val dynamicColor = Build.VERSION.SDK_INT >= 31 + val darkTheme = isSystemInDarkTheme() + val colorScheme = when { + dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current) + dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current) + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/app/src/main/java/xyz/cottongin/radio247/ui/theme/Type.kt b/app/src/main/java/xyz/cottongin/radio247/ui/theme/Type.kt new file mode 100644 index 0000000..a6070e2 --- /dev/null +++ b/app/src/main/java/xyz/cottongin/radio247/ui/theme/Type.kt @@ -0,0 +1,100 @@ +package xyz.cottongin.radio247.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +val Typography = Typography( + displayLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 57.sp, + lineHeight = 64.sp + ), + displayMedium = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 45.sp, + lineHeight = 52.sp + ), + displaySmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 36.sp, + lineHeight = 44.sp + ), + headlineLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 32.sp, + lineHeight = 40.sp + ), + headlineMedium = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 28.sp, + lineHeight = 36.sp + ), + headlineSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 24.sp, + lineHeight = 32.sp + ), + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 22.sp, + lineHeight = 28.sp + ), + titleMedium = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 24.sp + ), + titleSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp + ), + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp + ), + bodyMedium = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 14.sp, + lineHeight = 20.sp + ), + bodySmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp, + lineHeight = 16.sp + ), + labelLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 20.sp + ), + labelMedium = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + lineHeight = 16.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp + ) +) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fabbe57..08ff1de 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,6 +30,8 @@ datastore-preferences = { group = "androidx.datastore", name = "datastore-prefer okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" } lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } +lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" } +material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "lifecycle" } coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }