Browse Source

Merge branch 'page1' into main

Milan Jurkulak 3 years ago
parent
commit
f196cf46fb
42 changed files with 531 additions and 185 deletions
  1. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/api/MovieAPI.kt
  2. 10 4
      app/src/main/kotlin/org/mjdev/tvapp/base/activity/ComposableActivity.kt
  3. 4 1
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/NavExt.kt
  4. 4 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/StringExt.kt
  5. 13 0
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithBackground.kt
  6. 13 0
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithDescription.kt
  7. 15 0
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithId.kt
  8. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithImage.kt
  9. 13 0
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithMessage.kt
  10. 13 0
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithSubtitle.kt
  11. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithTitle.kt
  12. 6 1
      app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithVideoUri.kt
  13. 30 5
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/button/Button.kt
  14. 87 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/Card.kt
  15. 10 6
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/CarouselCard.kt
  16. 7 8
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/ItemCard.kt
  17. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/BigCarousel.kt
  18. 17 20
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableCard.kt
  19. 0 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/ScreenView.kt
  20. 0 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/ScrollableTvLazyRow.kt
  21. 1 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/TouchBox.kt
  22. 3 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/Navigation.kt
  23. 0 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/Screen.kt
  24. 5 10
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/ScreenWithPages.kt
  25. 6 7
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/Page.kt
  26. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/TvPager.kt
  27. 69 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/VerticalTvPager.kt
  28. 10 3
      app/src/main/kotlin/org/mjdev/tvapp/data/Category.kt
  29. 11 4
      app/src/main/kotlin/org/mjdev/tvapp/data/Message.kt
  30. 29 19
      app/src/main/kotlin/org/mjdev/tvapp/data/Movie.kt
  31. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/mock/MovieList.kt
  32. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/repository/MovieRepository.kt
  33. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/components/Details.kt
  34. 8 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/components/Loading.kt
  35. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/components/MediaPlayer.kt
  36. 5 6
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/AboutPage.kt
  37. 18 15
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/MainPage.kt
  38. 63 0
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/PluginsPage.kt
  39. 4 11
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/SubscriptionPage.kt
  40. 2 0
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/MainScreen.kt
  41. 42 47
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/SplashScreen.kt
  42. 1 0
      app/src/main/res/values/strings.xml

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/api/MovieAPI.kt

@@ -20,7 +20,7 @@ class MovieAPI {
     suspend fun loadCategoryList(): Result<List<Category>> {
         return Result.Success(MovieList.categories.map { name ->
             Category(
-                name = name,
+                title  = name,
                 movieList = getMovieListByCategory(name).unwrapOr(emptyList())
             )
         })
@@ -31,7 +31,7 @@ class MovieAPI {
     }
 
     suspend fun getMovieListByCategory(
-        category: String
+        category: String?
     ): Result<List<Movie>> {
         return Result.Success(MovieList.getByCategory(category))
     }

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

@@ -31,6 +31,7 @@ import org.mjdev.tvapp.activity.ActivityResultHandler
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ContextExt.isATv
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
+import org.mjdev.tvapp.base.ui.components.complex.ScreenView
 import org.mjdev.tvapp.base.ui.components.navigation.EmptyScreen
 import org.mjdev.tvapp.base.ui.components.navigation.NavGraphBuilderEx
 import org.mjdev.tvapp.base.ui.components.navigation.NavHostEx
@@ -69,11 +70,16 @@ open class ComposableActivity : ComponentActivity() {
                     containerColor = backgroundColor
                 )
             ) {
-                NavHostEx(
-                    modifier = Modifier.fillMaxSize(),
+                ScreenView(
                     navController = navController,
-                    builder = navGraphBuilder
-                )
+                    backgroundColor = backgroundColor
+                ) {
+                    NavHostEx(
+                        modifier = Modifier.fillMaxSize(),
+                        navController = navController,
+                        builder = navGraphBuilder
+                    )
+                }
             }
         }
 

+ 4 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/NavExt.kt

