feat: add Material 3 theme and screen navigation

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-10 02:37:26 -04:00
parent ca7b757812
commit cacbb0d98d
10 changed files with 301 additions and 15 deletions

View File

@@ -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)

View File

@@ -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>(Screen.StationList) }
BackHandler(enabled = currentScreen != Screen.StationList) {
currentScreen = Screen.StationList
}
@Composable
private fun Radio247Placeholder() {
Text("24/7 Radio")
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 }
)
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun Radio247PlaceholderPreview() {
Radio247Placeholder()
}

View File

@@ -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()
}

View File

@@ -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")
}
}

View File

@@ -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")
}
}

View File

@@ -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") }
}
}

View File

@@ -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)

View File

@@ -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
)
}

View File

@@ -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
)
)

View File

@@ -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" }