diff --git a/androidApp/src/androidMain/kotlin/com/uno/android/MainActivity.kt b/androidApp/src/androidMain/kotlin/com/uno/android/MainActivity.kt index 7fe0ad3..7f82585 100644 --- a/androidApp/src/androidMain/kotlin/com/uno/android/MainActivity.kt +++ b/androidApp/src/androidMain/kotlin/com/uno/android/MainActivity.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.unit.dp import com.uno.game.GameEngine import com.uno.game.model.* import com.uno.shared.UnoGameScreen +import com.uno.shared.UnoTheme import kotlinx.coroutines.delay class MainActivity : ComponentActivity() { @@ -20,7 +21,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - MaterialTheme { + UnoTheme { Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { UnoApp() } diff --git a/desktopApp/src/jvmMain/kotlin/com/uno/desktop/Main.kt b/desktopApp/src/jvmMain/kotlin/com/uno/desktop/Main.kt index 9a06a76..6233c09 100644 --- a/desktopApp/src/jvmMain/kotlin/com/uno/desktop/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/uno/desktop/Main.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.window.application import com.uno.game.GameEngine import com.uno.game.model.* import com.uno.shared.UnoGameScreen +import com.uno.shared.UnoTheme import kotlinx.coroutines.delay fun main() = application { @@ -49,31 +50,33 @@ fun UnoDesktopApp() { } } - if (!gameStarted) { - DesktopStartScreen(onStartGame = { playerCount -> - gameState = engine.startGame(playerCount) - gameStarted = true - }) - } else { - UnoGameScreen( - state = gameState, - onPlayCard = { card, color -> - gameState = engine.playCard(gameState, 0, card, color) - }, - onDrawCard = { - gameState = engine.drawCard(gameState, 0) - }, - onCallUno = { gameState = engine.callUno(gameState, 0) } - ) - - if (gameState.isFinished) { - DesktopGameOverScreen( - winner = gameState.winner, - onPlayAgain = { - gameState = engine.startGame() - gameStarted = false - } + UnoTheme { + if (!gameStarted) { + DesktopStartScreen(onStartGame = { playerCount -> + gameState = engine.startGame(playerCount) + gameStarted = true + }) + } else { + UnoGameScreen( + state = gameState, + onPlayCard = { card, color -> + gameState = engine.playCard(gameState, 0, card, color) + }, + onDrawCard = { + gameState = engine.drawCard(gameState, 0) + }, + onCallUno = { gameState = engine.callUno(gameState, 0) } ) + + if (gameState.isFinished) { + DesktopGameOverScreen( + winner = gameState.winner, + onPlayAgain = { + gameState = engine.startGame() + gameStarted = false + } + ) + } } } } diff --git a/shared/src/commonMain/kotlin/com/uno/shared/UnoGameScreen.kt b/shared/src/commonMain/kotlin/com/uno/shared/UnoGameScreen.kt index 84abab7..3e2619b 100644 --- a/shared/src/commonMain/kotlin/com/uno/shared/UnoGameScreen.kt +++ b/shared/src/commonMain/kotlin/com/uno/shared/UnoGameScreen.kt @@ -63,7 +63,7 @@ private class FlyingCardState { animatableX = Animatable(fromX) animatableY = Animatable(fromY) animatableScale = Animatable(0.3f) - animatableRotation = Animatable((Math.random() * 60 - 30).toFloat()) + animatableRotation = Animatable((kotlin.random.Random.nextFloat() * 60 - 30).toFloat()) coroutineScope { launch { @@ -274,6 +274,7 @@ fun UnoGameScreen( var previousLastAction by remember { mutableStateOf(state.lastAction) } var previousTopCard by remember { mutableStateOf(state.deck.topCard) } var lastPlayedByPlayerIndex by remember { mutableIntStateOf(0) } + var isDarkMode by remember { mutableStateOf(false) } val flyingState = rememberFlyingCardState() @@ -317,14 +318,15 @@ fun UnoGameScreen( previousLastAction = state.lastAction } - Box(modifier = Modifier.fillMaxSize()) { - Column( - modifier = Modifier - .fillMaxSize() - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - GameInfoBar(state) + UnoTheme(darkTheme = isDarkMode) { + Box(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + GameInfoBar(state, isDarkMode, { isDarkMode = it }) Spacer(modifier = Modifier.height(12.dp)) OpponentHands(state) Spacer(modifier = Modifier.height(12.dp)) @@ -417,12 +419,13 @@ fun UnoGameScreen( } ) } + } } // region --- GameInfoBar --- @Composable -private fun GameInfoBar(state: GameState) { +private fun GameInfoBar(state: GameState, isDarkMode: Boolean, onDarkModeToggle: (Boolean) -> Unit) { val directionRotation by animateFloatAsState( targetValue = if (state.direction == GameDirection.CLOCKWISE) 0f else 180f, animationSpec = tween(500, easing = FastOutSlowInEasing), @@ -462,6 +465,13 @@ private fun GameInfoBar(state: GameState) { ) } } + // Dark mode toggle + IconButton(onClick = { onDarkModeToggle(!isDarkMode) }) { + Text( + text = if (isDarkMode) "\u2600\uFE0F" else "\uD83C\uDF19", + style = MaterialTheme.typography.bodyLarge + ) + } } } } diff --git a/shared/src/commonMain/kotlin/com/uno/shared/UnoTheme.kt b/shared/src/commonMain/kotlin/com/uno/shared/UnoTheme.kt new file mode 100644 index 0000000..9af3f52 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/uno/shared/UnoTheme.kt @@ -0,0 +1,57 @@ +package com.uno.shared + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +private val LightColorScheme = lightColorScheme( + primary = Color(0xFF1976D2), + onPrimary = Color.White, + primaryContainer = Color(0xFFBBDEFB), + onPrimaryContainer = Color(0xFF0D47A1), + secondary = Color(0xFFFF6D00), + onSecondary = Color.White, + secondaryContainer = Color(0xFFFFE0B2), + onSecondaryContainer = Color(0xFFE65100), + background = Color(0xFFF5F5F5), + onBackground = Color(0xFF1C1B1F), + surface = Color.White, + onSurface = Color(0xFF1C1B1F), + surfaceVariant = Color(0xFFE7E0EC), + onSurfaceVariant = Color(0xFF49454F), + error = Color(0xFFE53935), + onError = Color.White, +) + +private val DarkColorScheme = darkColorScheme( + primary = Color(0xFF90CAF9), + onPrimary = Color(0xFF0D47A1), + primaryContainer = Color(0xFF1565C0), + onPrimaryContainer = Color(0xFFBBDEFB), + secondary = Color(0xFFFFB74D), + onSecondary = Color(0xFF3E2723), + secondaryContainer = Color(0xFFE65100), + onSecondaryContainer = Color(0xFFFFE0B2), + background = Color(0xFF121212), + onBackground = Color(0xFFE6E1E5), + surface = Color(0xFF1E1E1E), + onSurface = Color(0xFFE6E1E5), + surfaceVariant = Color(0xFF2C2C2C), + onSurfaceVariant = Color(0xFFCAC4D0), + error = Color(0xFFEF5350), + onError = Color(0xFF601010), +) + +@Composable +fun UnoTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme + + MaterialTheme( + colorScheme = colorScheme, + content = content + ) +} diff --git a/webApp/src/wasmJsMain/kotlin/com/uno/web/Main.kt b/webApp/src/wasmJsMain/kotlin/com/uno/web/Main.kt index f8cdb02..141c5f2 100644 --- a/webApp/src/wasmJsMain/kotlin/com/uno/web/Main.kt +++ b/webApp/src/wasmJsMain/kotlin/com/uno/web/Main.kt @@ -4,26 +4,25 @@ import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.CanvasBasedWindow import com.uno.game.GameEngine import com.uno.game.model.* import com.uno.shared.UnoGameScreen -import kotlinx.browser.document +import com.uno.shared.UnoTheme import kotlinx.coroutines.delay -import org.w3c.dom.HTMLElement +@OptIn(ExperimentalComposeUiApi::class) fun main() { - val root = document.getElementById("root") as HTMLElement - root.innerHTML = "" - val container = document.createElement("div") - container.id = "app" - root.appendChild(container) - composeWebRoot() + CanvasBasedWindow(canvasElementId = "ComposeTarget") { + UnoApp() + } } @Composable -fun composeWebRoot() { +fun UnoApp() { val engine = remember { GameEngine() } var gameState by remember { mutableStateOf(engine.startGame()) } var gameStarted by remember { mutableStateOf(false) } @@ -45,31 +44,33 @@ fun composeWebRoot() { } } - if (!gameStarted) { - WebStartScreen(onStartGame = { playerCount -> - gameState = engine.startGame(playerCount) - gameStarted = true - }) - } else { - UnoGameScreen( - state = gameState, - onPlayCard = { card, color -> - gameState = engine.playCard(gameState, 0, card, color) - }, - onDrawCard = { - gameState = engine.drawCard(gameState, 0) - }, - onCallUno = { gameState = engine.callUno(gameState, 0) } - ) - - if (gameState.isFinished) { - WebGameOverScreen( - winner = gameState.winner, - onPlayAgain = { - gameState = engine.startGame() - gameStarted = false - } + UnoTheme { + if (!gameStarted) { + WebStartScreen(onStartGame = { playerCount -> + gameState = engine.startGame(playerCount) + gameStarted = true + }) + } else { + UnoGameScreen( + state = gameState, + onPlayCard = { card, color -> + gameState = engine.playCard(gameState, 0, card, color) + }, + onDrawCard = { + gameState = engine.drawCard(gameState, 0) + }, + onCallUno = { gameState = engine.callUno(gameState, 0) } ) + + if (gameState.isFinished) { + WebGameOverScreen( + winner = gameState.winner, + onPlayAgain = { + gameState = engine.startGame() + gameStarted = false + } + ) + } } } }