Milan Jurkulak 3 лет назад
Родитель
Сommit
1c6e7d7b83
45 измененных файлов с 389 добавлено и 191 удалено
  1. 1 0
      app/build.gradle.kts
  2. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/activity/MainActivity.kt
  3. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/activity/ComposableActivity.kt
  4. 26 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ComposeExt.kt
  5. 16 22
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ModifierExt.kt
  6. 6 3
      app/src/main/kotlin/org/mjdev/tvapp/base/screen/Screen.kt
  7. 3 1
      app/src/main/kotlin/org/mjdev/tvapp/base/screen/ScreenWithPages.kt
  8. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/badge/Badge.kt
  9. 15 7
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/button/Button.kt
  10. 15 8
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/Card.kt
  11. 12 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/CardContent.kt
  12. 16 11
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/CarouselCard.kt
  13. 6 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/ItemCard.kt
  14. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PhotoCard.kt
  15. 19 12
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PreviewCard.kt
  16. 12 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/carousel/BigCarousel.kt
  17. 3 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableBox.kt
  18. 9 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableCard.kt
  19. 14 8
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/TouchBox.kt
  20. 13 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/PhotoImage.kt
  21. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavDrawerContent.kt
  22. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavHostEx.kt
  23. 2 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavigationRow.kt
  24. 2 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/SettingsDrawer.kt
  25. 6 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/Page.kt
  26. 0 27
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/PagerState.kt
  27. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/screen/EmptyScreen.kt
  28. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/text/TextAny.kt
  29. 9 5
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/BrowseView.kt
  30. 3 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/CategoryRow.kt
  31. 10 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Clock.kt
  32. 3 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/TVRow.kt
  33. 3 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Tabs.kt
  34. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Title.kt
  35. 9 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/UserPic.kt
  36. 82 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/theme/ThemeHelper.kt
  37. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/AboutPage.kt
  38. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/MainPage.kt
  39. 43 18
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/PluginsPage.kt
  40. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/SubscriptionPage.kt
  41. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/DetailScreen.kt
  42. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/MainScreen.kt
  43. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/PlayerScreen.kt
  44. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/SplashScreen.kt
  45. 12 17
      app/src/main/kotlin/org/mjdev/tvapp/ui/theme/Theme.kt

+ 1 - 0
app/build.gradle.kts

@@ -149,6 +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")
     kapt("com.google.dagger:dagger-compiler:2.46.1")
     // dagger android
     implementation("com.google.dagger:dagger-android:2.46.1")

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/activity/MainActivity.kt

@@ -13,7 +13,7 @@ import dagger.hilt.android.AndroidEntryPoint
 import org.mjdev.tvapp.base.activity.ComposableActivity
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.navigation.NavGraphBuilderEx
-import org.mjdev.tvapp.base.navigation.Screen.Companion.screen
+import org.mjdev.tvapp.base.screen.Screen.Companion.screen
 import org.mjdev.tvapp.ui.screens.DetailScreen
 import org.mjdev.tvapp.ui.screens.MainScreen
 import org.mjdev.tvapp.ui.screens.PlayerScreen

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

@@ -33,7 +33,7 @@ import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.ui.components.screen.EmptyScreen
 import org.mjdev.tvapp.base.navigation.NavGraphBuilderEx
 import org.mjdev.tvapp.base.ui.components.navigation.NavHostEx
-import org.mjdev.tvapp.base.navigation.Screen.Companion.screen
+import org.mjdev.tvapp.base.screen.Screen.Companion.screen
 import org.mjdev.tvapp.ui.theme.TVAppTheme
 import timber.log.Timber
 

+ 26 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ComposeExt.kt

@@ -10,8 +10,14 @@
 
 package org.mjdev.tvapp.base.extensions
 
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.State
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalInspectionMode
 import kotlinx.coroutines.flow.Flow
@@ -38,6 +44,26 @@ object ComposeExt {
         else -> text.toString()
     }
 
+    @Composable
+    fun rememberMutableInteractionSource() = remember {
+        MutableInteractionSource()
+    }
+
+    @Composable
+    fun rememberFocusState(
+        initial: FocusState? = null
+    ) = remember {
+        mutableStateOf(initial)
+    }
+
+    @Composable
+    fun rememberFocusRequester() = remember {
+        FocusRequester()
+    }
+
+    val MutableState<FocusState?>.isFocused
+        get() = (value?.isFocused == true) || (value?.hasFocus == true)
+
 //    @Composable
 //    fun Any?.toDrawable(): Drawable {
 //        val context = LocalContext.current

+ 16 - 22
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ModifierExt.kt

@@ -10,55 +10,49 @@ package org.mjdev.tvapp.base.extensions
 
 import android.annotation.SuppressLint
 import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.input.pointer.pointerInput
