Skip to content

Commit f5cf75c

Browse files
committed
Redesign sample app with modern Material3 adaptive UI
Complete rewrite of the sample app with: - Immersive video player: fullscreen with overlay controls, gradient scrims, auto-hide - Adaptive navigation: NavigationBar (mobile <600dp) + NavigationRail (tablet ≥600dp) - Netflix-style gallery: hero card + responsive grid (2-5 columns by width) - Social media feed: posts with engagement metrics, adaptive columns (1-3) - Modern Material3 theme: custom violet/teal palette, proper light/dark variants - Responsive layouts: player adjusts for portrait/landscape with proper constraints - Working video URLs: replaced dead googleapis links with w3.org, w3schools, archive.org - Clean architecture: feature-based package structure (player/, gallery/, feed/, theme/) - Bottom sheets for all secondary controls: source selection, settings, subtitles All features (volume, speed, loop, PiP, subtitles, metadata) showcase in the UI.
1 parent ec57ac2 commit f5cf75c

16 files changed

Lines changed: 1602 additions & 1777 deletions

gradle/libs.versions.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ androidx-core = "1.18.0"
1313
media3Exoplayer = "1.10.0"
1414
detekt = "1.23.8"
1515
ktlint = "14.2.0"
16+
androidx-lifecycle-runtime-ktx = "2.10.0"
1617

1718
# minSdk = 21 failed to compile because the project indirectly depends on the library [androidx.navigationevent:navigationevent-android:1.0.1] which requires minSdk = 23
1819
android-minSdk="23"
@@ -40,7 +41,7 @@ compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "compose" }
4041
compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "compose" }
4142
compose-ui-tooling-preview = { module = "org.jetbrains.compose.components:components-ui-tooling-preview", version.ref = "compose" }
4243
compose-material-icons-extended = { module = "org.jetbrains.compose.material:material-icons-extended", version = "1.7.3" }
43-
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "kotlin" }
44+
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "androidx-lifecycle-runtime-ktx" }
4445

4546
[plugins]
4647

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,104 @@
11
package sample.app
22

3-
import androidx.compose.foundation.background
4-
import androidx.compose.foundation.layout.Box
3+
import androidx.compose.foundation.layout.BoxWithConstraints
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.Spacer
6+
import androidx.compose.foundation.layout.fillMaxHeight
57
import androidx.compose.foundation.layout.fillMaxSize
68
import androidx.compose.foundation.layout.padding
79
import androidx.compose.material.icons.Icons
10+
import androidx.compose.material.icons.automirrored.filled.Article
811
import androidx.compose.material.icons.automirrored.filled.List
9-
import androidx.compose.material.icons.filled.Home
10-
import androidx.compose.material.icons.filled.Subtitles
11-
import androidx.compose.material3.*
12-
import androidx.compose.runtime.*
12+
import androidx.compose.material.icons.filled.PlayCircle
13+
import androidx.compose.material3.Icon
14+
import androidx.compose.material3.NavigationBar
15+
import androidx.compose.material3.NavigationBarItem
16+
import androidx.compose.material3.NavigationRail
17+
import androidx.compose.material3.NavigationRailItem
18+
import androidx.compose.material3.Scaffold
19+
import androidx.compose.material3.Text
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
1325
import androidx.compose.ui.Modifier
14-
import androidx.compose.foundation.isSystemInDarkTheme
15-
import sample.app.singleplayer.SinglePlayerScreen
26+
import androidx.compose.ui.graphics.vector.ImageVector
27+
import androidx.compose.ui.unit.dp
28+
import sample.app.feed.FeedScreen
29+
import sample.app.gallery.GalleryScreen
30+
import sample.app.player.PlayerScreen
31+
import sample.app.theme.AppTheme
32+
33+
private enum class Screen(val label: String, val icon: ImageVector) {
34+
Player("Player", Icons.Default.PlayCircle),
35+
Gallery("Gallery", Icons.AutoMirrored.Filled.List),
36+
Feed("Feed", Icons.AutoMirrored.Filled.Article),
37+
}
1638

