Pārlūkot izejas kodu

design improvements

Milan Jurkulak 3 gadi atpakaļ
vecāks
revīzija
0a44542eb9
29 mainītis faili ar 229 papildinājumiem un 169 dzēšanām
  1. 8 1
      README.md
  2. 27 29
      app/build.gradle.kts
  3. 5 1
      app/src/main/kotlin/org/mjdev/tvapp/base/activity/ComposableActivity.kt
  4. 11 1
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ComposeExt.kt
  5. 4 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/button/Button.kt
  6. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/Card.kt
  7. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/ItemCard.kt
  8. 3 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PhotoCard.kt
  9. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/carousel/BigCarousel.kt
  10. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/carousel/CarouselCard.kt
  11. 6 5
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableCard.kt
  12. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/IMediaPlayer.kt
  13. 1 10
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerContainer.kt
  14. 10 8
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerContainerDefaults.kt
  15. 27 31
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerImpl.kt
  16. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/NativePlayerImpl.kt
  17. 4 12
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/Navigation.kt
  18. 2 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavigationRow.kt
  19. 59 13
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/SettingsDrawer.kt
  20. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/Page.kt
  21. 5 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/PagerScope.kt
  22. 1 12
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Tabs.kt
  23. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Title.kt
  24. 5 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/drawable/PhotoDrawable.kt
  25. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/viewmodel/BaseViewModel.kt
  26. 18 6
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/MainPage.kt
  27. 4 0
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/MainScreen.kt
  28. 11 13
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/SplashScreen.kt
  29. 5 1
      app/src/main/res/values/themes.xml

+ 8 - 1
README.md

@@ -22,7 +22,14 @@
 
 ## Description
 
-Just an test app to skills showcase of coding skills, using
+Just an test app as showcase of coding skills, using modern frameworks  
+and a new unstable tv compose framework for tv apps with the diferrence  
+that components in this example should be touchable and therefore this  
+example can be used on any device include tv, tablet or any mobile phone.
+
+Components should be responsive to display size.
+
+Technologies that will be used:
 
 - Compose
 - Dagger/Hilt

+ 27 - 29
app/build.gradle.kts

