feat: add Material 3 theme and screen navigation
Made-with: Cursor
This commit is contained in:
@@ -55,6 +55,8 @@ dependencies {
|
|||||||
implementation(libs.datastore.preferences)
|
implementation(libs.datastore.preferences)
|
||||||
implementation(libs.okhttp)
|
implementation(libs.okhttp)
|
||||||
implementation(libs.lifecycle.viewmodel.compose)
|
implementation(libs.lifecycle.viewmodel.compose)
|
||||||
|
implementation(libs.lifecycle.viewmodel.ktx)
|
||||||
|
implementation(libs.material.icons.extended)
|
||||||
implementation(libs.lifecycle.runtime.compose)
|
implementation(libs.lifecycle.runtime.compose)
|
||||||
implementation(libs.lifecycle.service)
|
implementation(libs.lifecycle.service)
|
||||||
implementation(libs.coroutines.core)
|
implementation(libs.coroutines.core)
|
||||||
|
|||||||
@@ -2,27 +2,42 @@ package xyz.cottongin.radio247
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
Radio247Placeholder()
|
Radio247Theme {
|
||||||
|
var currentScreen by remember { mutableStateOf<Screen>(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()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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") }
|
||||||
|
}
|
||||||
|
}
|
||||||
23
app/src/main/java/xyz/cottongin/radio247/ui/theme/Color.kt
Normal file
23
app/src/main/java/xyz/cottongin/radio247/ui/theme/Color.kt
Normal 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)
|
||||||
70
app/src/main/java/xyz/cottongin/radio247/ui/theme/Theme.kt
Normal file
70
app/src/main/java/xyz/cottongin/radio247/ui/theme/Theme.kt
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
100
app/src/main/java/xyz/cottongin/radio247/ui/theme/Type.kt
Normal file
100
app/src/main/java/xyz/cottongin/radio247/ui/theme/Type.kt
Normal 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
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -30,6 +30,8 @@ datastore-preferences = { group = "androidx.datastore", name = "datastore-prefer
|
|||||||
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
|
||||||
okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", 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-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-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
|
||||||
lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", 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" }
|
coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||||
|
|||||||
Reference in New Issue
Block a user