1739
@Composable
1840
fun App() {
19-
MaterialTheme(colorScheme = if(isSystemInDarkTheme()) darkColorScheme() else lightColorScheme()) {
20-
// Navigation state
21-
var currentScreen by remember { mutableStateOf(Screen.SinglePlayer) }
41+
AppTheme {
42+
var currentScreen by remember { mutableStateOf(Screen.Player) }
2243

23-
Scaffold(
24-
bottomBar = {
25-
NavigationBar {
26-
NavigationBarItem(
27-
icon = { Icon(Icons.Default.Home, contentDescription = "Single Player") },
28-
label = { Text("Single Player") },
29-
selected = currentScreen == Screen.SinglePlayer,
30-
onClick = { currentScreen = Screen.SinglePlayer }
31-
)
32-
NavigationBarItem(
33-
icon = { Icon(Icons.AutoMirrored.Filled.List, contentDescription = "Multi Player") },
34-
label = { Text("Multi Player") },
35-
selected = currentScreen == Screen.MultiPlayer,
36-
onClick = { currentScreen = Screen.MultiPlayer }
37-
)
44+
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
45+
val useRail = maxWidth >= 600.dp
46+
47+
if (useRail) {
48+
RailLayout(currentScreen, onScreenChange = { currentScreen = it })
49+
} else {
50+
BarLayout(currentScreen, onScreenChange = { currentScreen = it })
51+
}
52+
}
53+
}
54+
}
55+
56+
// Compact: bottom NavigationBar
57+
@Composable
58+
private fun BarLayout(current: Screen, onScreenChange: (Screen) -> Unit) {
59+
Scaffold(
60+
bottomBar = {
61+
NavigationBar {
62+
Screen.entries.forEach { screen ->
3863
NavigationBarItem(
39-
icon = { Icon(Icons.Default.Subtitles, contentDescription = "Video Attachment") },
40-
label = { Text("Video Attachment") },
41-
selected = currentScreen == Screen.VideoAttachmentPlayer,
42-
onClick = { currentScreen = Screen.VideoAttachmentPlayer }
64+
icon = { Icon(screen.icon, contentDescription = screen.label) },
65+
label = { Text(screen.label) },
66+
selected = current == screen,
67+
onClick = { onScreenChange(screen) },
4368
)
4469
}
4570
}
46-
) { paddingValues ->
47-
Box(
48-
modifier = Modifier
49-
.fillMaxSize()
50-
.padding(paddingValues)
51-
.background(MaterialTheme.colorScheme.background)
52-
) {
53-
when (currentScreen) {
54-
Screen.SinglePlayer -> SinglePlayerScreen()
55-
Screen.MultiPlayer -> MultiPlayerScreen()
56-
Screen.VideoAttachmentPlayer -> VideoAttachmentPlayerScreen()
57-
}
71+
},
72+
) { padding ->
73+
ScreenContent(current, Modifier.fillMaxSize().padding(padding))
74+
}
75+
}
76+
77+
// Medium+: side NavigationRail
78+
@Composable
79+
private fun RailLayout(current: Screen, onScreenChange: (Screen) -> Unit) {
80+
Row(modifier = Modifier.fillMaxSize()) {
81+
NavigationRail {
82+
Spacer(Modifier.weight(1f))
83+
Screen.entries.forEach { screen ->
84+
NavigationRailItem(
85+
icon = { Icon(screen.icon, contentDescription = screen.label) },
86+
label = { Text(screen.label) },
87+
selected = current == screen,
88+
onClick = { onScreenChange(screen) },
89+
)
5890
}
91+
Spacer(Modifier.weight(1f))
5992
}
93+
ScreenContent(current, Modifier.weight(1f).fillMaxHeight())
94+
}
95+
}
96+
97+
@Composable
98+
private fun ScreenContent(screen: Screen, modifier: Modifier) {
99+
when (screen) {
100+
Screen.Player -> PlayerScreen(modifier)
101+
Screen.Gallery -> GalleryScreen(modifier)
102+
Screen.Feed -> FeedScreen(modifier)
60103
}
61104
}

sample/composeApp/src/commonMain/kotlin/sample/app/MultiPlayerScreen.kt

Lines changed: 0 additions & 182 deletions
This file was deleted.

0 commit comments

Comments
 (0)