+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 timber.log.Timber
 
 object ModifierExt {
 
-    @Composable
-    fun rememberMutableInteractionSource() = remember { MutableInteractionSource() }
-
-    @Composable
-    fun rememberFocusState(
-        initial: FocusState? = null
-    ) = remember {
-        mutableStateOf(initial)
+    fun Modifier.conditional(
+        condition: Boolean,
+        other: Modifier.() -> Modifier
+    ): Modifier {
+        return when(condition) {
+            true -> this.then(other.invoke(this))
+            else -> this
+        }
     }
 
-    @Composable
-    fun rememberFocusRequester() = remember { FocusRequester() }
-
-    val MutableState<FocusState?>.isFocused
-        get() = (value?.isFocused == true) || (value?.hasFocus == true)
-
     @SuppressLint("ComposableModifierFactory")
     @Composable
     // todo focus problem, requester not registered
     fun Modifier.touchable(
         state: MutableState<FocusState?> = rememberFocusState(),
         focusRequester: FocusRequester = rememberFocusRequester(),
-        onFocus: () -> Unit = {},
-        onClick: () -> Unit = {},
+        onFocus: FocusRequester.() -> Unit = {},
+        onClick: FocusRequester.() -> Unit = {},
     ): Modifier = focusRequester(
         focusRequester
     ).pointerInput(Unit) {
         detectTapGestures {
             if (state.isFocused) {
-                onClick()
+                onClick(focusRequester)
             } else {
                 try {
                     focusRequester.requestFocus()
-                    onFocus()
+                    onFocus(focusRequester)
                 } catch (e: Exception) {
                     Timber.e(e)
                 }
@@ -66,7 +60,7 @@ object ModifierExt {
         }
     }.onFocusChanged { fstate ->
         state.value = fstate
-        onFocus()
+        onFocus(focusRequester)
     }.apply {
         if (state.isFocused) {
             try {

+ 6 - 3
app/src/main/kotlin/org/mjdev/tvapp/base/navigation/Screen.kt → app/src/main/kotlin/org/mjdev/tvapp/base/screen/Screen.kt

@@ -8,7 +8,7 @@
 
 @file:Suppress("MemberVisibilityCanBePrivate")
 
-package org.mjdev.tvapp.base.navigation
+package org.mjdev.tvapp.base.screen
 
 import androidx.annotation.CallSuper
 import androidx.annotation.MainThread
@@ -41,6 +41,9 @@ import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.AnimExt.FadeIn
 import org.mjdev.tvapp.base.extensions.AnimExt.FadeOut
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
+import org.mjdev.tvapp.base.navigation.MenuItem
+import org.mjdev.tvapp.base.navigation.NavGraphBuilderEx
+import org.mjdev.tvapp.base.navigation.NavHostControllerEx
 import kotlin.reflect.full.createInstance
 
 @Suppress("unused", "LeakingThis")
@@ -83,9 +86,9 @@ open class Screen {
             menuRoute = completeRoute
         ) else null
 
-    lateinit var navController: NavHostControllerEx
+    var navController: NavHostControllerEx? = null
     var backStackEntry: NavBackStackEntry? = null
-    lateinit var args: Map<String, Any?>
+    var args: Map<String, Any?> = emptyMap()
 
     @TvPreview
     @Composable

+ 3 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/navigation/ScreenWithPages.kt → app/src/main/kotlin/org/mjdev/tvapp/base/screen/ScreenWithPages.kt

@@ -6,10 +6,11 @@
  *  w: https://mjdev.org
  */
 
-package org.mjdev.tvapp.base.navigation
+package org.mjdev.tvapp.base.screen
 
 import androidx.compose.runtime.Composable
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.ui.components.page.TvPager
 import org.mjdev.tvapp.base.ui.components.page.PagerScope
 
@@ -22,6 +23,7 @@ open class ScreenWithPages : Screen() {
     @TvPreview
     @Composable
     override fun ComposeScreen() {
+        val navController = navController ?: rememberNavControllerEx()
         TvPager(
             navController = navController,
             startIndex = startPageIndex,

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

@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.MaterialTheme
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.text.CircleText
 

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

@@ -9,17 +9,20 @@
 package org.mjdev.tvapp.base.ui.components.button
 
 import android.annotation.SuppressLint
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
@@ -29,7 +32,7 @@ import androidx.tv.material3.ButtonGlow
 import androidx.tv.material3.ButtonScale
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Glow
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
@@ -39,7 +42,7 @@ import org.mjdev.tvapp.base.ui.components.text.TextAny
 @SuppressLint("ModifierParameter")
 @Composable
 fun Button(
-    text: Any? = null,
+    text: Any? = "test",
     onClick: () -> Unit = {},
     modifier: Modifier = Modifier,
     fontSize: TextUnit = 24.sp,
@@ -47,7 +50,10 @@ fun Button(
     color: Color = Color.White,
     containerColor: Color = Color.Transparent,
     contentPadding: PaddingValues = PaddingValues(0.dp),
-    shape: Shape = RectangleShape,
+    roundRectSize: Dp = 8.dp,
+    shape: Shape = RoundedCornerShape(roundRectSize),
+    borderSize: Dp = 2.dp,
+    borderColor: Color = Color.White,
     glow: ButtonGlow = ButtonDefaults.NO_GLOW,
     scale: ButtonScale = ButtonDefaults.NO_SCALE,
     focusState: MutableState<FocusState?> = rememberFocusState(),
@@ -57,9 +63,11 @@ fun Button(
 ) {
 
     Button(
-        modifier = modifier.touchable(
-            state = focusState,
-        ),
+        modifier = modifier
+            .touchable(
+                state = focusState,
+            )
+            .border(BorderStroke(borderSize, borderColor), shape),
         glow = glow,
         scale = scale,
         onClick = onClick,

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

@@ -8,11 +8,13 @@
 
 package org.mjdev.tvapp.base.ui.components.card
 
+import android.annotation.SuppressLint
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.border
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.widthIn
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -37,6 +39,7 @@ import androidx.tv.material3.Glow
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
+@SuppressLint("ModifierParameter")
 fun Card(
     modifier: Modifier = Modifier,
     scale: CardScale = CardScale.None,
@@ -50,7 +53,9 @@ fun Card(
     content: @Composable ColumnScope.() -> Unit = {}
 ) = androidx.tv.material3.Card(
     onClick = onClick,
-    modifier = modifier,
+    modifier = modifier
+        .widthIn(max = 320.dp)
+        .aspectRatio(16f / 9f),
     onLongClick = onLongClick,
     shape = shape,
     colors = colors,
@@ -148,13 +153,15 @@ fun Modifier.rainbowBorder(
     borderSize: Dp = 4.dp,
     borderRound: Dp = 8.dp,
     shape: Shape = RoundedCornerShape(borderRound)
-) = composed { border(
-    BorderStroke(
-        borderSize,
-        rainbowColorsBrush()
-    ),
-    shape
-) }
+) = composed {
+    border(
+        BorderStroke(
+            borderSize,
+            rainbowColorsBrush()
+        ),
+        shape
+    )
+}
 
 @Suppress("unused")
 fun Modifier.tvAspectRatio() = aspectRatio(16f / 9f)

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

@@ -13,15 +13,24 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.tv.material3.MaterialTheme
 import androidx.tv.material3.ProvideTextStyle
+import org.mjdev.tvapp.base.ui.components.text.TextAny
 
+@Preview
 @Composable
 fun CardContent(
     modifier: Modifier = Modifier,
-    title: @Composable () -> Unit,
-    subtitle: @Composable () -> Unit = {},
-    description: @Composable () -> Unit = {},
+    title: @Composable () -> Unit = {
+        TextAny("title")
+    },
+    subtitle: @Composable () -> Unit = {
+        TextAny("subtitle")
+    },
+    description: @Composable () -> Unit = {
+        TextAny("description")
+    },
     subtitleAlpha: Float = 0.6f,
     descriptionAlpha: Float = 0.8f,
 ) {

+ 16 - 11
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/CarouselCard.kt

@@ -9,6 +9,8 @@
 package org.mjdev.tvapp.base.ui.components.card
 
 import android.annotation.SuppressLint
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
@@ -22,9 +24,9 @@ import androidx.tv.material3.CardDefaults
 import androidx.tv.material3.CardScale
 import androidx.tv.material3.CompactCard
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import org.mjdev.tvapp.R
-import org.mjdev.tvapp.base.extensions.ModifierExt
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 import org.mjdev.tvapp.base.interfaces.ItemWithBackground
 import org.mjdev.tvapp.base.interfaces.ItemWithDescription
@@ -39,12 +41,11 @@ import org.mjdev.tvapp.base.ui.components.text.TextAny
 @SuppressLint("ModifierParameter")
 fun CarouselCard(
     item: Any? = null,
-    placeHolder: Int = R.drawable.placeholder,
     modifier: Modifier = Modifier,
     contentScale: ContentScale = ContentScale.Crop,
     scale: CardScale = CardScale.None,
-    placeholder: @Composable () -> Unit = {},
     focusState: MutableState<FocusState?> = rememberFocusState(),
+    placeholder: @Composable () -> Unit = {}, // todo
     imageRenderer: @Composable (modifier: Modifier) -> Unit = {
         ImageAny(
             modifier = modifier,
@@ -57,13 +58,17 @@ fun CarouselCard(
     onFocus: () -> Unit = {},
     onClick: () -> Unit = {}
 ) {
-
+    val isEdit = isEditMode()
     CompactCard(
-        modifier = modifier.touchable(
-            state = focusState,
-            onClick = onClick,
-            onFocus = onFocus,
-        ),
+        modifier = modifier
+            .touchable(
+                state = focusState,
+                onClick = { onClick() },
+                onFocus = { onFocus() },
+            )
+            .conditional(isEdit) {
+                aspectRatio(16f / 9f).defaultMinSize(260.dp)
+            },
         border = CardDefaults.colorFocusBorder(Color.Green),
         scale = scale,
         image = {

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

@@ -26,7 +26,7 @@ import androidx.tv.material3.CardGlow
 import androidx.tv.material3.CardScale
 import androidx.tv.material3.CardShape
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import org.mjdev.tvapp.base.extensions.ModifierExt
+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.complex.FocusableCard
@@ -55,7 +55,7 @@ fun ItemCard(
             placeholder = placeholder
         )
     },
-    focusState: MutableState<FocusState?> = ModifierExt.rememberFocusState(),
+    focusState: MutableState<FocusState?> = rememberFocusState(),
     onFocus: (item: Any?) -> Unit = {},
     onClick: (item: Any?) -> Unit = {},
 ) = FocusableCard(
@@ -66,6 +66,10 @@ fun ItemCard(
     focusState = focusState,
     contentScale = contentScale,
     scale = scale,
+    shape = shape,
+    colors = colors,
+    border = border,
+    glow = glow,
     imageRenderer = imageRenderer,
     placeholder = placeholder,
     onFocus = onFocus,

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

@@ -18,7 +18,7 @@ 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.ModifierExt.rememberFocusState
+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

+ 19 - 12
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PreviewCard.kt

@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
+import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.runtime.*
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Alignment
@@ -27,6 +28,7 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
@@ -55,13 +57,14 @@ import kotlinx.coroutines.withContext
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Suppress("unused")
 @UnstableApi
+@Preview
 @Composable
 fun PreviewCard(
     modifier: Modifier = Modifier,
-    cardWidth: Dp,
-    cardHeight: Dp,
-    videoUrl: String,
-    hasFocus: Boolean,
+    cardWidth: Dp = 200.dp,
+    cardHeight: Dp = 140.dp,
+    videoUrl: String = "",
+    hasFocus: Boolean = false,
     clipStartPosition: Long = 2000000,
     clipEndPosition: Long = 8000000,
     thumbnailFrame: Long = 5000000,
@@ -114,15 +117,18 @@ fun PreviewCard(
                     Lifecycle.Event.ON_PAUSE -> {
                         exoPlayer.playWhenReady = false
                     }
+
                     Lifecycle.Event.ON_RESUME -> {
                         exoPlayer.playWhenReady = true
                     }
+
                     Lifecycle.Event.ON_DESTROY -> {
                         exoPlayer.run {
                             stop()
                             release()
                         }
                     }
+
                     else -> {}
                 }
             }
@@ -169,14 +175,15 @@ fun PreviewCard(
     }
 }
 
+@Preview
 @Composable
 fun PreviewThumbnail(
-    thumbnail: String?,
-    thumbnailBitmap: Bitmap?,
-    thumbnailFrame: Long,
-    videoUrl: String,
-    isLoadingVideo: Boolean,
-    onBitmapLoaded: (bitmap: Bitmap?) -> Unit
+    thumbnail: String? = "",
+    thumbnailBitmap: Bitmap? = null,
+    thumbnailFrame: Long = 0,
+    videoUrl: String = "",
+    isLoadingVideo: Boolean = false,
+    onBitmapLoaded: (bitmap: Bitmap?) -> Unit = {}
 ) {
     if (thumbnail == null && thumbnailBitmap == null) {
         LaunchedEffect(videoUrl) {
@@ -200,7 +207,7 @@ fun PreviewThumbnail(
                 .border(2.dp, Color.White.copy(alpha = 0.4f)),
             contentAlignment = Alignment.Center
         ) {
-//            CircularProgressIndicator()
+            CircularProgressIndicator()
         }
     } else {
         Box(
@@ -226,7 +233,7 @@ fun PreviewThumbnail(
                 )
             }
             if (isLoadingVideo) {
-//                CircularProgressIndicator(strokeWidth = 4.dp)
+                CircularProgressIndicator(strokeWidth = 4.dp)
             }
         }
     }

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

@@ -12,9 +12,11 @@ import android.annotation.SuppressLint
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
@@ -22,6 +24,8 @@ 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.isFocused
 import org.mjdev.tvapp.base.ui.components.card.CarouselCard
 
 // todo swipe left and swipe right
@@ -36,6 +40,7 @@ fun BigCarousel(
     onItemSelected: (movie: Any?) -> Unit = {},
     onItemClicked: (movie: Any?) -> Unit = {},
 ) {
+    val focusState: MutableState<FocusState?> = ComposeExt.rememberFocusState()
     val itemIndex = remember { mutableIntStateOf(0) }
     val selectedItem: () -> Any? = {
         items[itemIndex.value]
@@ -53,7 +58,13 @@ fun BigCarousel(
                 .height(height),
             contentScale = ContentScale.Crop,
             scale = CardScale.None,
-            item = items[indexOfCarouselItem],
+            focusState = focusState,
+            onFocus = {
+                if (focusState.isFocused) {
+                    // todo check
+                    onItemSelected(selectedItem())
+                }
+            },
             onClick = {
                 onItemClicked(selectedItem())
             }

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

@@ -24,7 +24,7 @@ import androidx.tv.material3.Border
 import androidx.tv.material3.ClickableSurfaceDefaults
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Surface
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 
 @OptIn(ExperimentalTvMaterial3Api::class)
@@ -47,13 +47,12 @@ fun FocusableBox(
     focusState: MutableState<FocusState?> = rememberFocusState(),
     content: @Composable BoxScope.() -> Unit = {}
 ) {
-
     Surface(
         onClick = onClick,
         modifier = modifier.touchable(
             state = focusState,
-            onFocus = onFocus,
-            onClick = onClick,
+            onFocus = { onFocus() },
+            onClick = { onClick() },
         ),
         onLongClick = null,
         enabled = enabled,

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

@@ -9,6 +9,8 @@
 package org.mjdev.tvapp.base.ui.components.complex
 
 import android.annotation.SuppressLint
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
@@ -27,7 +29,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.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 import org.mjdev.tvapp.base.interfaces.ItemWithDescription
 import org.mjdev.tvapp.base.interfaces.ItemWithImage
@@ -65,6 +69,7 @@ fun FocusableCard(
     onFocus: (item: Any?) -> Unit = {},
     onClick: (item: Any?) -> Unit = {},
 ) {
+    val isEdit = ComposeExt.isEditMode()
     CompactCard(
         scale = scale,
         shape = shape,
@@ -72,6 +77,9 @@ fun FocusableCard(
         border = border,
         glow = glow,
         modifier = modifier
+            .conditional(isEdit) {
+                aspectRatio(16f / 9f).defaultMinSize(80.dp)
+            }
             .onFocusChanged { state ->
                 focusState.value = state
             }

+ 14 - 8
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/TouchBox.kt

@@ -12,6 +12,8 @@ import android.annotation.SuppressLint
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableFloatStateOf
@@ -20,7 +22,10 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
 import androidx.tv.foundation.lazy.list.TvLazyListState
+import org.mjdev.tvapp.base.extensions.ComposeExt
+import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 
 @SuppressLint("AutoboxingStateValueProperty")
 @Preview
@@ -32,23 +37,24 @@ fun TouchBox(
     state: TvLazyListState? = null,
     content: @Composable BoxScope.() -> Unit = {}
 ) {
-
+    val isEdit = ComposeExt.isEditMode()
     val scrollDelta = remember { mutableFloatStateOf(0f) }
-
     Box(
-        modifier = modifier.pointerInput(Unit) {
-            detectDragGestures { _, dragAmount ->
-                scrollDelta.value = dragAmount.y
+        modifier = modifier
+            .conditional(isEdit) {
+                aspectRatio(16f / 9f).defaultMinSize(80.dp)
             }
-        },
+            .pointerInput(Unit) {
+                detectDragGestures { _, dragAmount ->
+                    scrollDelta.value = dragAmount.y
+                }
+            },
         contentAlignment = contentAlignment,
         propagateMinConstraints = propagateMinConstraints,
     ) {
         content.invoke(this)
     }
-
     LaunchedEffect(scrollDelta.value) {
         state?.dispatchRawDelta(-scrollDelta.value)
     }
-
 }

+ 13 - 4
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/PhotoImage.kt

@@ -12,6 +12,8 @@ import android.annotation.SuppressLint
 import androidx.annotation.FloatRange
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -32,7 +34,9 @@ import coil.ImageLoader
 import com.skydoves.landscapist.ImageOptions
 import com.skydoves.landscapist.coil.CoilImage
 import org.mjdev.tvapp.R
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.DrawableExt.asPhoto
+import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 
 @SuppressLint("ModifierParameter")
 @Preview
@@ -55,6 +59,7 @@ fun PhotoImage(
     colorFilter: ColorFilter? = null,
     contentDescription: String? = null,
 ) {
+    val isEdit = isEditMode()
     val context = LocalContext.current
     CoilImage(
         imageLoader = {
@@ -63,10 +68,14 @@ fun PhotoImage(
                 .build()
         },
         imageModel = { src },
-        modifier = modifier.border(
-            BorderStroke(borderSize, borderColor),
-            shape
-        ),
+        modifier = modifier
+            .conditional(isEdit) {
+                aspectRatio(16f / 9f).defaultMinSize(80.dp)
+            }
+            .border(
+                BorderStroke(borderSize, borderColor),
+                shape
+            ),
         imageOptions = ImageOptions(
             contentScale = contentScale,
             alignment = alignment,

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

@@ -24,7 +24,7 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.navigation.MenuItem
 import org.mjdev.tvapp.base.navigation.NavHostControllerEx
-import org.mjdev.tvapp.base.navigation.Screen.Companion.open
+import org.mjdev.tvapp.base.screen.Screen.Companion.open
 
 @SuppressLint("AutoboxingStateValueProperty")
 @OptIn(ExperimentalTvMaterial3Api::class)

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

@@ -25,7 +25,7 @@ import org.mjdev.tvapp.base.extensions.NavExt.createGraph
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.navigation.NavGraphBuilderEx
 import org.mjdev.tvapp.base.navigation.NavHostControllerEx
-import org.mjdev.tvapp.base.navigation.Screen.Companion.screen
+import org.mjdev.tvapp.base.screen.Screen.Companion.screen
 import org.mjdev.tvapp.base.ui.components.screen.EmptyScreen
 import org.mjdev.tvapp.base.ui.components.screen.ScreenView
 

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

@@ -35,8 +35,8 @@ import androidx.tv.material3.DrawerValue
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.rememberDrawerState
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
-import org.mjdev.tvapp.base.extensions.ModifierExt.isFocused
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt.isFocused
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 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
@@ -74,9 +74,7 @@ fun NavigationRow(
     onFocus: (id: Int) -> Unit = {},
     onClick: (id: Int) -> Unit = {},
 ) {
-
     val isEdit = isEditMode()
-
     FocusableBox(
         modifier = modifier
             .padding(margin)

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

@@ -17,8 +17,10 @@ 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
 
 @OptIn(ExperimentalTvMaterial3Api::class)
+@TvPreview
 @Composable
 fun SettingsDrawer(
     modifier: Modifier = Modifier,

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

@@ -11,6 +11,7 @@ package org.mjdev.tvapp.base.ui.components.page
 import android.annotation.SuppressLint
 import androidx.annotation.CallSuper
 import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
@@ -25,6 +26,7 @@ import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 import org.mjdev.tvapp.base.ui.components.complex.TouchBox
 import org.mjdev.tvapp.base.navigation.MenuItem
 import org.mjdev.tvapp.base.navigation.NavHostControllerEx
@@ -51,9 +53,9 @@ open class Page {
             Modifier
                 .fillMaxSize()
                 .background(backgroundColor, background)
-//                .touchable {
-//                    freeFocus()
-//                }
+                .touchable {
+                    freeFocus()
+                }
         ) {
             TouchBox(
                 contentAlignment = Alignment.Center,
@@ -66,7 +68,7 @@ open class Page {
     @Composable
     open fun Content() {
         Column(
-            modifier = Modifier.fillMaxSize(),
+            modifier = Modifier.fillMaxSize().focusable(),
             verticalArrangement = Arrangement.Center,
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {

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

@@ -1,27 +0,0 @@
-//package org.mjdev.tvapp.base.page
-//
-//import android.annotation.SuppressLint
-//import androidx.compose.runtime.mutableIntStateOf
-//
-//@Suppress("MemberVisibilityCanBePrivate", "unused")
-//@SuppressLint("AutoboxingStateValueProperty")
-//class PagerState(
-//    val startIndex: Int = 0
-//) {
-//
-//    val history = mutableListOf<Int>()
-//    val index = mutableIntStateOf(startIndex)
-//    val currentPage get() = if (history.size > 0) history[history.size - 1] else startIndex
-//
-//
-//    fun goToPage(pageIndex: Int) {
-//        history.add(currentPage)
-//        index.value = pageIndex
-//    }
-//
-//    fun goBack() {
-//        history.removeAt(history.size-1)
-//        index.value = currentPage
-//    }
-//
-//}

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

@@ -10,7 +10,7 @@ package org.mjdev.tvapp.base.ui.components.screen
 
 import androidx.compose.runtime.Composable
 import org.mjdev.tvapp.base.annotations.TvPreview
-import org.mjdev.tvapp.base.navigation.Screen
+import org.mjdev.tvapp.base.screen.Screen
 
 internal class EmptyScreen : Screen() {
 

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

@@ -30,7 +30,7 @@ import org.mjdev.tvapp.base.extensions.ComposeExt.textFrom
 @Preview
 @Composable
 fun TextAny(
-    text: Any? = "",
+    text: Any? = "test",
     modifier: Modifier = Modifier,
     color: Color = Color.Unspecified,
     fontSize: TextUnit = TextUnit.Unspecified,

+ 9 - 5
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/BrowseView.kt

@@ -38,10 +38,14 @@ fun BrowseView(
     showHeader: Boolean = true,
     showNetworkState: Boolean = true,
     title: Any? = "test",
-    messages: List<Any?> = emptyList(),
-    categories: List<Any?> = emptyList(),
-    featuredItems: List<Any?> = emptyList(),
-    categoriesAndItemsMap: Map<*, List<*>> = emptyMap<Any?, List<Any?>>(),
+    messages: List<Any?> = listOf(Unit, Unit, Unit),
+    categories: List<Any?> = listOf(Unit, Unit, Unit),
+    featuredItems: List<Any?> = listOf(Unit, Unit, Unit),
+    categoriesAndItemsMap: Map<*, List<*>> = mapOf<Any?, List<Any?>>(
+        Unit to listOf(Unit, Unit, Unit, Unit),
+        Unit to listOf(Unit, Unit, Unit, Unit),
+        Unit to listOf(Unit, Unit, Unit, Unit),
+    ),
     networkState: State<NetworkStatus?> = mutableStateOf(NetworkStatus.Unknown),
     errorState: State<Throwable?> = mutableStateOf(null),
     backgroundColor: Color = Color.DarkGray,
@@ -81,7 +85,7 @@ fun BrowseView(
         if (isEdit || categories.isNotEmpty()) item {
             Tabs(
                 items = categories.map { category ->
-                    (category as ItemWithTitle).title
+                    (category as? ItemWithTitle)?.title
                 }
             )
         }

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

@@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.tv.foundation.lazy.list.TvLazyListState
 import androidx.tv.foundation.lazy.list.rememberTvLazyListState
@@ -25,10 +26,11 @@ import org.mjdev.tvapp.base.ui.components.text.TextAny
 
 @Suppress("UNUSED_PARAMETER")
 @OptIn(ExperimentalTvMaterial3Api::class)
+@Preview
 @Composable
 fun CategoryRow(
     title: Any? = "category 1",
-    items: List<Any?> = listOf(),
+    items: List<Any?> = listOf(Unit, Unit, Unit),
     rowState: TvLazyListState = rememberTvLazyListState(),
     onItemClick: (item: Any?) -> Unit = {},
 ) {

+ 10 - 3
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Clock.kt

@@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
@@ -33,9 +34,11 @@ import androidx.compose.ui.unit.sp
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.channelFlow
 import kotlinx.coroutines.launch
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ContextExt.dateAsString
 import org.mjdev.tvapp.base.extensions.ContextExt.timeAsString
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
@@ -58,7 +61,7 @@ fun Clock(
     focusState: MutableState<FocusState?> = rememberFocusState(),
     onClick: () -> Unit = {},
 ) {
-
+    val isEdit = isEditMode()
     val context = LocalContext.current
 
     val timeFlow = if (showTime) channelFlow {
@@ -92,7 +95,11 @@ fun Clock(
     }.collectAsState(initial = "1.1.1970") else null
 
     FocusableBox(
-        modifier = modifier.padding(contentPadding),
+        modifier = modifier
+            .padding(contentPadding)
+            .conditional(isEdit) {
+                background(Color.DarkGray, RectangleShape)
+            },
         focusState = focusState,
         shape = RoundedCornerShape(roundSize),
         onClick = onClick

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

@@ -21,7 +21,9 @@ import androidx.tv.foundation.lazy.list.TvLazyListScope
 import androidx.tv.foundation.lazy.list.TvLazyListState
 import androidx.tv.foundation.lazy.list.TvLazyRow
 import androidx.tv.foundation.lazy.list.rememberTvLazyListState
+import org.mjdev.tvapp.base.annotations.TvPreview
 
+@TvPreview
 @Composable
 fun TVRow(
     modifier: Modifier = Modifier,
@@ -33,7 +35,7 @@ fun TVRow(
     verticalAlignment: Alignment.Vertical = Alignment.Top,
     userScrollEnabled: Boolean = true,
     pivotOffsets: PivotOffsets = PivotOffsets(),
-    content: TvLazyListScope.() -> Unit
+    content: TvLazyListScope.() -> Unit = {}
 ) {
     TvLazyRow(
         modifier = modifier,

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

@@ -30,7 +30,7 @@ import androidx.tv.material3.TabDefaults
 import androidx.tv.material3.TabRow
 import androidx.tv.material3.TabRowDefaults
 import org.mjdev.tvapp.base.annotations.TvPreview
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberMutableInteractionSource
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberMutableInteractionSource
 import org.mjdev.tvapp.base.interfaces.ItemWithTitle
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.text.TextAny
@@ -39,9 +39,9 @@ import org.mjdev.tvapp.base.ui.components.text.TextAny
 @OptIn(ExperimentalTvMaterial3Api::class)
 @TvPreview
 @Composable
-fun <T> Tabs(
+fun Tabs(
     modifier: Modifier = Modifier,
-    items: List<T> = emptyList(),
+    items: List<Any?> = listOf(Unit, Unit, Unit),
     fontSize: TextUnit = 12.sp,
     itemsSpacing: Dp = 8.dp,
     activeContentColor: Color = Color.Green,

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

@@ -28,8 +28,8 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
-import org.mjdev.tvapp.base.extensions.ModifierExt.isFocused
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt.isFocused
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.image.CircleImage
 import org.mjdev.tvapp.base.ui.components.text.TextWithShadow

+ 9 - 2
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/UserPic.kt

@@ -8,6 +8,7 @@
 
 package org.mjdev.tvapp.base.ui.components.tv
 
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.runtime.Composable
@@ -22,7 +23,9 @@ import androidx.compose.ui.unit.dp
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.MaterialTheme
 import org.mjdev.tvapp.R
-import org.mjdev.tvapp.base.extensions.ModifierExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
+import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.image.CircleImage
 
@@ -46,8 +49,12 @@ fun UserPic(
     onClick = onClick
 ) {
 
+    val isEdit = isEditMode()
     CircleImage(
-        modifier = modifier,
+        modifier = modifier
+            .conditional(isEdit) {
+                size(64.dp)
+            },
         backGroundColor = backGroundColor,
         borderSize = borderSize,
         borderColor = borderColor,

+ 82 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/ui/theme/ThemeHelper.kt

@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.unit.dp
+import androidx.core.view.ViewCompat
+import androidx.tv.material3.ColorScheme
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.MaterialTheme
+import androidx.tv.material3.darkColorScheme
+import androidx.tv.material3.lightColorScheme
+
+@Suppress("DEPRECATION", "UNUSED_PARAMETER")
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun ThemeHelper(
+    useDarkTheme: Boolean = isSystemInDarkTheme(),
+    dynamicColor: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S,
+    content: @Composable () -> Unit,
+) {
+//    val context = LocalContext.current
+    val colorScheme: ColorScheme = when {
+//        dynamicColor -> {
+//            if (useDarkTheme)
+//                dynamicDarkColorScheme(context)
+//            else
+//                dynamicLightColorScheme(context)
+//        }
+
+        else -> {
+            if (useDarkTheme)
+                darkColorScheme()
+            else
+                lightColorScheme()
+        }
+    }
+
+    val view = LocalView.current
+    if (!view.isInEditMode) {
+        SideEffect {
+            (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
+            ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = useDarkTheme
+        }
+    }
+
+    MaterialTheme(
+        colorScheme = colorScheme,
+        content = content
+    )
+
+}
+
+val LocalCardShape = staticCompositionLocalOf<Shape> {
+    RoundedCornerShape(8.dp)
+}
+
+@Suppress("unused")
+object ThemeHelper {
+
+    val cardShape: Shape
+        @Composable
+        @ReadOnlyComposable
+        get() = LocalCardShape.current
+
+}

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/ui/pages/AboutPage.kt

@@ -76,7 +76,7 @@ class AboutPage : Page() {
                             title = {
                                 TextAny(
                                     color = Color.White,
-                                    text = R.string.app_name
+                                    text = title
                                 )
                             },
                             subtitle = {

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

@@ -25,7 +25,7 @@ import org.mjdev.tvapp.base.extensions.HiltExt.appViewModel
 import org.mjdev.tvapp.base.extensions.StringExt.asException
 import org.mjdev.tvapp.base.interfaces.ItemWithId
 import org.mjdev.tvapp.base.interfaces.ItemWithVideoUri
-import org.mjdev.tvapp.base.navigation.Screen.Companion.open
+import org.mjdev.tvapp.base.screen.Screen.Companion.open
 import org.mjdev.tvapp.base.ui.components.page.Page
 import org.mjdev.tvapp.base.ui.components.tv.BrowseView
 import org.mjdev.tvapp.ui.screens.DetailScreen

+ 43 - 18
app/src/main/kotlin/org/mjdev/tvapp/ui/pages/PluginsPage.kt

@@ -8,10 +8,11 @@
 
 package org.mjdev.tvapp.ui.pages
 
-import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.SettingsInputComponent
@@ -26,7 +27,9 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
 import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.ui.components.card.Card
+import org.mjdev.tvapp.base.ui.components.card.CardContent
 import org.mjdev.tvapp.base.ui.components.page.Page
+import org.mjdev.tvapp.base.ui.components.text.TextAny
 
 class PluginsPage : Page() {
 
@@ -37,26 +40,48 @@ class PluginsPage : Page() {
     @TvPreview
     @Composable
     override fun Content() {
-        Card(
+
+        Box(
             modifier = Modifier
                 .fillMaxSize()
-                .background(backgroundColor, background),
-            onClick = {},
-            onLongClick = {},
-            shape = CardDefaults.shape(),
-            colors = CardDefaults.colors(
-                containerColor = Color(0xff242424)
-            ),
+                .padding(16.dp)
         ) {
-            Row(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .padding(16.dp),
-                horizontalArrangement = Arrangement.Start,
-                verticalAlignment = Alignment.CenterVertically,
-                content = {
-                }
-            )
+            Card(
+                onClick = {},
+                onLongClick = {},
+                modifier = Modifier.fillMaxSize(),
+                shape = CardDefaults.shape(),
+                colors = CardDefaults.colors(
+                    containerColor = Color(0xff242424)
+                ),
+            ) {
+                Row(
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .padding(16.dp),
+                    horizontalArrangement = Arrangement.Start,
+                    verticalAlignment = Alignment.CenterVertically,
+                    content = {
+                        CardContent(
+                            modifier = Modifier.fillMaxWidth(),
+                            title = {
+                                TextAny(
+                                    color = Color.White,
+                                    text = title
+                                )
+                            },
+                            subtitle = {
+                                TextAny(
+                                    color = Color.White,
+                                    text = R.string.text_unimplemented_yet
+                                )
+                            },
+                            description = {
+                            }
+                        )
+                    }
+                )
+            }
         }
     }
 

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/ui/pages/SubscriptionPage.kt

@@ -66,7 +66,7 @@ class SubscriptionPage : Page() {
                             title = {
                                 TextAny(
                                     color = Color.White,
-                                    text = R.string.app_name
+                                    text = title
                                 )
                             },
                             subtitle = {

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/ui/screens/DetailScreen.kt

@@ -20,7 +20,7 @@ import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.HiltExt.appViewModel
 import org.mjdev.tvapp.base.extensions.StringExt.asException
-import org.mjdev.tvapp.base.navigation.Screen
+import org.mjdev.tvapp.base.screen.Screen
 import org.mjdev.tvapp.state.DetailsLoadingState
 import org.mjdev.tvapp.ui.components.Details
 import org.mjdev.tvapp.ui.components.Loading

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

@@ -14,7 +14,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.vector.ImageVector
 import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.annotations.TvPreview
-import org.mjdev.tvapp.base.navigation.ScreenWithPages
+import org.mjdev.tvapp.base.screen.ScreenWithPages
 import org.mjdev.tvapp.base.ui.components.page.PagerScope
 import org.mjdev.tvapp.base.ui.components.page.SearchPage
 import org.mjdev.tvapp.base.ui.components.page.SettingsPage

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/ui/screens/PlayerScreen.kt

@@ -23,7 +23,7 @@ import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.HiltExt.appViewModel
 import org.mjdev.tvapp.base.extensions.StringExt.asException
-import org.mjdev.tvapp.base.navigation.Screen
+import org.mjdev.tvapp.base.screen.Screen
 import org.mjdev.tvapp.state.DetailsLoadingState
 import org.mjdev.tvapp.ui.components.Loading
 import org.mjdev.tvapp.ui.components.MediaPlayer
@@ -62,7 +62,7 @@ class PlayerScreen : Screen() {
         if (isEdit || (movieState.value is DetailsLoadingState.Ready)) {
             MediaPlayer(
                 modifier = Modifier.fillMaxSize(),
-                movie = (movieState.value as DetailsLoadingState.Ready).movie
+                movie = (movieState.value as? DetailsLoadingState.Ready)?.movie
             )
         } else if (movieState.value is DetailsLoadingState.NotFound) {
             postError("Movie for id: $movieId not found.")

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

@@ -31,7 +31,7 @@ import kotlinx.coroutines.delay
 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.navigation.Screen
+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
@@ -99,7 +99,7 @@ class SplashScreen : Screen() {
                     })
             )
             delay(3000L)
-            navController.openAsTop<MainScreen>()
+            navController?.openAsTop<MainScreen>()
         }
 
     }

+ 12 - 17
app/src/main/kotlin/org/mjdev/tvapp/ui/theme/Theme.kt

@@ -8,29 +8,24 @@
 
 package org.mjdev.tvapp.ui.theme
 
+import android.os.Build
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.runtime.Composable
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.MaterialTheme
-import androidx.tv.material3.darkColorScheme
-import androidx.tv.material3.lightColorScheme
+import org.mjdev.tvapp.base.ui.theme.ThemeHelper
 
-@OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 fun TVAppTheme(
     useDarkTheme: Boolean = isSystemInDarkTheme(),
+    dynamicColor: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S,
     content: @Composable () -> Unit,
-) {
+) = ThemeHelper (useDarkTheme, dynamicColor, content)
 
-    val colorScheme = if (useDarkTheme) {
-        darkColorScheme()
-    } else {
-        lightColorScheme()
-    }
+// todo
 
-    MaterialTheme(
-        colorScheme = colorScheme,
-        content = content
-    )
-
-}
+//val backgroundColorDefault = Color.DarkGray
+//val backgroundColorMenu = Color(0xff202020)
+//
+//val textColorDefault = Color.White
+//
+//val roundCornerSize: Dp = 16.dp
+//val backgroundShape: Shape = RoundedCornerShape(roundCornerSize)