@@ -46,7 +46,10 @@ object NavExt {
         vararg navigators: Navigator<out NavDestination>
     ): NavHostControllerEx {
         val context = LocalContext.current
-        return rememberSaveable(inputs = navigators, saver = NavControllerSaver(context)) {
+        return rememberSaveable(
+            inputs = navigators,
+            saver = NavControllerSaver(context)
+        ) {
             createNavController(context)
         }.apply {
             for (navigator in navigators) {

+ 4 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/StringExt.kt

@@ -8,8 +8,12 @@
 
 package org.mjdev.tvapp.base.extensions
 
+import android.net.Uri
+
 object StringExt {
 
     fun String.asException() = Exception(this)
 
+    fun String.parseUri() = Uri.parse(this)
+
 }

+ 13 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithBackground.kt

@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.interfaces
+
+interface ItemWithBackground {
+    var backgroundImageUrl: Any?
+}

+ 13 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithDescription.kt

@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.interfaces
+
+interface ItemWithDescription {
+    var description: Any?
+}

+ 15 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithId.kt

@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.interfaces
+
+interface ItemWithId {
+    var id: Long
+
+    val hasId: Boolean get() = (id != -1L)
+}

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithImage.kt

@@ -9,5 +9,5 @@
 package org.mjdev.tvapp.base.interfaces
 
 interface ItemWithImage {
-    val backgroundImageUrl : String
+    var imageUrl : Any?
 }

+ 13 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithMessage.kt

@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.interfaces
+
+interface ItemWithMessage {
+    var message: Any?
+}

+ 13 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithSubtitle.kt

@@ -0,0 +1,13 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.interfaces
+
+interface ItemWithSubtitle {
+    var subtitle: Any?
+}

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithTitle.kt

@@ -9,5 +9,5 @@
 package org.mjdev.tvapp.base.interfaces
 
 interface ItemWithTitle {
-    val title : String
+    var title : Any?
 }

+ 6 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/interfaces/ItemWithVideoUri.kt

@@ -12,6 +12,11 @@ import android.net.Uri
 
 interface ItemWithVideoUri {
 
-    val videoUri : Uri
+    var videoUri: Any?
+
+    val hasVideoUri
+        get() = (videoUri != null) &&
+            (videoUri != Uri.EMPTY) &&
+            (videoUri.toString().isNotEmpty())
 
 }

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

@@ -8,6 +8,7 @@
 
 package org.mjdev.tvapp.base.ui.components.button
 
+import android.annotation.SuppressLint
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.runtime.Composable
@@ -25,13 +26,18 @@ import androidx.tv.material3.ButtonDefaults
 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.touchable
+import org.mjdev.tvapp.base.ui.components.text.TextAny
 
 @Preview
 @Suppress("UNUSED_PARAMETER")
 @OptIn(ExperimentalTvMaterial3Api::class)
+@SuppressLint("ModifierParameter")
 @Composable
 fun Button(
+    text: Any? = null,
+    onClick: () -> Unit = {},
     modifier: Modifier = Modifier,
     fontSize: TextUnit = 24.sp,
     fontWeight: FontWeight = FontWeight.Bold,
@@ -39,10 +45,11 @@ fun Button(
     containerColor: Color = Color.Transparent,
     contentPadding: PaddingValues = PaddingValues(0.dp),
     shape: Shape = RectangleShape,
-    glow: ButtonGlow = ButtonDefaults.glow(),
-    scale: ButtonScale = ButtonDefaults.scale(),
-    onClick: () -> Unit = {},
-    content: @Composable RowScope.() -> Unit = {}
+    glow: ButtonGlow = ButtonDefaults.NO_GLOW,
+    scale: ButtonScale = ButtonDefaults.NO_SCALE,
+    content: @Composable RowScope.() -> Unit = {
+        TextAny(text = text)
+    }
 ) {
 
     Button(
@@ -56,4 +63,22 @@ fun Button(
         content = content
     )
 
-}
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+val ButtonDefaults.NO_GLOW
+    get() = glow(
+        glow = Glow.None,
+        focusedGlow = Glow.None,
+        pressedGlow = Glow.None
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+val ButtonDefaults.NO_SCALE
+    get() = scale(
+        scale = 1f,
+        focusedScale = 1f,
+        pressedScale = 1f,
+        disabledScale = 1f,
+        focusedDisabledScale = 1f
+    )

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

@@ -0,0 +1,87 @@
+/*
+ * 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.components.card
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.Border
+import androidx.tv.material3.CardBorder
+import androidx.tv.material3.CardColors
+import androidx.tv.material3.CardDefaults
+import androidx.tv.material3.CardGlow
+import androidx.tv.material3.CardScale
+import androidx.tv.material3.CardShape
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.Glow
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Preview
+@Composable
+fun Card(
+    modifier: Modifier = Modifier,
+    scale: CardScale = CardScale.None,
+    onClick: () -> Unit = {},
+    onLongClick: (() -> Unit)? = null,
+    shape: CardShape = CardDefaults.shape(),
+    colors: CardColors = CardDefaults.colors(),
+    border: CardBorder = CardDefaults.NO_BORDER,
+    glow: CardGlow = CardDefaults.NO_GLOW,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable ColumnScope.() -> Unit = {}
+) = androidx.tv.material3.Card(
+    onClick = onClick,
+    modifier = modifier,
+    onLongClick = onLongClick,
+    shape = shape,
+    colors = colors,
+    scale = scale,
+    border = border,
+    glow = glow,
+    interactionSource = interactionSource,
+    content = content
+)
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+val CardDefaults.NO_BORDER: CardBorder
+    @Composable
+    get() = border(
+        border = Border.None,
+        focusedBorder = Border.None,
+        pressedBorder = Border.None
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+val CardDefaults.NO_GLOW: CardGlow
+    @Composable
+    get() = glow(
+        glow = Glow.None,
+        focusedGlow = Glow.None,
+        pressedGlow = Glow.None
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun CardDefaults.colorFocusGlow(
+    color: Color = Color.Gray,
+    elevation: Dp = 10.dp
+): CardGlow = glow(
+    glow = Glow.None,
+    focusedGlow = Glow(
+        elevationColor = color,
+        elevation = elevation
+    ),
+    pressedGlow = Glow.None
+)

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

@@ -22,16 +22,19 @@ import androidx.tv.material3.CompactCard
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Glow
 import org.mjdev.tvapp.R
+import org.mjdev.tvapp.base.interfaces.ItemWithBackground
+import org.mjdev.tvapp.base.interfaces.ItemWithDescription
+import org.mjdev.tvapp.base.interfaces.ItemWithSubtitle
+import org.mjdev.tvapp.base.interfaces.ItemWithTitle
 import org.mjdev.tvapp.base.ui.components.image.ImageAny
 import org.mjdev.tvapp.base.ui.components.text.TextAny
-import org.mjdev.tvapp.data.Movie
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
 @SuppressLint("ModifierParameter")
 fun CarouselCard(
-    movie: Movie? = null,
+    item: Any? = null,
     placeHolder: Int = R.drawable.placeholder,
     modifier: Modifier = Modifier,
     contentScale: ContentScale = ContentScale.Crop,
@@ -45,8 +48,8 @@ fun CarouselCard(
         image = {
             ImageAny(
                 modifier = modifier,
-                src = movie?.cardImageUrl,
-                contentDescription = movie?.description,
+                src = (item as? ItemWithBackground)?.backgroundImageUrl,
+                contentDescription = (item as? ItemWithDescription)?.description?.toString(),
                 contentScale = contentScale,
                 placeholder = placeHolder,
             )
@@ -54,16 +57,17 @@ fun CarouselCard(
         title = {
             TextAny(
                 modifier = Modifier.padding(4.dp),
-                text = movie?.title
+                text = (item as? ItemWithTitle)?.title
             )
         },
         subtitle = {
             TextAny(
                 modifier = Modifier.padding(4.dp),
-                text = movie?.studio
+                text = (item as? ItemWithSubtitle)?.subtitle
             )
         },
         description = {
+          // todo
         },
         glow = CardDefaults.glow(
             glow = Glow.None,

+ 7 - 8
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/MovieCard.kt → app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/ItemCard.kt

@@ -16,30 +16,29 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import org.mjdev.tvapp.data.Movie
 import org.mjdev.tvapp.base.ui.components.complex.FocusableCard
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Preview
 @Composable
 @SuppressLint("ModifierParameter")
-fun MovieCard(
-    movie: Movie? = null,
+fun ItemCard(
+    item: Any? = null,
     modifier: Modifier = Modifier,
-    onFocus: (movie: Movie?) -> Unit = {},
-    onClick: (movie: Movie?) -> Unit = {},
+    onFocus: (item: Any?) -> Unit = {},
+    onClick: (item: Any?) -> Unit = {},
 ) {
 
     FocusableCard(
         modifier = modifier
             .widthIn(max = 320.dp)
             .aspectRatio(16f / 9f),
-        movie = movie,
+        item = item,
         onFocus = {
-            onFocus(movie)
+            onFocus(item)
         },
         onClick = {
-            onClick(movie)
+            onClick(item)
         }
     )
 

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

@@ -76,7 +76,7 @@ fun BigCarousel(
                 .height(height),
             contentScale = ContentScale.Crop,
             scale = CardScale.None,
-            movie = items[indexOfCarouselItem],
+            item = items[indexOfCarouselItem],
             onClick = {
                 onItemClicked(selectedMovie())
             }

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

@@ -29,19 +29,22 @@ import androidx.tv.material3.CardScale
 import androidx.tv.material3.CardShape
 import androidx.tv.material3.CompactCard
 import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.Glow
 import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
+import org.mjdev.tvapp.base.interfaces.ItemWithDescription
+import org.mjdev.tvapp.base.interfaces.ItemWithImage
+import org.mjdev.tvapp.base.interfaces.ItemWithSubtitle
+import org.mjdev.tvapp.base.interfaces.ItemWithTitle
+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
-import org.mjdev.tvapp.data.Movie
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @SuppressLint("ModifierParameter")
 @Preview
 @Composable
 fun FocusableCard(
-    movie: Movie? = null,
+    item: Any? = null,
     modifier: Modifier = Modifier,
     contentScale: ContentScale = ContentScale.Crop,
     placeHolder: Int = R.drawable.placeholder,
@@ -51,8 +54,8 @@ fun FocusableCard(
     border: CardBorder = CardDefaults.border(),
     glow: CardGlow = CardDefaults.glow(),
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    onFocus: (movie: Movie?) -> Unit = {},
-    onClick: (movie: Movie?) -> Unit = {},
+    onFocus: (item: Any?) -> Unit = {},
+    onClick: (item: Any?) -> Unit = {},
 ) {
     val focusState = remember { mutableStateOf<FocusState?>(null) }
     val isFocused: () -> Boolean = {
@@ -65,29 +68,22 @@ fun FocusableCard(
         shape = shape,
         colors = colors,
         border = border,
-        glow = CardDefaults.glow(
-            glow = Glow.None,
-            focusedGlow = Glow(
-                elevationColor = Color.Green,
-                elevation = 10.dp
-            ),
-            pressedGlow = Glow.None
-        ),
+        glow = CardDefaults.colorFocusGlow(Color.Green),
         modifier = modifier
             .onFocusChanged { state ->
                 focusState.value = state
             }
             .touchable {
                 if (isFocused())
-                    onClick(movie)
+                    onClick(item)
                 else
-                    onFocus(movie)
+                    onFocus(item)
             },
         image = {
             ImageAny(
                 modifier = modifier,
-                src = movie?.cardImageUrl,
-                contentDescription = movie?.description,
+                src = (item as? ItemWithImage)?.imageUrl,
+                contentDescription = (item as? ItemWithDescription)?.description?.toString(),
                 contentScale = contentScale,
                 placeholder = placeHolder
             )
@@ -95,19 +91,20 @@ fun FocusableCard(
         title = {
             TextAny(
                 modifier = Modifier.padding(4.dp),
-                text = movie?.title
+                text = (item as? ItemWithTitle)?.title
             )
         },
         subtitle = {
             TextAny(
                 modifier = Modifier.padding(4.dp),
-                text = movie?.studio
+                text = (item as? ItemWithSubtitle)?.subtitle
             )
         },
         description = {
+          // todo
         },
         onClick = {
-            onClick(movie)
+            onClick(item)
         },
     )
 

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

@@ -41,7 +41,6 @@ fun ScreenView(
             .background(backgroundColor, shape),
         contentAlignment = Alignment.BottomCenter,
     ) {
-
         if (navController.menuState.value) {
             Navigation(
                 navController = navController,
@@ -53,6 +52,5 @@ fun ScreenView(
         } else {
             content()
         }
-
     }
 }

+ 0 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/ScrollableTvLayRow.kt → app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/ScrollableTvLazyRow.kt


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

@@ -32,6 +32,7 @@ fun TouchBox(
     state: TvLazyListState? = null,
     content: @Composable BoxScope.() -> Unit = {}
 ) {
+
     val scrollDelta = remember { mutableFloatStateOf(0f) }
 
     Box(
@@ -43,9 +44,7 @@ fun TouchBox(
         contentAlignment = contentAlignment,
         propagateMinConstraints = propagateMinConstraints,
     ) {
-
         content.invoke(this)
-
     }
 
     LaunchedEffect(scrollDelta.value) {

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

@@ -37,7 +37,7 @@ fun Navigation(
     navigationState: NavigationState = rememberNavigationState(
         navController = navController,
         drawerState = rememberDrawerState(
-            initialValue = DrawerValue.Open
+            initialValue = DrawerValue.Closed
         )
     ),
     backgroundColor: Color = Color(0xff202020),
@@ -50,7 +50,9 @@ fun Navigation(
 ) {
 
     val isEdit = isEditMode()
+
     if (isEdit) navigationState.openDrawer()
+
     navController.addMenuItem(*menuItems.toTypedArray())
 
     if (navController.menuState.value) {

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

@@ -89,7 +89,6 @@ open class Screen : NavController.OnDestinationChangedListener {
     @Composable
     @CallSuper
     open fun Compose() {
-
         val navController = rememberNavControllerEx()
 
         Compose(

+ 5 - 10
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/ScreenWithPages.kt

@@ -11,9 +11,8 @@ package org.mjdev.tvapp.base.ui.components.navigation
 import androidx.compose.runtime.Composable
 import androidx.navigation.NavBackStackEntry
 import org.mjdev.tvapp.base.annotations.TvPreview
-import org.mjdev.tvapp.base.ui.components.page.Pager
+import org.mjdev.tvapp.base.ui.components.page.TvPager
 import org.mjdev.tvapp.base.ui.components.page.PagerScope
-import org.mjdev.tvapp.base.ui.components.complex.ScreenView
 
 open class ScreenWithPages : Screen() {
 
@@ -31,15 +30,11 @@ open class ScreenWithPages : Screen() {
         backStackEntry: NavBackStackEntry?,
         args: Map<String, Any?>
     ) {
-        ScreenView(
+        TvPager(
             navController = navController,
-        ) {
-            Pager(
-                navController = navController,
-                startIndex = startPageIndex,
-                pages = pages
-            )
-        }
+            startIndex = startPageIndex,
+            pages = pages
+        )
     }
 
 }

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

@@ -26,6 +26,7 @@ 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.ui.components.navigation.MenuItem
 import org.mjdev.tvapp.base.ui.components.navigation.NavHostControllerEx
 import org.mjdev.tvapp.base.ui.components.text.TextAny
@@ -47,7 +48,6 @@ open class Page {
     @Composable
     @CallSuper
     fun content() {
-
         Column(
             Modifier
                 .fillMaxSize()
@@ -56,16 +56,16 @@ open class Page {
                     freeFocus()
                 }
         ) {
-
-            Content()
-
+            TouchBox(
+                contentAlignment = Alignment.Center,
+            ) {
+                Content()
+            }
         }
-
     }
 
     @Composable
     open fun Content() {
-
         Column(
             modifier = Modifier.fillMaxSize(),
             verticalArrangement = Arrangement.Center,
@@ -78,7 +78,6 @@ open class Page {
                 color = Color.White,
             )
         }
-
     }
 
     override fun toString(): String = this::class.simpleName ?: "Page[]"

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/Pager.kt → app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/TvPager.kt

@@ -37,7 +37,7 @@ import org.mjdev.tvapp.base.ui.components.page.Page.Companion.EMPTY_PAGE
 @SuppressLint("AutoboxingStateValueProperty")
 @TvPreview
 @Composable
-fun Pager(
+fun TvPager(
     navController: NavHostControllerEx = rememberNavControllerEx(),
     startIndex: Int = 0,
     roundCornerSize: Dp = 0.dp,

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

@@ -0,0 +1,69 @@
+/*
+ * 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.components.page
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.snapping.SnapFlingBehavior
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.pager.PageSize
+import androidx.compose.foundation.pager.PagerDefaults
+import androidx.compose.foundation.pager.PagerScope
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.VerticalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import org.mjdev.tvapp.base.annotations.TvPreview
+
+@OptIn(ExperimentalFoundationApi::class)
+@TvPreview
+@Composable
+fun VerticalTvPager(
+    modifier: Modifier = Modifier,
+    initialPage: Int = 0,
+    initialPageOffsetFraction: Float = 0f,
+    pageCount: () -> Int = { 0 },
+    state: PagerState = rememberPagerState(
+        initialPage = initialPage,
+        initialPageOffsetFraction = initialPageOffsetFraction,
+        pageCount = pageCount
+    ),
+    beyondBoundsPageCount: Int = 0,
+    contentPadding: Dp = 0.dp,
+    pageSize: PageSize = PageSize.Fill,
+    pageSpacing: Dp = 0.dp,
+    horizontalAlignment: Alignment.Horizontal = Alignment.End,
+    flingBehavior: SnapFlingBehavior = PagerDefaults.flingBehavior(state = state),
+    userScrollEnabled: Boolean = false,
+    reverseLayout: Boolean = false,
+    key: ((index: Int) -> Any)? = null,
+    pageNestedScrollConnection: NestedScrollConnection = PagerDefaults.pageNestedScrollConnection(
+        Orientation.Vertical
+    ),
+    pageContent: @Composable PagerScope.(page: Int) -> Unit = {},
+) = VerticalPager(
+    state = state,
+    modifier = modifier,
+    contentPadding = PaddingValues(contentPadding),
+    pageSize = pageSize,
+    beyondBoundsPageCount = beyondBoundsPageCount,
+    pageSpacing = pageSpacing,
+    horizontalAlignment = horizontalAlignment,
+    flingBehavior = flingBehavior,
+    userScrollEnabled = userScrollEnabled,
+    reverseLayout = reverseLayout,
+    key = key,
+    pageNestedScrollConnection = pageNestedScrollConnection,
+    pageContent = pageContent
+)

+ 10 - 3
app/src/main/kotlin/org/mjdev/tvapp/data/Category.kt

@@ -9,9 +9,16 @@
 package org.mjdev.tvapp.data
 
 import androidx.compose.runtime.Stable
+import org.mjdev.tvapp.base.interfaces.ItemWithId
+import org.mjdev.tvapp.base.interfaces.ItemWithTitle
+import java.io.Serializable
 
 @Stable
 data class Category(
-    var name: String,
-    var movieList: List<Movie>
-)
+
+    override var id: Long = 0L,
+    override var title: Any? = "",
+
+    var movieList: List<Movie> = emptyList(),
+
+) : Serializable, ItemWithTitle, ItemWithId

+ 11 - 4
app/src/main/kotlin/org/mjdev/tvapp/data/Message.kt

@@ -8,10 +8,17 @@
 
 package org.mjdev.tvapp.data
 
+import org.mjdev.tvapp.base.interfaces.ItemWithId
+import org.mjdev.tvapp.base.interfaces.ItemWithMessage
+import org.mjdev.tvapp.base.interfaces.ItemWithTitle
+
 data class Message(
-    val id: Int = 0,
-    val title: String = "",
-    val message: String,
+
+    override var id: Long = 0,
+    override var title: Any? = "",
+    override var message: Any? = "",
+
     val read: Boolean = false,
     val deleted: Boolean = false
-)
+
+) : ItemWithTitle, ItemWithMessage, ItemWithId

+ 29 - 19
app/src/main/kotlin/org/mjdev/tvapp/data/Movie.kt

@@ -9,37 +9,47 @@
 package org.mjdev.tvapp.data
 
 import android.net.Uri
+import org.mjdev.tvapp.base.interfaces.ItemWithBackground
+import org.mjdev.tvapp.base.interfaces.ItemWithDescription
+import org.mjdev.tvapp.base.interfaces.ItemWithId
 import org.mjdev.tvapp.base.interfaces.ItemWithImage
+import org.mjdev.tvapp.base.interfaces.ItemWithSubtitle
 import org.mjdev.tvapp.base.interfaces.ItemWithTitle
 import org.mjdev.tvapp.base.interfaces.ItemWithVideoUri
 import java.io.Serializable
 
 data class Movie(
 
-    var id: Long = 0,
-    override var title: String = "",
-    var description: String = "",
-    override var backgroundImageUrl: String = "",
-    var cardImageUrl: String = "",
-    override var videoUri: Uri = Uri.EMPTY,
-    var studio: String = ""
+    var studio: Any? = "",
 
-) : Serializable, ItemWithTitle, ItemWithImage, ItemWithVideoUri {
+    override var id: Long = 0,
+    override var title: Any? = "",
+    override var description: Any? = "",
+    override var backgroundImageUrl: Any? = "",
+    override var imageUrl: Any? = "",
+    override var videoUri: Any? = Uri.EMPTY,
 
-    val hasVideoUri get() = videoUri != Uri.EMPTY
+    ) : Serializable,
+    ItemWithId,
+    ItemWithTitle,
+    ItemWithSubtitle,
+    ItemWithImage,
+    ItemWithVideoUri,
+    ItemWithBackground,
+    ItemWithDescription {
+
+    override var subtitle: Any?
+        get() = studio
+        set(value) { studio = value }
 
     override fun toString(): String {
         return "Movie{" +
-                "id=" + id +
-                ", title='" + title + '\'' +
-                ", videoUrl='" + videoUri.toString() + '\'' +
-                ", backgroundImageUrl='" + backgroundImageUrl + '\'' +
-                ", cardImageUrl='" + cardImageUrl + '\'' +
-                '}'
-    }
-
-    companion object {
-        internal const val serialVersionUID = 727566175075960653L
+            "id=" + id +
+            ", title='" + title + '\'' +
+            ", videoUrl='" + videoUri.toString() + '\'' +
+            ", backgroundImageUrl='" + backgroundImageUrl + '\'' +
+            ", cardImageUrl='" + imageUrl + '\'' +
+            '}'
     }
 
 }

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/mock/MovieList.kt

@@ -35,7 +35,7 @@ internal object MovieList {
         categories.associateWith { all.shuffled() }
     }
 
-    fun getByCategory(category: String): List<Movie> {
+    fun getByCategory(category: String?): List<Movie> {
         return categoryMoviesMap[category] ?: all
     }
 
@@ -96,7 +96,7 @@ internal object MovieList {
                 description = description,
                 studio = studio[it],
                 videoUri = Uri.parse(videoUrl[it]),
-                cardImageUrl = cardImageUrl[it],
+                imageUrl = cardImageUrl[it],
                 backgroundImageUrl = bgImageUrl[it],
             )
         }

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/repository/MovieRepository.kt

@@ -76,7 +76,7 @@ class MovieRepository @Inject constructor(
         if (force || !categoryMovieListMap.contains(category)) {
             updateCache(
                 category = category,
-                movieList = dataSource.getMovieListByCategory(category.name).unwrapOr(listOf())
+                movieList = dataSource.getMovieListByCategory(category.title?.toString()).unwrapOr(listOf())
             )
         }
     }

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/ui/components/Details.kt

@@ -42,7 +42,7 @@ fun Details(
     ) {
 
         ImageAny(
-            src = movie?.cardImageUrl,
+            src = movie?.imageUrl,
             contentDescription = null,
             contentScale = ContentScale.Crop,
             modifier = Modifier.fillMaxSize()

+ 8 - 1
app/src/main/kotlin/org/mjdev/tvapp/ui/components/Loading.kt

@@ -8,12 +8,15 @@
 
 package org.mjdev.tvapp.ui.components
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.unit.sp
 import androidx.tv.material3.MaterialTheme
 import androidx.tv.material3.Text
 import org.mjdev.tvapp.base.annotations.TvPreview
@@ -23,12 +26,16 @@ import org.mjdev.tvapp.base.annotations.TvPreview
 fun Loading(modifier: Modifier = Modifier) {
 
     Box(
-        modifier = modifier.fillMaxSize(),
+        modifier = modifier.fillMaxSize().background(
+            Color.DarkGray,
+            RectangleShape
+        ),
         contentAlignment = Alignment.Center
     ) {
         Text(
             text = "Loading...",
             color = Color.White,
+            fontSize=20.sp,
             style = MaterialTheme.typography.headlineMedium
         )
     }

+ 2 - 1
app/src/main/kotlin/org/mjdev/tvapp/ui/components/MediaPlayer.kt

@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.StringExt.parseUri
 import org.mjdev.tvapp.base.interfaces.ItemWithVideoUri
 import org.mjdev.tvapp.base.ui.components.media.MediaPlayerContainer
 
@@ -26,7 +27,7 @@ fun MediaPlayer(
 
     MediaPlayerContainer(
         modifier = modifier.fillMaxSize(),
-        uri = movie?.videoUri
+        uri = movie?.videoUri.toString().parseUri()
     )
 
 }

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

@@ -8,6 +8,7 @@
 
 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
@@ -23,11 +24,11 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.unit.dp
-import androidx.tv.material3.Card
 import androidx.tv.material3.CardDefaults
 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.page.Page
 import org.mjdev.tvapp.base.ui.components.card.CardContent
 import org.mjdev.tvapp.base.ui.components.image.CircleImage
@@ -48,9 +49,11 @@ class AboutPage : Page() {
                 .padding(16.dp)
         ) {
             Card(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(backgroundColor, background),
                 onClick = {},
                 onLongClick = {},
-                modifier = Modifier.fillMaxSize(),
                 shape = CardDefaults.shape(),
                 colors = CardDefaults.colors(
                     containerColor = Color(0xff242424)
@@ -63,13 +66,11 @@ class AboutPage : Page() {
                     horizontalArrangement = Arrangement.Start,
                     verticalAlignment = Alignment.CenterVertically,
                     content = {
-
                         CircleImage(
                             modifier = Modifier.size(128.dp),
                             contentPadding = 10.dp,
                             src = R.drawable.milanj
                         )
-
                         CardContent(
                             modifier = Modifier.fillMaxWidth(),
                             title = {
@@ -91,12 +92,10 @@ class AboutPage : Page() {
                                 )
                             }
                         )
-
                     }
                 )
             }
         }
-
     }
 
 }

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

@@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.padding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Home
 import androidx.compose.runtime.Composable
-
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -31,23 +30,24 @@ import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.tv.foundation.lazy.list.TvLazyRow
 import androidx.tv.foundation.lazy.list.items
-import androidx.tv.material3.Text
 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.extensions.HiltExt.appViewModel
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 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.ui.components.navigation.Screen.Companion.open
 import org.mjdev.tvapp.base.network.NetworkStatus
 import org.mjdev.tvapp.base.ui.components.page.Page
 import org.mjdev.tvapp.base.ui.components.complex.BigCarousel
 import org.mjdev.tvapp.base.ui.components.complex.ScrollableTvLazyRow
 import org.mjdev.tvapp.base.ui.components.complex.Tabs
-import org.mjdev.tvapp.data.Movie
-import org.mjdev.tvapp.base.ui.components.card.MovieCard
+import org.mjdev.tvapp.base.ui.components.card.ItemCard
 import org.mjdev.tvapp.base.ui.components.complex.ErrorMessage
 import org.mjdev.tvapp.base.ui.components.complex.Header
+import org.mjdev.tvapp.base.ui.components.text.TextAny
 import org.mjdev.tvapp.ui.screens.DetailScreen
 import org.mjdev.tvapp.ui.screens.PlayerScreen
 import org.mjdev.tvapp.viewmodel.MainViewModel
@@ -74,13 +74,16 @@ class MainPage : Page() {
         val errorState = remember { mutableStateOf<Throwable?>(null) }
         val titleState = remember { mutableStateOf<Any?>(R.string.app_name) }
 
-        val onItemClick: (movie: Movie?) -> Unit = { movie ->
-            if (movie == null) {
+        val onItemClick: (item: Any?) -> Unit = { item ->
+            val isVideo = ((item as? ItemWithVideoUri)?.hasVideoUri == true)
+            val isId = ((item as? ItemWithId)?.hasId == true)
+            val id = (item as? ItemWithId)?.id
+            if ((item == null) || (!isId)) {
                 viewModel.postError("No media found.".asException())
-            } else if (movie.hasVideoUri) {
-                navController?.open<PlayerScreen>(movie.id)
+            } else if (isVideo) {
+                navController?.open<PlayerScreen>(id)
             } else {
-                navController?.open<DetailScreen>(movie.id)
+                navController?.open<DetailScreen>(id)
             }
         }
 
@@ -91,7 +94,7 @@ class MainPage : Page() {
         ScrollableTvLazyRow(
             modifier = Modifier
                 .fillMaxSize()
-                .background(Color.DarkGray),
+                .background(backgroundColor, background),
             verticalArrangement = Arrangement.spacedBy(32.dp),
             contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
         ) {
@@ -116,7 +119,7 @@ class MainPage : Page() {
             }
             if (isEdit || categoryList.value.isNotEmpty()) item {
                 Tabs(
-                    items = categoryList.value.map { it.name }
+                    items = categoryList.value.map { category -> category.title }
                 )
             }
             if (isEdit || featuredMovieList.value.isNotEmpty()) item {
@@ -127,11 +130,11 @@ class MainPage : Page() {
                 )
             }
             items(categoryList.value) { category ->
-                Text(
+                TextAny(
                     modifier = Modifier
                         .fillMaxWidth()
                         .padding(0.dp, 16.dp),
-                    text = category.name,
+                    text = category.title,
                     color = Color.White,
                     fontWeight = FontWeight.Bold
                 )
@@ -146,8 +149,8 @@ class MainPage : Page() {
                     )
                 ) {
                     items(category.movieList) { item ->
-                        MovieCard(
-                            movie = item,
+                        ItemCard(
+                            item = item,
                             onClick = onItemClick
                         )
                     }

+ 63 - 0
app/src/main/kotlin/org/mjdev/tvapp/ui/pages/PluginsPage.kt

@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ * Contact:
+ * e: mimoccc@gmail.com
+ * e: mj@mjdev.org
+ * w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.ui.pages
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.SettingsInputComponent
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.CardDefaults
+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.page.Page
+
+class PluginsPage : Page() {
+
+    override val title: Int = R.string.title_plugins
+    override val icon: ImageVector = Icons.Default.SettingsInputComponent
+
+    @OptIn(ExperimentalTvMaterial3Api::class)
+    @TvPreview
+    @Composable
+    override fun Content() {
+        Card(
+            modifier = Modifier
+                .fillMaxSize()
+                .background(backgroundColor, background),
+            onClick = {},
+            onLongClick = {},
+            shape = CardDefaults.shape(),
+            colors = CardDefaults.colors(
+                containerColor = Color(0xff242424)
+            ),
+        ) {
+            Row(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .padding(16.dp),
+                horizontalArrangement = Arrangement.Start,
+                verticalAlignment = Alignment.CenterVertically,
+                content = {
+                }
+            )
+        }
+    }
+
+}

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

@@ -22,31 +22,29 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.unit.dp
-import androidx.tv.material3.Card
 import androidx.tv.material3.CardDefaults
 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.page.Page
 import org.mjdev.tvapp.base.ui.components.card.CardContent
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
 class SubscriptionPage : Page() {
 
-    override val title : Int = R.string.title_subscription
-    override val icon : ImageVector = Icons.Default.ShoppingCart
+    override val title: Int = R.string.title_subscription
+    override val icon: ImageVector = Icons.Default.ShoppingCart
 
     @OptIn(ExperimentalTvMaterial3Api::class)
     @TvPreview
     @Composable
-    override fun Content () {
-
+    override fun Content() {
         Box(
             modifier = Modifier
                 .fillMaxSize()
                 .padding(16.dp)
         ) {
-
             Card(
                 onClick = {},
                 onLongClick = {},
@@ -56,7 +54,6 @@ class SubscriptionPage : Page() {
                     containerColor = Color(0xff242424)
                 ),
             ) {
-
                 Row(
                     modifier = Modifier
                         .fillMaxSize()
@@ -64,7 +61,6 @@ class SubscriptionPage : Page() {
                     horizontalArrangement = Arrangement.Start,
                     verticalAlignment = Alignment.CenterVertically,
                     content = {
-
                         CardContent(
                             modifier = Modifier.fillMaxWidth(),
                             title = {
@@ -82,12 +78,9 @@ class SubscriptionPage : Page() {
                             description = {
                             }
                         )
-
                     }
                 )
-
             }
-
         }
     }
 

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

@@ -20,6 +20,7 @@ import org.mjdev.tvapp.base.ui.components.page.SearchPage
 import org.mjdev.tvapp.base.ui.components.page.SettingsPage
 import org.mjdev.tvapp.ui.pages.AboutPage
 import org.mjdev.tvapp.ui.pages.MainPage
+import org.mjdev.tvapp.ui.pages.PluginsPage
 import org.mjdev.tvapp.ui.pages.SubscriptionPage
 
 class MainScreen : ScreenWithPages() {
@@ -41,6 +42,7 @@ class MainScreen : ScreenWithPages() {
         page(MainPage())
         page(SubscriptionPage())
 
+        page(PluginsPage())
         page(SettingsPage())
 
     }

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

@@ -36,7 +36,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.ui.components.navigation.NavHostControllerEx
-import org.mjdev.tvapp.base.ui.components.complex.ScreenView
 import org.mjdev.tvapp.base.ui.components.navigation.Screen
 import org.mjdev.tvapp.base.ui.components.icon.IconAny
 import org.mjdev.tvapp.base.ui.components.text.TextAny
@@ -75,53 +74,49 @@ class SplashScreen : Screen() {
             navController.open<MainScreen>()
         }
 
-        ScreenView(
-            backgroundColor = backgroundColor
+        Box(
+            modifier = Modifier
+                .scale(scale.value)
+                .fillMaxSize(),
+            contentAlignment = Alignment.Center
         ) {
-            Box(
-                modifier = Modifier
-                    .scale(scale.value)
-                    .fillMaxSize(),
-                contentAlignment = Alignment.Center
-            ) {
-                IconAny(
-                    modifier = Modifier.size(128.dp),
-                    src = Icons.Sharp.Tv,
-                    tint = Color.White.copy(alpha = 0.5f)
-                )
-                TextAny(
-                    text = R.string.app_name,
-                    fontSize = 64.sp,
-                    fontWeight = FontWeight.Bold,
-                    color = Color.White,
-                    modifier = Modifier.padding(10.dp),
-                    style = MaterialTheme.typography.bodyMedium.copy(
-                        shadow = Shadow(
-                            color = Color.Gray,
-                            offset = Offset(5f, 5f),
-                            blurRadius = 10f
-                        )
-                    ),
-                )
-            }
-            Box(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .padding(
-                        0.dp,
-                        0.dp,
-                        0.dp,
-                        4.dp
-                    ),
-                contentAlignment = Alignment.BottomCenter
-            ) {
-                TextAny(
-                    text = R.string.author,
-                    fontSize = 14.sp,
-                    fontWeight = FontWeight.Bold,
-                    color = Color.White.copy(alpha = 0.5f)
-                )
-            }
+            IconAny(
+                modifier = Modifier.size(128.dp),
+                src = Icons.Sharp.Tv,
+                tint = Color.White.copy(alpha = 0.5f)
+            )
+            TextAny(
+                text = R.string.app_name,
+                fontSize = 64.sp,
+                fontWeight = FontWeight.Bold,
+                color = Color.White,
+                modifier = Modifier.padding(10.dp),
+                style = MaterialTheme.typography.bodyMedium.copy(
+                    shadow = Shadow(
+                        color = Color.Gray,
+                        offset = Offset(5f, 5f),
+                        blurRadius = 10f
+                    )
+                ),
+            )
+        }
+        Box(
+            modifier = Modifier
+                .fillMaxSize()
+                .padding(
+                    0.dp,
+                    0.dp,
+                    0.dp,
+                    4.dp
+                ),
+            contentAlignment = Alignment.BottomCenter
+        ) {
+            TextAny(
+                text = R.string.author,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Color.White.copy(alpha = 0.5f)
+            )
         }
 
     }

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -33,5 +33,6 @@
     <string name="title_subscription">Subscription</string>
 
     <string name="author">Created by Milan Jurkulak © 2023</string>
+    <string name="title_plugins">Plugins</string>
 
 </resources>