@@ -34,7 +34,7 @@ plugins {
 
 android {
     namespace = "org.mjdev.tvapp"
-    compileSdk = 33
+    compileSdk = 34
 
     signingConfigs {
         loadKeyStoreProperties(
@@ -53,7 +53,7 @@ android {
         applicationId = "org.mjdev.tvapp"
 
         minSdk = 21
-        targetSdk = 33
+        targetSdk = 34
 
         versionCode = project.versionCode
         versionName = project.versionName
@@ -67,7 +67,6 @@ android {
             applicationIdSuffix = ".debug"
             isDebuggable = true
             isMinifyEnabled = false
-
             proguardFiles(
                 getDefaultProguardFile("proguard-android-optimize.txt"),
                 "proguard-rules.pro"
@@ -78,7 +77,6 @@ android {
             applicationIdSuffix = ""
             isDebuggable = true
             isMinifyEnabled = false
-
             proguardFiles(
                 getDefaultProguardFile("proguard-android-optimize.txt"),
                 "proguard-rules.pro"
@@ -122,11 +120,11 @@ android {
 dependencies {
 
     // base libs
-    implementation("androidx.core:core-ktx:1.11.0-beta02")
+    implementation("androidx.core:core-ktx:1.12.0-alpha05")
     // reflect / debug purposes only
     implementation(kotlin("reflect", "1.8.21"))
     // window manager
-    implementation("androidx.window:window:1.0.0")
+    implementation("androidx.window:window:1.1.0")
     // compose base libs
     implementation(
         platform("androidx.compose:compose-bom:2023.06.01")
@@ -134,14 +132,14 @@ dependencies {
     implementation("androidx.compose.ui:ui-tooling")
     implementation("androidx.activity:activity-compose:1.7.2")
     // more icons
-    implementation("androidx.compose.material:material-icons-extended")
+    implementation("androidx.compose.material:material-icons-extended:1.4.3")
     // tv compose
-    implementation("androidx.tv:tv-foundation:1.0.0-alpha07")
+    implementation("androidx.tv:tv-foundation:1.0.0-SNAPSHOT")
     implementation("androidx.tv:tv-material:1.0.0-alpha07")
     // view model
     implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
     // navigation
-    implementation("androidx.navigation:navigation-compose:2.7.0-beta01")
+    implementation("androidx.navigation:navigation-compose:2.7.0-beta02")
     // image loading
     implementation("io.coil-kt:coil-compose:2.4.0")
     implementation("com.github.skydoves:landscapist-coil:2.2.2")
@@ -151,7 +149,7 @@ dependencies {
     implementation("com.jakewharton.timber:timber:5.0.1")
     // dagger core
     implementation("com.google.dagger:dagger:2.46.1")
-    implementation("androidx.compose.material3:material3:1.1.0")
+    implementation("androidx.compose.material3:material3:1.1.1")
     kapt("com.google.dagger:dagger-compiler:2.46.1")
     // dagger android
     implementation("com.google.dagger:dagger-android:2.46.1")
@@ -162,25 +160,25 @@ dependencies {
     implementation("androidx.hilt:hilt-navigation-compose:1.1.0-alpha01")
     kapt("com.google.dagger:hilt-compiler:2.46.1")
     // exoplayer
-    implementation("androidx.media3:media3-exoplayer:1.0.2")
-    implementation("androidx.media3:media3-exoplayer-dash:1.0.2")
-    implementation("androidx.media3:media3-exoplayer-hls:1.0.2")
-    implementation("androidx.media3:media3-exoplayer-rtsp:1.0.2")
-    implementation("androidx.media3:media3-exoplayer-ima:1.0.2")
-    implementation("androidx.media3:media3-datasource-cronet:1.0.2")
-    implementation("androidx.media3:media3-datasource-okhttp:1.0.2")
-    implementation("androidx.media3:media3-datasource-rtmp:1.0.2")
-    implementation("androidx.media3:media3-ui:1.0.2")
-    implementation("androidx.media3:media3-ui-leanback:1.0.2")
-    implementation("androidx.media3:media3-session:1.0.2")
-    implementation("androidx.media3:media3-extractor:1.0.2")
-    implementation("androidx.media3:media3-cast:1.0.2")
-    implementation("androidx.media3:media3-exoplayer-workmanager:1.0.2")
-    implementation("androidx.media3:media3-transformer:1.0.2")
-    implementation("androidx.media3:media3-database:1.0.2")
-    implementation("androidx.media3:media3-decoder:1.0.2")
-    implementation("androidx.media3:media3-datasource:1.0.2")
-    implementation("androidx.media3:media3-common:1.0.2")
+    implementation("androidx.media3:media3-exoplayer:1.1.0")
+    implementation("androidx.media3:media3-exoplayer-dash:1.1.0")
+    implementation("androidx.media3:media3-exoplayer-hls:1.1.0")
+    implementation("androidx.media3:media3-exoplayer-rtsp:1.1.0")
+    implementation("androidx.media3:media3-exoplayer-ima:1.1.0")
+    implementation("androidx.media3:media3-datasource-cronet:1.1.0")
+    implementation("androidx.media3:media3-datasource-okhttp:1.1.0")
+    implementation("androidx.media3:media3-datasource-rtmp:1.1.0")
+    implementation("androidx.media3:media3-ui:1.1.0")
+    implementation("androidx.media3:media3-ui-leanback:1.1.0")
+    implementation("androidx.media3:media3-session:1.1.0")
+    implementation("androidx.media3:media3-extractor:1.1.0")
+    implementation("androidx.media3:media3-cast:1.1.0")
+    implementation("androidx.media3:media3-exoplayer-workmanager:1.1.0")
+    implementation("androidx.media3:media3-transformer:1.1.0")
+    implementation("androidx.media3:media3-database:1.1.0")
+    implementation("androidx.media3:media3-decoder:1.1.0")
+    implementation("androidx.media3:media3-datasource:1.1.0")
+    implementation("androidx.media3:media3-common:1.1.0")
 //    implementation ("com.rubensousa.dpadrecyclerview:dpadrecyclerview:1.1.0-alpha02")
 //    implementation ("com.rubensousa.dpadrecyclerview:dpadrecyclerview-compose:1.1.0-alpha02")
 

+ 5 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/activity/ComposableActivity.kt

@@ -15,6 +15,8 @@ import androidx.activity.compose.setContent
 import androidx.annotation.CallSuper
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.statusBarsPadding
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -62,7 +64,9 @@ open class ComposableActivity : ComponentActivity() {
             Surface(
                 modifier = Modifier
                     .background(backgroundColor, backgroundShape)
-                    .fillMaxSize(),
+                    .fillMaxSize()
+                    .navigationBarsPadding()
+                    .statusBarsPadding(),
                 shape = RectangleShape,
                 colors = NonInteractiveSurfaceDefaults.colors(
                     containerColor = backgroundColor

+ 11 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ComposeExt.kt

@@ -57,7 +57,17 @@ object ComposeExt {
     }
 
     @Composable
-    fun rememberFocusRequester() = remember {
+    fun rememberFocusState(
+        key: Any?,
+        initial: FocusState? = null
+    ) = remember(key) {
+        mutableStateOf(initial)
+    }
+
+    @Composable
+    fun rememberFocusRequester(
+        key: Any? = Unit
+    ) = remember(key) {
         FocusRequester()
     }
 

+ 4 - 3
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/button/Button.kt

@@ -35,10 +35,11 @@ import androidx.tv.material3.Glow
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
-@Preview
+
 @Suppress("UNUSED_PARAMETER")
-@OptIn(ExperimentalTvMaterial3Api::class)
 @SuppressLint("ModifierParameter")
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Preview
 @Composable
 fun Button(
     text: Any? = "test",
@@ -55,7 +56,7 @@ fun Button(
     borderColor: Color = Color.White,
     glow: ButtonGlow = ButtonDefaults.NO_GLOW,
     scale: ButtonScale = ButtonDefaults.NO_SCALE,
-    focusState: MutableState<FocusState?> = rememberFocusState(),
+    focusState: MutableState<FocusState?> = rememberFocusState(text),
     content: @Composable RowScope.() -> Unit = {
         TextAny(text = text)
     }

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/Card.kt

@@ -37,10 +37,10 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Glow
 import org.mjdev.tvapp.base.extensions.ModifierExt.tvAspectRatio
 
+@SuppressLint("ModifierParameter")
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
-@SuppressLint("ModifierParameter")
 fun Card(
     modifier: Modifier = Modifier,
     scale: CardScale = CardScale.None,

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/ItemCard.kt

@@ -27,10 +27,10 @@ import org.mjdev.tvapp.base.interfaces.ItemWithImage
 import org.mjdev.tvapp.base.ui.components.complex.FocusableCard
 import org.mjdev.tvapp.base.ui.components.image.ImageAny
 
+@SuppressLint("ModifierParameter")
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
-@SuppressLint("ModifierParameter")
 fun ItemCard(
     item: Any? = null,
     modifier: Modifier = Modifier,

+ 3 - 3
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PhotoCard.kt

@@ -18,20 +18,20 @@ import androidx.compose.ui.tooling.preview.Preview
 import androidx.tv.material3.CardDefaults
 import androidx.tv.material3.CardScale
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import org.mjdev.tvapp.base.extensions.ComposeExt
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.interfaces.ItemWithDescription
 import org.mjdev.tvapp.base.interfaces.ItemWithImage
 import org.mjdev.tvapp.base.ui.components.image.PhotoImage
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 @SuppressLint("ModifierParameter")
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
 fun PhotoCard(
     item: Any? = null,
     modifier: Modifier = Modifier,
     contentScale: ContentScale = ContentScale.Crop,
-    focusState: MutableState<FocusState?> = ComposeExt.rememberFocusState(),
+    focusState: MutableState<FocusState?> = rememberFocusState(item),
     imageRenderer: @Composable (modifier: Modifier) -> Unit = { m ->
         PhotoImage(
             modifier = m,

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/carousel/BigCarousel.kt

@@ -27,7 +27,7 @@ import androidx.compose.ui.unit.dp
 import androidx.tv.material3.CardScale
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import org.mjdev.tvapp.base.extensions.ComposeExt
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 
 // todo swipe left and swipe right
 @SuppressLint("AutoboxingStateValueProperty")
@@ -44,7 +44,7 @@ fun BigCarousel(
         if (config.orientation == Configuration.ORIENTATION_PORTRAIT) config.screenWidthDp * 0.4f
         else config.screenHeightDp * 0.4f
     }
-    val focusState: MutableState<FocusState?> = ComposeExt.rememberFocusState()
+    val focusState: MutableState<FocusState?> = rememberFocusState(items)
     val itemIndex = remember { mutableIntStateOf(0) }
     val selectedItem: () -> Any? = { items[itemIndex.value] }
     BoxWithConstraints {

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/carousel/CarouselCard.kt

@@ -27,16 +27,16 @@ import org.mjdev.tvapp.base.interfaces.ItemWithDescription
 import org.mjdev.tvapp.base.ui.components.card.PhotoCard
 import org.mjdev.tvapp.base.ui.components.image.ImageAny
 
+@SuppressLint("ModifierParameter")
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
-@SuppressLint("ModifierParameter")
 fun CarouselCard(
     item: Any? = null,
     modifier: Modifier = Modifier,
     contentScale: ContentScale = ContentScale.Crop,
     scale: CardScale = CardScale.None,
-    focusState: MutableState<FocusState?> = rememberFocusState(),
+    focusState: MutableState<FocusState?> = rememberFocusState(item),
     placeholder: @Composable () -> Unit = {}, // todo
     imageRenderer: @Composable (modifier: Modifier) -> Unit = {
         ImageAny(

+ 6 - 5
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableCard.kt

@@ -32,8 +32,9 @@ import androidx.tv.material3.CardScale
 import androidx.tv.material3.CardShape
 import androidx.tv.material3.CompactCard
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import org.mjdev.tvapp.base.extensions.ComposeExt
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ComposeExt.isFocused
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusRequester
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 import org.mjdev.tvapp.base.extensions.ModifierExt.focusState
@@ -47,8 +48,8 @@ import org.mjdev.tvapp.base.ui.components.card.colorFocusGlow
 import org.mjdev.tvapp.base.ui.components.image.ImageAny
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 @SuppressLint("ModifierParameter")
+@OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
 fun FocusableCard(
@@ -70,12 +71,12 @@ fun FocusableCard(
             placeholder = placeholder
         )
     },
-    focusRequester: FocusRequester = ComposeExt.rememberFocusRequester(),
-    focusState: MutableState<FocusState?> = rememberFocusState(),
+    focusRequester: FocusRequester = rememberFocusRequester(item),
+    focusState: MutableState<FocusState?> = rememberFocusState(item),
     onFocusChange: (state: FocusState) -> Unit = {},
     onClick: (item: Any?) -> Unit = {},
 ) {
-    val isEdit = ComposeExt.isEditMode()
+    val isEdit = isEditMode()
     val width = LocalConfiguration.current.let { config ->
         if (config.orientation == ORIENTATION_PORTRAIT) config.screenWidthDp * 0.5f
         else config.screenHeightDp * 0.5f

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/IMediaPlayer.kt

@@ -14,7 +14,7 @@ import androidx.compose.runtime.Composable
 interface IMediaPlayer {
 
     @Composable
-    fun PlayerView()
+    fun GetPlayerView()
 
     fun setMediaUri(uri: Uri)
 
@@ -37,7 +37,7 @@ interface IMediaPlayer {
         val EMPTY = object : IMediaPlayer {
 
             @Composable
-            override fun PlayerView() {
+            override fun GetPlayerView() {
             }
 
             override fun setMediaUri(uri: Uri) {}

+ 1 - 10
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerContainer.kt

@@ -30,20 +30,14 @@ fun MediaPlayerContainer(
     mediaPlayerOverlay: @Composable (state: MediaPlayerState) -> Unit = {},
     mediaPlayerControls: @Composable (state: MediaPlayerState) -> Unit = {}
 ) {
-
     val state = rememberMediaPlayerState(uri ?: Uri.EMPTY, autoPlay, startSeek)
-
     Box(modifier = modifier) {
-
         background(state)
-        mediaPlayer.PlayerView()
+        mediaPlayer.GetPlayerView()
         mediaPlayerOverlay(state)
         mediaPlayerControls(state)
-
     }
-
     DisposableEffect(state) {
-
         if (state.hasMediaToPlay) {
             mediaPlayer.setMediaUri(state.mediaUri.value)
             mediaPlayer.prepare()
@@ -54,12 +48,9 @@ fun MediaPlayerContainer(
                 mediaPlayer.play()
             }
         }
-
         onDispose {
             mediaPlayer.stop()
             mediaPlayer.dispose()
         }
-
     }
-
 }

+ 10 - 8
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerContainerDefaults.kt

@@ -18,6 +18,10 @@ import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 object MediaPlayerContainerDefaults {
 
+    val isEdit: Boolean
+        @Composable
+        get() = isEditMode()
+
     val engines: Array<IMediaPlayer?>
         @Composable
         get() = arrayOf(
@@ -29,20 +33,18 @@ object MediaPlayerContainerDefaults {
         @Composable
         get() = run {
             val context = LocalContext.current
-            val isEdit = isEditMode()
-            remember {
-                if (isEdit) IMediaPlayer.EMPTY
-                else ExoPlayerImpl(ExoPlayer.Builder(context).build())
+            if (isEdit) IMediaPlayer.EMPTY
+            else remember {
+                ExoPlayerImpl(ExoPlayer.Builder(context).build())
             }
         }
 
     val internalPlayer: IMediaPlayer
         @Composable
         get() = run {
-            val isEdit = isEditMode()
-            remember {
-                if (isEdit) IMediaPlayer.EMPTY
-                else NativePlayerImpl(MediaPlayer())
+            if (isEdit) IMediaPlayer.EMPTY
+            else remember {
+                NativePlayerImpl(MediaPlayer())
             }
         }
 

+ 27 - 31
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerImpl.kt

@@ -11,17 +11,19 @@ package org.mjdev.tvapp.base.ui.components.media
 import android.net.Uri
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.media3.common.MediaItem
 import androidx.media3.common.util.UnstableApi
 import androidx.media3.exoplayer.ExoPlayer
-import org.mjdev.tvapp.base.extensions.ComposeExt
+import androidx.media3.ui.PlayerView
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ContextExt.isATv
 
 class ExoPlayerImpl(
@@ -30,36 +32,29 @@ class ExoPlayerImpl(
 
     @UnstableApi
     @Composable
-    override fun PlayerView() {
-
-        val isEdit = ComposeExt.isEditMode()
-        val context = LocalContext.current
-        val isAtv = context.isATv
-
-        if (isEdit) {
-            Box(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .background(Color.Black, RectangleShape)
-            )
-
-        } else {
-
-            AndroidView(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .background(Color.Transparent, RectangleShape),
-                factory = {
-                    androidx.media3.ui.PlayerView(context).apply {
-                        player = exoPlayer
-                        controllerAutoShow = !isAtv
-                        controllerHideOnTouch = !isAtv
+    override fun GetPlayerView() {
+        val width = LocalConfiguration.current.screenWidthDp
+        val height = LocalConfiguration.current.screenHeightDp
+        val isEdit = isEditMode()
+        Box(
+            modifier = Modifier
+                .size(width.dp, height.dp)
+                .background(Color.Black, RectangleShape)
+        ) {
+            if (!isEdit) {
+                AndroidView(
+                    modifier = Modifier.size(width.dp, height.dp),
+                    factory = { context ->
+                        val isAtv = context.isATv
+                        PlayerView(context).apply {
+                            controllerAutoShow = !isAtv
+                            controllerHideOnTouch = !isAtv
+                            player = exoPlayer
+                        }
                     }
-                }
-            )
-
+                )
+            }
         }
-
     }
 
     override fun setMediaUri(uri: Uri) {
@@ -87,6 +82,7 @@ class ExoPlayerImpl(
     }
 
     override fun dispose() {
+        exoPlayer.release()
     }
 
     override fun seekTo(value: Long) {

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/NativePlayerImpl.kt

@@ -19,7 +19,7 @@ class NativePlayerImpl(
 ) : IMediaPlayer {
 
     @Composable
-    override fun PlayerView() {
+    override fun GetPlayerView() {
     }
 
     override fun setMediaUri(uri: Uri) {

+ 4 - 12
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/Navigation.kt

@@ -13,6 +13,7 @@ import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
@@ -44,20 +45,12 @@ fun Navigation(
     menuItems: List<MenuItem> = listOf(),
     isEdit: Boolean = isEditMode()
 ) {
-
-    if (isEdit) {
-        navController.openMenu()
-    }
-
+    if (isEdit) navController.openMenu()
     navController.addMenuItem(*menuItems.toTypedArray())
-
     if (navController.isMenuEnabled) {
         SettingsDrawer(
             drawerState = navController.settingsDrawerState,
-            modifier = modifier
-                .fillMaxHeight()
-                .background(backgroundColor, shape)
-                .border(borderSize, borderColor, shape)
+            modifier = Modifier.fillMaxHeight()
         ) {
             NavigationDrawer(
                 modifier = modifier
@@ -67,7 +60,7 @@ fun Navigation(
                 drawerState = navController.menuDrawerState,
                 content = {
                     Box(
-                        modifier.fillMaxHeight()
+                        modifier.fillMaxSize()
                     ) {
                         content()
                     }
@@ -88,5 +81,4 @@ fun Navigation(
             content()
         }
     }
-
 }

+ 2 - 4
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavigationRow.kt

@@ -42,8 +42,8 @@ import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.icon.IconAny
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
-@Preview
 @OptIn(ExperimentalTvMaterial3Api::class)
+@Preview
 @Composable
 fun NavigationRow(
     modifier: Modifier = Modifier,
@@ -71,7 +71,7 @@ fun NavigationRow(
 //        )
 //        .build(),
     drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
-    focusState: MutableState<FocusState?> = rememberFocusState(),
+    focusState: MutableState<FocusState?> = rememberFocusState(text),
     onFocus: (id: Int) -> Unit = {},
     onClick: (id: Int) -> Unit = {},
 ) {
@@ -128,9 +128,7 @@ fun NavigationRow(
                     color = textColor,
                     textAlign = TextAlign.Left
                 )
-
             }
         }
     }
-
 }

+ 59 - 13
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/SettingsDrawer.kt

@@ -11,13 +11,14 @@ package org.mjdev.tvapp.base.ui.components.navigation
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.tv.material3.DrawerState
 import androidx.tv.material3.DrawerValue
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.MaterialTheme
-import androidx.tv.material3.ModalNavigationDrawer
 import androidx.tv.material3.rememberDrawerState
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @TvPreview
@@ -25,15 +26,60 @@ import org.mjdev.tvapp.base.annotations.TvPreview
 fun SettingsDrawer(
     modifier: Modifier = Modifier,
     drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
-    scrimColor: Color = MaterialTheme.colorScheme.scrim.copy(alpha = 0.5f),
+    scrimColor: Color = Color.DarkGray.copy(alpha = 0.5f),
+    drawerWidth: Dp = 200.dp,
+    isEdit: Boolean = isEditMode(),
     content: @Composable () -> Unit = {}
-) = ModalNavigationDrawer(
-    modifier = modifier,
-    drawerState = drawerState,
-    scrimColor = scrimColor,
-    content = content,
-    drawerContent = {
-        // todo
-    },
-
-    )
+) {
+//    val focusRequester = rememberFocusRequester()
+//    if (isEdit) drawerState.setValue(DrawerValue.Open)
+    content()
+//    Box(
+//        modifier = Modifier
+//            .conditional(drawerState.currentValue == DrawerValue.Open) {
+//                navigationBarsPadding()
+//                    .statusBarsPadding()
+//                    .fillMaxSize()
+//            }
+//            .conditional(drawerState.currentValue == DrawerValue.Closed) {
+//                width(0.dp)
+//            }
+//            .focusable()
+//            .onFocusChanged { state ->
+//                if (state.isFocused || state.hasFocus) {
+//                    focusRequester.freeFocus()
+//                }
+//            }
+//            .requestFocusOnTouch(focusRequester)
+//    ) {
+//        Column(
+//            modifier = Modifier
+//                .fillMaxSize()
+//                .background(
+//                    Brush.linearGradient(
+//                        colors = listOf(Color.Transparent, scrimColor),
+//                    ), RectangleShape
+//                ),
+//            horizontalAlignment = Alignment.End,
+//            verticalArrangement = Arrangement.Center
+//        ) {
+//            Column(
+//                modifier = modifier
+//                    .width(
+//                        when (drawerState.currentValue) {
+//                            DrawerValue.Open -> drawerWidth
+//                            else -> 0.dp
+//                        }
+//                    )
+//                    .fillMaxHeight(),
+//                horizontalAlignment = Alignment.Start,
+//                verticalArrangement = Arrangement.Top
+//            ) {
+//                TextAny(
+//                    text = "settings",
+//                    color = Color.White
+//                )
+//            }
+//        }
+//    }
+}

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/Page.kt

@@ -51,7 +51,7 @@ open class Page {
     @Composable
     @CallSuper
     fun content() {
-        val focusRequester = rememberFocusRequester()
+        val focusRequester = rememberFocusRequester(title)
         Column(
             Modifier.fillMaxSize()
         ) {
@@ -82,7 +82,7 @@ open class Page {
         ) {
             TextAny(
                 text = title ?: "Empty Page",
-                fontSize = 64.sp,
+                fontSize = 32.sp,
                 fontWeight = FontWeight.Bold,
                 color = Color.White,
             )

+ 5 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/PagerScope.kt

@@ -16,6 +16,7 @@ import org.mjdev.tvapp.base.extensions.ListExt.addUnique
 import org.mjdev.tvapp.base.navigation.MenuItem
 import org.mjdev.tvapp.base.navigation.NavHostControllerEx
 
+@Suppress("unused")
 class PagerScope(
     val navController: NavHostControllerEx,
     pages: PagerScope.() -> Unit = {},
@@ -27,6 +28,10 @@ class PagerScope(
         pages.invoke(this)
     }
 
+    fun menuItem(vararg item: MenuItem) {
+        navController.addMenuItem(*item)
+    }
+
     fun page(page: Page, isStartPage: Boolean = false) {
         page.navController = navController
         addUnique(page)

+ 1 - 12
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Tabs.kt

@@ -52,9 +52,7 @@ fun Tabs(
     interactionSource: MutableInteractionSource = rememberMutableInteractionSource(),
     onItemClick: (item: Any?) -> Unit = {}
 ) {
-
     val selectedTabIndex = remember { mutableIntStateOf(0) }
-
     val colors = TabDefaults.pillIndicatorTabColors(
         activeContentColor = activeContentColor,
         contentColor = activeContentColor,
@@ -65,7 +63,6 @@ fun Tabs(
         disabledContentColor = activeContentColor.copy(alpha = 0.4f),
         disabledSelectedContentColor = selectedContentColor,
     )
-
     TabRow(
         modifier = modifier.fillMaxWidth(),
         selectedTabIndex = selectedTabIndex.value,
@@ -83,9 +80,7 @@ fun Tabs(
             }
         },
     ) {
-
         items.forEachIndexed { index, tab ->
-
             Tab(
                 modifier = Modifier,
                 colors = colors,
@@ -98,7 +93,7 @@ fun Tabs(
                 },
                 interactionSource = interactionSource
             ) {
-                val focusState = rememberFocusState()
+                val focusState = rememberFocusState(tab)
                 FocusableBox(
                     focusedColor = Color.Transparent,
                     onFocusChange = {
@@ -108,7 +103,6 @@ fun Tabs(
                     },
                     onClick = { onItemClick(items[selectedTabIndex.value]) }
                 ) {
-
                     TextAny(
                         modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
                         text = if (tab is ItemWithTitle) tab.title else tab.toString(),
@@ -116,13 +110,8 @@ fun Tabs(
                         color = Color.White,
                         fontWeight = FontWeight.Bold
                     )
-
                 }
-
             }
-
         }
-
     }
-
 }

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Title.kt

@@ -46,7 +46,7 @@ fun Title(
     onClick: () -> Unit = {},
 ) {
     val isEdit = isEditMode()
-    val focusState = rememberFocusState()
+    val focusState = rememberFocusState(title)
 
     FocusableBox(
         modifier = modifier,

+ 5 - 3
app/src/main/kotlin/org/mjdev/tvapp/base/ui/drawable/PhotoDrawable.kt

@@ -7,6 +7,7 @@ import android.graphics.drawable.Drawable
 import android.graphics.drawable.GradientDrawable
 import android.graphics.drawable.LayerDrawable
 
+@Suppress("unused", "DEPRECATION")
 class PhotoDrawable(
     d: Drawable?
 ) : LayerDrawable(arrayOf(d, getPDrawable(d), getLight(d))) {
@@ -32,6 +33,7 @@ class PhotoDrawable(
             return gd
         }
 
+        @Suppress("UNUSED_PARAMETER")
         private fun getLight(d: Drawable?): Drawable {
 //        ShapeDrawable sd = new ShapeDrawable(new OvalShape());
 //        sd.setIntrinsicHeight(d.getIntrinsicWidth());
@@ -54,9 +56,9 @@ class PhotoDrawable(
             return gd
         }
 
-        private val roundColor: Int = 0xd0000000.toInt()
-        private val lightColor: Int = 0x20ffffff
-        private val centerColor: Int = 0x00000000
+        private const val roundColor: Int = 0xd0000000.toInt()
+        private const val lightColor: Int = 0x20ffffff
+        private const val centerColor: Int = 0x00000000
 
     }
 

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/viewmodel/BaseViewModel.kt

@@ -15,7 +15,7 @@ import timber.log.Timber
 
 open class BaseViewModel : ViewModel() {
 
-    private val error: MutableStateFlow<Throwable?> = MutableStateFlow(null)
+    val error: MutableStateFlow<Throwable?> = MutableStateFlow(null)
 
     private var errorHandler: (error: Throwable) -> Unit = {}
 

+ 18 - 6
app/src/main/kotlin/org/mjdev/tvapp/ui/pages/MainPage.kt

@@ -45,12 +45,24 @@ class MainPage : Page() {
             MainViewModel.mockMainViewModel(context)
         }
 
-        val categoryList = remember { viewModel.categoryList }.collectAsState()
-        val featuredMovieList = remember { viewModel.featuredMovieList }.collectAsState()
-        val messages = remember { viewModel.messages }.collectAsState()
-        val networkState = remember { viewModel.networkInfo.networkStatus }.collectAsState(null)
-        val errorState = remember { mutableStateOf<Throwable?>(null) }
-        val titleState = remember { mutableStateOf<Any?>(R.string.app_name) }
+        val categoryList = remember(viewModel.categoryList) {
+            viewModel.categoryList
+        }.collectAsState()
+        val featuredMovieList = remember(viewModel.featuredMovieList) {
+            viewModel.featuredMovieList
+        }.collectAsState()
+        val messages = remember(viewModel.messages) {
+            viewModel.messages
+        }.collectAsState()
+        val networkState = remember(viewModel.networkInfo.networkStatus) {
+            viewModel.networkInfo.networkStatus
+        }.collectAsState(null)
+        val errorState = remember(viewModel.error) {
+            mutableStateOf(viewModel.error.value)
+        }
+        val titleState = remember {
+            mutableStateOf<Any?>(R.string.app_name)
+        }
 
         val onItemClick: (item: Any?) -> Unit = { item ->
             val isVideo = ((item as? ItemWithVideoUri)?.hasVideoUri == true)

+ 4 - 0
app/src/main/kotlin/org/mjdev/tvapp/ui/screens/MainScreen.kt

@@ -35,6 +35,10 @@ class MainScreen : ScreenWithPages() {
 
     override val pages: (PagerScope.() -> Unit) = {
 
+//        menuItem(MENU_ITEM_SETTINGS.action {
+//            navController.openSettings()
+//        })
+
         page(SearchPage())
 
         page(AboutPage())

+ 11 - 13
app/src/main/kotlin/org/mjdev/tvapp/ui/screens/SplashScreen.kt

@@ -11,19 +11,19 @@ package org.mjdev.tvapp.ui.screens
 import android.view.animation.OvershootInterpolator
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Tv
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.scale
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
@@ -32,7 +32,6 @@ import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.screen.Screen
-import org.mjdev.tvapp.base.ui.components.icon.IconAny
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 import org.mjdev.tvapp.base.ui.components.text.TextWithShadow
 
@@ -48,25 +47,24 @@ class SplashScreen : Screen() {
 
         val isEdit = isEditMode()
         val scale = remember { Animatable(if (isEdit) 1f else 0f) }
+        val brush = Brush.radialGradient(
+            colors = listOf(Color.Black, Color.Black, Color.DarkGray, Color.Black),
+        )
 
         Box(
             modifier = Modifier
                 .scale(scale.value)
+                .background(brush, RectangleShape)
                 .fillMaxSize(),
             contentAlignment = Alignment.Center
         ) {
-            IconAny(
-                modifier = Modifier.size(128.dp),
-                src = Icons.Default.Tv,
-                tint=Color.Black
-            )
             TextWithShadow(
                 text = R.string.app_name,
                 fontSize = 64.sp,
                 fontWeight = FontWeight.Bold,
-                color = Color.White,
+                color = Color.Gray,
                 modifier = Modifier.padding(10.dp),
-                shadowColor = Color.Green,
+                shadowColor = Color.DarkGray,
                 shadowSize = 18.dp
             )
         }
@@ -91,9 +89,9 @@ class SplashScreen : Screen() {
 
         LaunchedEffect(key1 = true) {
             scale.animateTo(
-                targetValue = 0.7f,
+                targetValue = 1f,
                 animationSpec = tween(
-                    durationMillis = 800,
+                    durationMillis = 1000,
                     easing = {
                         OvershootInterpolator(4f).getInterpolation(it)
                     })

+ 5 - 1
app/src/main/res/values/themes.xml

@@ -10,9 +10,13 @@
 
 <resources>
 
-    <style name="Theme.TvApp" parent="">
+    <style name="Theme.TvApp" parent="android:Theme.Material.Light.NoActionBar.TranslucentDecor">
 
         <item name="android:windowNoTitle">true</item>
+        <item name="android:windowBackground">#FF444444</item>
+        <item name="android:navigationBarColor">#FF444444</item>
+        <item name="android:colorBackground">#FF444444</item>
+        <item name="android:windowTranslucentNavigation">true</item>
 
     </style>