فهرست منبع

previews improvement/components

Milan Jurkulak 3 سال پیش
والد
کامیت
ac9ae8a165
41فایلهای تغییر یافته به همراه546 افزوده شده و 488 حذف شده
  1. 16 6
      app/src/main/kotlin/org/mjdev/tvapp/activity/MainActivity.kt
  2. 36 27
      app/src/main/kotlin/org/mjdev/tvapp/base/activity/ComposableActivity.kt
  3. 7 3
      app/src/main/kotlin/org/mjdev/tvapp/base/annotations/TvPreview.kt
  4. 5 9
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ComposeExt.kt
  5. 0 33
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ContextExt.kt
  6. 67 64
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/HiltExt.kt
  7. 10 30
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/NavExt.kt
  8. 71 0
      app/src/main/kotlin/org/mjdev/tvapp/base/helpers/MockedViewModels.kt
  9. 0 85
      app/src/main/kotlin/org/mjdev/tvapp/base/media/SimpleMediaPlayerView.kt
  10. 20 0
      app/src/main/kotlin/org/mjdev/tvapp/base/navigation/EmptyScreen.kt
  11. 7 8
      app/src/main/kotlin/org/mjdev/tvapp/base/navigation/NavGraphBuilderEx.kt
  12. 38 13
      app/src/main/kotlin/org/mjdev/tvapp/base/navigation/NavHostEx.kt
  13. 26 74
      app/src/main/kotlin/org/mjdev/tvapp/base/navigation/Screen.kt
  14. 50 0
      app/src/main/kotlin/org/mjdev/tvapp/base/navigation/ScreenWithPages.kt
  15. 59 10
      app/src/main/kotlin/org/mjdev/tvapp/base/page/Page.kt
  16. 9 12
      app/src/main/kotlin/org/mjdev/tvapp/base/page/PageState.kt
  17. 25 9
      app/src/main/kotlin/org/mjdev/tvapp/base/page/Pager.kt
  18. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/page/PagerScope.kt
  19. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/CarouselCard.kt
  20. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/MovieCard.kt
  21. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/BigCarousel.kt
  22. 4 6
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/ErrorMessage.kt
  23. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableBox.kt
  24. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableCard.kt
  25. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/Header.kt
  26. 7 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/ScreenView.kt
  27. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/Title.kt
  28. 7 5
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/TouchBox.kt
  29. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/icon/IconAny.kt
  30. 3 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/Navigation.kt
  31. 11 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavigationRow.kt
  32. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/text/TextAny.kt
  33. 2 0
      app/src/main/kotlin/org/mjdev/tvapp/ui/components/Loading.kt
  34. 11 12
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/MainPage.kt
  35. 4 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/DetailScreen.kt
  36. 9 41
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/MainScreen.kt
  37. 4 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/PlayerScreen.kt
  38. 4 4
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/SplashScreen.kt
  39. 4 1
      app/src/main/kotlin/org/mjdev/tvapp/viewmodel/DetailViewModel.kt
  40. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/viewmodel/MainViewModel.kt
  41. 8 6
      settings.gradle.kts

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

@@ -8,8 +8,11 @@
 
 package org.mjdev.tvapp.activity
 
+import androidx.compose.runtime.Composable
 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.ui.screens.DetailScreen
 import org.mjdev.tvapp.ui.screens.MainScreen
@@ -17,11 +20,18 @@ import org.mjdev.tvapp.ui.screens.PlayerScreen
 import org.mjdev.tvapp.ui.screens.SplashScreen
 
 @AndroidEntryPoint
-class MainActivity : ComposableActivity ({
+class MainActivity : ComposableActivity() {
 
-    screen(route = SplashScreen(), isStartScreen = true)
-    screen(route = MainScreen(), isHomeScreen = true)
-    screen(route = DetailScreen())
-    screen(route = PlayerScreen())
+    @TvPreview
+    @Composable
+    override fun Compose() = super.Compose()
 
-})
+    override val navGraphBuilder: NavGraphBuilderEx.() -> Unit = {
+
+        screen(route = SplashScreen(), isStartScreen = true)
+        screen(route = MainScreen(), isHomeScreen = true)
+        screen(route = DetailScreen())
+        screen(route = PlayerScreen())
+
+    }
+}

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

@@ -12,7 +12,9 @@ import android.content.Intent
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
+import androidx.annotation.CallSuper
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
@@ -23,9 +25,12 @@ import androidx.navigation.compose.rememberNavController
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.NonInteractiveSurfaceDefaults
 import androidx.tv.material3.Surface
+import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ContextExt.isATv
+import org.mjdev.tvapp.base.navigation.EmptyScreen
 import org.mjdev.tvapp.base.navigation.NavGraphBuilderEx
 import org.mjdev.tvapp.base.navigation.NavHostEx
+import org.mjdev.tvapp.base.navigation.Screen.Companion.screen
 import org.mjdev.tvapp.ui.theme.TVAppTheme
 import timber.log.Timber
 
@@ -38,45 +43,40 @@ import timber.log.Timber
  * @property navGraphBuilder
  */
 @Suppress("MemberVisibilityCanBePrivate", "unused")
-abstract class ComposableActivity(
-    val navGraphBuilder: NavGraphBuilderEx.() -> Unit
-) : ComponentActivity() {
+open class ComposableActivity : ComponentActivity() {
 
     /**
      * Custom activity result listeners
      */
     val activityResultListeners = mutableListOf<ActivityResultHandler<*>>()
 
-    /**
-     * {@inheritDoc}
-     */
+    open val navGraphBuilder: NavGraphBuilderEx.() -> Unit = {
+        screen(route = EmptyScreen())
+    }
+
+    @TvPreview
     @OptIn(ExperimentalTvMaterial3Api::class)
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
+    @Composable
+    @CallSuper
+    open fun Compose() {
 
-        WindowCompat.setDecorFitsSystemWindows(window, !isATv)
+        TVAppTheme {
 
-        setContent {
+            Surface(
+                modifier = Modifier.fillMaxSize(),
+                shape = RectangleShape,
+                colors = NonInteractiveSurfaceDefaults.colors(
+                    containerColor = Color.Black
+                )
+            ) {
 
-            TVAppTheme {
+                val navController = rememberNavController()
 
-                Surface(
+                NavHostEx(
                     modifier = Modifier.fillMaxSize(),
-                    shape = RectangleShape,
-                    colors = NonInteractiveSurfaceDefaults.colors(
-                        containerColor = Color.Black
-                    )
-                ) {
-
-                    val navController = rememberNavController()
-
-                    NavHostEx(
-                        modifier = Modifier.fillMaxSize(),
-                        navController = navController,
-                        builder = navGraphBuilder
-                    )
-
-                }
+                    navController = navController,
+                    builder = navGraphBuilder
+                )
 
             }
 
@@ -84,6 +84,15 @@ abstract class ComposableActivity(
 
     }
 
+    /**
+     * {@inheritDoc}
+     */
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        WindowCompat.setDecorFitsSystemWindows(window, !isATv)
+        setContent { Compose() }
+    }
+
     /**
      * On activity result handler.
      *

+ 7 - 3
app/src/main/kotlin/org/mjdev/tvapp/base/annotations/TvPreview.kt

@@ -8,13 +8,17 @@
 
 package org.mjdev.tvapp.base.annotations
 
-import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_TYPE_TELEVISION
 import androidx.compose.ui.tooling.preview.Devices
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.Wallpapers.GREEN_DOMINATED_EXAMPLE
 
 @Preview(
-    uiMode = UI_MODE_NIGHT_NO,
+    uiMode = UI_MODE_TYPE_TELEVISION,
     showBackground = true,
-    device= Devices.TV_720p
+    device = Devices.TV_720p,
+    showSystemUi = true,
+    backgroundColor = 0xFF444444,
+    wallpaper = GREEN_DOMINATED_EXAMPLE
 )
 annotation class TvPreview

+ 5 - 9
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ComposeExt.kt

@@ -11,8 +11,10 @@
 package org.mjdev.tvapp.base.extensions
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalInspectionMode
+import kotlinx.coroutines.flow.Flow
 import org.mjdev.tvapp.BuildConfig
 
 @Suppress("MemberVisibilityCanBePrivate")
@@ -24,15 +26,9 @@ object ComposeExt {
     @Composable
     fun isEditMode() = LocalInspectionMode.current
 
-    /**
-     * Helper function to return text from any object is given as input.
-     * Mainly: Int -> is represented as resource text id String -> String null
-     * -> empty string other -> toString() call result
-     *
-     * @param text Text
-     * @param T T type of result
-     * @return String generated from any resource given
-     */
+    @Composable
+    fun <T> Flow<T>?.collectAsState(): State<T>? = this?.collectAsState()
+
     @Composable
     inline fun <reified T> textFrom(text: T?): String = when (text) {
         null -> ""

+ 0 - 33
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ContextExt.kt

@@ -16,45 +16,12 @@ import java.util.Date
 
 object ContextExt {
 
-//    @Suppress("DEPRECATION")
-//    @Dimension(unit = Dimension.DP)
-//    fun Context.pxToDp(@Dimension(unit = Dimension.PX) px: Int): Int {
-//        val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
-//        val displayMetrics = DisplayMetrics()
-//        windowManager.defaultDisplay.getRealMetrics(displayMetrics)
-//        return (px / displayMetrics.densityDpi.toFloat() * DisplayMetrics.DENSITY_DEFAULT)
-//            .roundToInt()
-//    }
-
-//    val Context.isPhone: Boolean
-//        get() = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-
     val Context.isATv: Boolean
         get() {
             val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
             return uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
         }
 
-//    @Suppress("DEPRECATION")
-//    val Context.isTablet: Boolean
-//        get() {
-//            val outSize = Point()
-//            val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
-//            windowManager.defaultDisplay.getRealSize(outSize)
-//            outSize.x = pxToDp(outSize.x)
-//            outSize.y = pxToDp(outSize.y)
-//            val shorterSideDp: Int
-//            val longerSideDp: Int
-//            if (outSize.x > outSize.y) {
-//                shorterSideDp = outSize.y
-//                longerSideDp = outSize.x
-//            } else {
-//                shorterSideDp = outSize.x
-//                longerSideDp = outSize.y
-//            }
-//            return (shorterSideDp > 480) && (longerSideDp > 640) && (!isPhone) && (!isATv)
-//        }
-
     val Context.timeAsString: String
         get() = DateFormat.getTimeFormat(this).format(Date())
 

+ 67 - 64
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/HiltExt.kt

@@ -11,79 +11,73 @@ package org.mjdev.tvapp.base.extensions
 import android.app.Activity
 import android.content.Context
 import android.content.ContextWrapper
-import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.ViewModelStoreOwner
+import androidx.lifecycle.viewmodel.CreationExtras
 import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavBackStackEntry
-import androidx.savedstate.SavedStateRegistryOwner
+import dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.createInternal
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
-import org.mjdev.tvapp.base.helpers.StateContextWrapper
-import org.mjdev.tvapp.base.viewmodel.BaseViewModel
-import kotlin.reflect.full.companionObjectInstance
-import kotlin.reflect.full.memberFunctions
+import kotlin.reflect.KClass
+import kotlin.reflect.full.companionObject
+import kotlin.reflect.full.functions
 
 object HiltExt {
 
     @Suppress("LocalVariableName")
     @Composable
-    inline fun <reified VM : BaseViewModel> appViewModel(
+    inline fun <reified VM : ViewModel> appViewModel(
         viewModelStoreOwner: ViewModelStoreOwner? = null,
-        key: String? = null
+        key: String? = null,
+        mockFnName :String = String.format("mock%s", VM::class.simpleName),
+        companion: KClass<*>? = VM::class.companionObject,
+        noinline modelMockFnc: ((context: Context) -> VM)? =  { context ->
+            companion?.functions?.firstOrNull { fn ->
+                fn.name == mockFnName
+            }?.call(companion, context) as VM
+        }
     ): VM {
-        return if (isEditMode())
-            createMockModel()
-        else {
-            val _viewModelStoreOwner = viewModelStoreOwner
-                ?: LocalViewModelStoreOwner.current!!
-            val factory = createHiltViewModelFactory(
-                _viewModelStoreOwner
-            )
-            viewModel(
-                viewModelStoreOwner = _viewModelStoreOwner,
-                key = key,
-                factory = factory
-            )
+        val _viewModelStoreOwner = (viewModelStoreOwner ?: LocalViewModelStoreOwner.current)
+            ?: throw (RuntimeException("No view model store."))
+        val modelCreator: MutableMap<KClass<*>, (context: Context) -> Any>.() -> Unit = {
+            put(VM::class) { context ->
+                modelMockFnc?.invoke(context) as VM
+            }
         }
+        return viewModel(
+            viewModelStoreOwner = _viewModelStoreOwner,
+            key = key,
+            factory = createHiltViewModelFactory(
+                _viewModelStoreOwner,
+                modelCreator
+            ),
+        )
     }
 
-    // todo
     @Composable
-    inline fun <reified VM : BaseViewModel> createMockModel(): VM {
-        val context = StateContextWrapper(LocalContext.current)
-        return VM::class.memberFunctions.first { fn ->
-            fn.name == "mock"
-        }.call(VM::class.companionObjectInstance, context) as? VM
-            ?: throw (RuntimeException(
-                "ViewModel ${
-                    VM::class.simpleName
-                } does not contain MOCK companion function." +
-                    "Please define companion function fun MOCK(context:Context)in this view model."
-            ))
-    }
+    fun createHiltViewModelFactory(
+        viewModelStoreOwner: ViewModelStoreOwner,
+        modelCreator: MutableMap<KClass<*>, (context: Context) -> Any>.() -> Unit = {}
+    ): ViewModelProvider.Factory = hiltViewModelFactory(
+        context = LocalContext.current,
+        viewModelStoreOwner = viewModelStoreOwner,
+        modelCreator = modelCreator
+    )
 
     @Composable
-    @PublishedApi
-    internal fun createHiltViewModelFactory(
-        viewModelStoreOwner: ViewModelStoreOwner
-    ): ViewModelProvider.Factory? = if (viewModelStoreOwner is NavBackStackEntry) {
-        HiltViewModelFactory(
-            context = LocalContext.current,
-            navBackStackEntry = viewModelStoreOwner
-        )
-    } else {
-        null
-    }
-
-    @Suppress("FunctionName")
-    private fun HiltViewModelFactory(
+    fun hiltViewModelFactory(
         context: Context,
-        navBackStackEntry: NavBackStackEntry
-    ): ViewModelProvider.Factory? {
-        try {
+        viewModelStoreOwner: ViewModelStoreOwner,
+        modelCreator: MutableMap<KClass<*>, (context: Context) -> Any>.() -> Unit = {}
+    ): ViewModelProvider.Factory {
+        val navBackStackEntry: NavBackStackEntry? = viewModelStoreOwner as? NavBackStackEntry
+        return if (navBackStackEntry == null || isEditMode())
+            createInternal(context, modelCreator)
+        else {
             val activity = context.let {
                 var ctx = it
                 while (ctx is ContextWrapper) {
@@ -97,28 +91,37 @@ object HiltExt {
                         "NavBackStackEntry but instead found: $ctx"
                 )
             }
-            return createInternal(
+            createInternal(
                 activity,
                 navBackStackEntry,
                 navBackStackEntry.arguments,
                 navBackStackEntry.defaultViewModelProviderFactory,
             )
-        } catch (e: Throwable) {
-            return null
         }
     }
 
-    @Suppress("UNUSED_PARAMETER")
     private fun createInternal(
-        activity: Activity,
-        owner: SavedStateRegistryOwner,
-        defaultArgs: Bundle?,
-        delegateFactory: ViewModelProvider.Factory
-    ): ViewModelProvider.Factory? {
-        return dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.createInternal(
-            activity,
-            delegateFactory
-        )
+        context: Context,
+        modelCreator: MutableMap<KClass<*>, (context: Context) -> Any>.() -> Unit
+    ): ViewModelProvider.Factory = MockViewModelFactory(context, modelCreator)
+
+    @Suppress("UNCHECKED_CAST")
+    class MockViewModelFactory(
+        val context: Context,
+        modelCreator: MutableMap<KClass<*>, (context: Context) -> Any>.() -> Unit
+    ) : ViewModelProvider.Factory {
+
+        private val store = mutableMapOf<KClass<*>, (context: Context) -> Any>()
+            .apply(modelCreator)
+
+        override fun <T : ViewModel> create(modelClass: Class<T>): T {
+            return store[modelClass.kotlin]?.invoke(context) as T
+        }
+
+        override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
+            return store[modelClass.kotlin]?.invoke(context) as T
+        }
+
     }
 
 }

+ 10 - 30
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/NavExt.kt

@@ -12,45 +12,25 @@ import androidx.navigation.NavController
 import androidx.navigation.NavGraph
 import androidx.navigation.NavHostController
 import androidx.navigation.NavigatorProvider
-import androidx.navigation.navigation
 import org.mjdev.tvapp.base.navigation.NavGraphBuilderEx
 
 object NavExt {
 
-    /**
-     * Create navigation from extra builder to simplify generation of routes.
-     * @see: [androidx.navigation.NavigatorProvider.navigation]
-     *
-     * @param navController Nav controller
-     * @param route Route
-     * @param builder Builder
-     * @return [NavGraph]
-     * @receiver [NavigatorProvider]
-     */
-    inline fun NavigatorProvider.navigation(
+    inline fun NavController.createGraph(
         navController: NavHostController,
         route: String? = null,
+        startRoute: String? = null,
         builder: NavGraphBuilderEx.() -> Unit
-    ): NavGraph = NavGraphBuilderEx(this, route, navController)
-        .apply(builder)
-        .build()
+    ): NavGraph = navigatorProvider.navigation(navController, route, startRoute, builder)
 
-    /**
-     * Create navigation graph from nav controller.
-     * Extended just with custom builder.
-     *
-     * @see: [androidx.navigation.NavController.createGraph]
-     *
-     * @param route Route
-     * @param navController Nav controller
-     * @param builder Builder
-     * @return [NavGraph]
-     * @receiver [NavController]
-     */
-    inline fun NavController.createGraph(
-        route: String? = null,
+
+    inline fun NavigatorProvider.navigation(
         navController: NavHostController,
+        route: String? = null,
+        startRoute: String? = null,
         builder: NavGraphBuilderEx.() -> Unit
-    ): NavGraph = navigatorProvider.navigation(navController, route, builder)
+    ): NavGraph = NavGraphBuilderEx(this, route, startRoute, navController)
+        .apply(builder)
+        .build()
 
 }

+ 71 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/helpers/MockedViewModels.kt

@@ -0,0 +1,71 @@
+//package org.mjdev.tvapp.base.helpers
+//
+//import android.content.Context
+//import androidx.lifecycle.ViewModel
+//import androidx.lifecycle.ViewModelProvider
+//import androidx.lifecycle.viewmodel.CreationExtras
+//import kotlin.jvm.internal.Reflection
+//import kotlin.reflect.KClass
+//import kotlin.reflect.KFunction
+//import kotlin.reflect.full.companionObject
+//import kotlin.reflect.full.functions
+//
+//// todo: preview android studio does not support reflection
+//// todo: this works runtime only
+//class MockedViewModels<VM : ViewModel>(
+//    val context: Context
+//) : ViewModelProvider.Factory {
+//
+//    private val KClass<*>.viewModelFncName: String
+//        get() = String.format("mock%s", simpleName)
+//
+//    private val KClass<*>.companion : KClass<*> get() {
+//        return companionObject ?: createCompanion()
+//    }
+//
+//    private fun KClass<*>.createCompanion() : KClass<*> {
+//        val cname = qualifiedName?.let {
+//            String.format("%s.Companion", it)
+//        } ?: throw(RuntimeException("Can not create companion class $this"))
+//        val clazz =  Class.forName(cname)
+//            ?: throw(RuntimeException("Can not create companion class $cname"))
+//        return clazz.kt
+//    }
+//
+//    private val KClass<*>.viewModelFnc: KFunction<*>
+//        get() {
+//            val companion = companion
+//            val fnc = companion.functions.firstOrNull { fn -> fn.name == viewModelFncName }
+//            return fnc ?: throw (RuntimeException("No function $viewModelFncName exists in $this."))
+//        }
+//
+//    fun KFunction<*>.invoke(context: Context): Any? {
+//        val instance = (parameters[0].type.classifier as KClass<*>).objectInstance
+//            ?: throw (RuntimeException("Cant create instance of ${parameters[0].type.classifier}"))
+//        return call(instance, context)
+//    }
+//
+//    private fun KClass<*>.viewModelInstance(
+//        context: Context,
+//    ): Any? {
+//        return viewModelFnc.invoke(context)
+//    }
+//
+//    fun KClass<*>.get(context: Context): Any {
+//        return viewModelInstance(context)
+//            ?: throw (RuntimeException("No model instance"))
+//    }
+//
+//    val <T : Any> Class<T>.kt: KClass<T>
+//        get() = Reflection.getOrCreateKotlinClass(this) as KClass<T>
+//
+//    override fun <VM : ViewModel> create(
+//        modelClass: Class<VM>,
+//        extras: CreationExtras
+//    ): VM = modelClass.kt.get(context) as VM
+//
+//    override fun <VM : ViewModel> create(
+//        modelClass: Class<VM>
+//    ): VM = modelClass.kt.get(context) as VM
+//
+//}

+ 0 - 85
app/src/main/kotlin/org/mjdev/tvapp/base/media/SimpleMediaPlayerView.kt

@@ -1,85 +0,0 @@
-/*
- * Copyright (c) Milan Jurkulák 2023.
- * Contact:
- * e: mimoccc@gmail.com
- * e: mj@mjdev.org
- * w: https://mjdev.org
- */
-
-package org.mjdev.tvapp.base.media
-
-import android.net.Uri
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.media3.common.MediaItem
-import androidx.media3.exoplayer.ExoPlayer
-import androidx.media3.ui.PlayerView
-import org.mjdev.tvapp.base.extensions.ComposeExt
-
-@Composable
-@Deprecated("Deprecated, use MediaPlayerContainer instead.")
-fun SimpleMediaPlayerView(
-    modifier: Modifier = Modifier,
-    uri: Uri?,
-    autoPlay: Boolean = true,
-    background: @Composable (player: ExoPlayer) -> Unit = {},
-    mediaPlayerOverlay: @Composable (player: ExoPlayer) -> Unit = {},
-    mediaPlayerControls: @Composable (player: ExoPlayer) -> Unit = {}
-) {
-
-    val isEdit = ComposeExt.isEditMode()
-    val context = LocalContext.current
-
-    val player = remember { ExoPlayer.Builder(context).build() }
-
-    Box(modifier = modifier) {
-
-        background(player)
-
-        if (!isEdit) {
-            AndroidView(
-                modifier = Modifier
-                    .background(Color.Transparent, RectangleShape)
-                    .fillMaxSize(),
-                factory = {
-                    PlayerView(context).apply {
-                        this.player = player
-                    }
-                }
-            )
-        }
-
-        mediaPlayerOverlay(player)
-
-        mediaPlayerControls(player)
-
-    }
-
-    if (!isEdit) {
-
-        DisposableEffect(player) {
-
-            if (uri != null) {
-                player.playWhenReady = autoPlay
-                player.setMediaItem(MediaItem.fromUri(uri))
-                player.prepare()
-            }
-
-            onDispose {
-                player.playWhenReady = false
-            }
-
-        }
-
-    }
-
-}

+ 20 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/navigation/EmptyScreen.kt

@@ -0,0 +1,20 @@
+package org.mjdev.tvapp.base.navigation
+
+import androidx.compose.runtime.Composable
+import org.mjdev.tvapp.base.annotations.TvPreview
+
+internal class EmptyScreen : Screen() {
+
+    override val completeRoute: String = ROUTE_NONE
+
+    @TvPreview
+    @Composable
+    override fun Compose() = super.Compose()
+
+    companion object {
+
+        const val ROUTE_NONE = "none"
+
+    }
+
+}

+ 7 - 8
app/src/main/kotlin/org/mjdev/tvapp/base/navigation/NavGraphBuilderEx.kt

@@ -12,6 +12,7 @@ import androidx.navigation.NavGraph
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.NavHostController
 import androidx.navigation.NavigatorProvider
+import org.mjdev.tvapp.base.navigation.EmptyScreen.Companion.ROUTE_NONE
 
 /**
  * Custom nav graph builder.
@@ -26,20 +27,18 @@ import androidx.navigation.NavigatorProvider
  */
 class NavGraphBuilderEx(
     provider: NavigatorProvider,
-    route: String?,
+    route: String? = null,
+    startRoute: String? = null,
     val navHostController: NavHostController
-) : NavGraphBuilder(provider, "none", route) {
+) : NavGraphBuilder(provider, startRoute ?: "none", route) {
 
-    var splashDestinationRoute: String? = null
-    var homeDestinationRoute : String? = null
+    var splashDestinationRoute: String? = startRoute
+    var homeDestinationRoute: String = startRoute ?: ROUTE_NONE
     var menuItems: MutableList<MenuItem> = mutableListOf()
 
     override fun build(): NavGraph {
         val navGraph = super.build()
-        if (splashDestinationRoute == null && homeDestinationRoute == null) {
-            throw(RuntimeException("One class in ComposeActivity should have start property set."))
-        }
-        (splashDestinationRoute ?: homeDestinationRoute)?.also { route ->
+        (splashDestinationRoute ?: homeDestinationRoute).also { route ->
             navGraph.setStartDestination(route)
         }
         return navGraph

+ 38 - 13
app/src/main/kotlin/org/mjdev/tvapp/base/navigation/NavHostEx.kt

@@ -8,38 +8,63 @@
 
 package org.mjdev.tvapp.base.navigation
 
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavHostController
 import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.rememberNavController
+import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.NavExt.createGraph
+import org.mjdev.tvapp.base.navigation.Screen.Companion.screen
 
-/**
- * Custom NavHost object to extend functionality and simplify
- * navigation across application.
- *
- * @param modifier Modifier modifier as usual for composable
- * @param route Route route expected
- * @param navController Nav controller navigation controller
- * @param builder Builder builder for routes
- */
+@TvPreview
 @Composable
 fun NavHostEx(
     modifier: Modifier = Modifier,
     route: String? = null,
-    navController: NavHostController,
-    builder: NavGraphBuilderEx.() -> Unit
+    startRoute: String? = null,
+    navController: NavHostController = rememberNavController(),
+    contentAlignment: Alignment = Alignment.Center,
+    enterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) = {
+        fadeIn(animationSpec = tween(700))
+    },
+    exitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) = {
+        fadeOut(animationSpec = tween(700))
+    },
+    popEnterTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition) =
+        enterTransition,
+    popExitTransition: (AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition) =
+        exitTransition,
+    builder: NavGraphBuilderEx.() -> Unit = {
+        screen(route = EmptyScreen())
+    }
 ) {
+
     NavHost(
+        modifier = modifier,
         navController = navController,
         graph = remember(route, builder) {
             navController.createGraph(
-                route,
                 navController,
+                route,
+                startRoute,
                 builder
             )
         },
-        modifier = modifier
+        contentAlignment = contentAlignment,
+        enterTransition = enterTransition,
+        exitTransition = exitTransition,
+        popEnterTransition = popEnterTransition,
+        popExitTransition = popExitTransition,
     )
+
 }

+ 26 - 74
app/src/main/kotlin/org/mjdev/tvapp/base/navigation/Screen.kt

@@ -23,12 +23,16 @@ import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.shape.RoundedCornerShape
 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.graphics.Shape
 import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.navigation.NamedNavArgument
 import androidx.navigation.NavBackStackEntry
@@ -50,19 +54,13 @@ import kotlin.reflect.full.createInstance
  *
  * @constructor Create empty constructor for screen
  */
-@Suppress("unused")
+@Suppress("unused", "LeakingThis")
 open class Screen : NavController.OnDestinationChangedListener {
 
-    /**
-     * Autogenerated path / route
-     */
     private val routeBase: String
         get() = (this::class.simpleName ?: "-") + "?"
 
-    /**
-     * Complete autogenerated path / route with parameters
-     */
-    val completeRoute: String
+    open val completeRoute: String
         get() = routeBase.let { rb ->
             var routeImpl = rb
             args.forEach { arg ->
@@ -71,47 +69,26 @@ open class Screen : NavController.OnDestinationChangedListener {
             routeImpl
         }
 
-    /**
-     * Screen title
-     */
     open val titleResId: Int = -1
 
-    /**
-     * Screen arguments if any
-     */
     open val args: List<NamedNavArgument> = emptyList()
 
-    /**
-     * Menu string resource id
-     * menu is generated only if it returns id > 0
-     */
     open val menuTitleResId: Int = -1
 
-    /**
-     * Menu icon if any
-     */
     open val menuIcon: ImageVector? = null
 
-    /**
-     * Show once and hide on back pressed, remove from backstack
-     */
     open val showOnce = false
 
-    /**
-     * Autogenerated menu item, if any
-     * Generated only if [menuTitleResId] > 0
-     */
+    open val backgroundColor :Color = Color.DarkGray
+
+    open val roundRadius : Dp = 0.dp
+
+    open val background : Shape = RoundedCornerShape(roundRadius)
+
     val menuItem
         get() = if (menuTitleResId >= 0) MenuItem(menuTitleResId, menuIcon, completeRoute)
         else null
 
-    /**
-     * Compose function.
-     *
-     * Helper function to preview without parameters as preview of compose needed.
-     *
-     * @return
-     */
     @TvPreview
     @Composable
     @CallSuper
@@ -119,14 +96,6 @@ open class Screen : NavController.OnDestinationChangedListener {
         Compose(null, null, emptyList(), mapOf())
     }
 
-    /**
-     * Compose function for compose screen.
-     * This should be only a method overridden in super classes.
-     *
-     * @param navController Nav controller
-     * @param backStackEntry Back stack entry
-     * @param menuItems Menu items
-     */
     @Composable
     open fun Compose(
         navController: NavHostController?,
@@ -142,14 +111,15 @@ open class Screen : NavController.OnDestinationChangedListener {
         Column(
             modifier = Modifier
                 .fillMaxSize()
-                .background(Color.DarkGray, RectangleShape),
+                .background(backgroundColor, background),
             verticalArrangement = Arrangement.Center,
             horizontalAlignment = Alignment.CenterHorizontally
         ) {
 
             Text(
                 text = "Empty Screen",
-                fontSize = 32.sp,
+                fontSize = 64.sp,
+                fontWeight = FontWeight.Bold,
                 color = Color.White
             )
 
@@ -172,14 +142,6 @@ open class Screen : NavController.OnDestinationChangedListener {
         val SlideInVertically = slideInVertically(animationSpec = tween(1000))
         val SlideOutVertically = slideOutVertically(animationSpec = tween(1000))
 
-        /**
-         * Helper function to get params from route.
-         *
-         * @param argId Arg id
-         * @param defaultValue Default value
-         * @return
-         * @receiver [NavBackStackEntry]
-         */
         @Suppress("UNCHECKED_CAST", "DEPRECATION")
         private fun <T> NavBackStackEntry.arg(
             argId: String,
@@ -204,7 +166,11 @@ open class Screen : NavController.OnDestinationChangedListener {
                 val currentRoute = currentRoute
                 val equals = currentRoute?.equals(finalRoute)
                 if (equals != true) {
-                    navigate(finalRoute)
+//                    try {
+                        navigate(finalRoute)
+//                    } catch (e: Exception) {
+//                        Timber.e(e)
+//                    }
                 }
             }
         }
@@ -227,32 +193,18 @@ open class Screen : NavController.OnDestinationChangedListener {
                 val currentRoute = currentRoute
                 val equals = currentRoute?.equals(finalRoute)
                 if (equals != true) {
-                    navigate(finalRoute)
+//                    try {
+                        navigate(finalRoute)
+//                    } catch (e: Exception) {
+//                        Timber.e(e)
+//                    }
                 }
             }
         }
 
-        /**
-         * Current route nav host helper
-         *
-         * Function returns current route that is shown.
-         *
-         * @return current route on screen
-         */
         val NavHostController.currentRoute
             get() = currentDestination?.route
 
-        /**
-         * Custom function to generate screen in nav graph.
-         *
-         * @param route Route route expected
-         * @param isHomeScreen Is home screen, indicates that this screen will
-         * be used as home screen. Should be used once, or in case that is defined multiple times,
-         * the last one is used as home screen.
-         *
-         * @param T T Screen object generated for route.
-         * @receiver [NavGraphBuilderEx]
-         */
         fun <T : Screen> NavGraphBuilderEx.screen(
             route: T,
             isHomeScreen: Boolean = false,

+ 50 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/navigation/ScreenWithPages.kt

@@ -0,0 +1,50 @@
+package org.mjdev.tvapp.base.navigation
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavHostController
+import org.mjdev.tvapp.R
+import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.page.Pager
+import org.mjdev.tvapp.base.page.PagerScope
+import org.mjdev.tvapp.base.page.PagerState
+import org.mjdev.tvapp.base.ui.components.complex.ScreenView
+
+open class ScreenWithPages : Screen() {
+
+    open val pages : @Composable PagerScope.(pagerState: PagerState) -> Unit = {}
+
+    @TvPreview
+    @Composable
+    override fun Compose() = super.Compose()
+
+    @Composable
+    override fun Compose(
+        navController: NavHostController?,
+        backStackEntry: NavBackStackEntry?,
+        menuItems: List<MenuItem>,
+        args: Map<String, Any?>
+    ) {
+
+        val menuState = remember { mutableListOf<MenuItem>().apply { addAll(menuItems) } }
+
+        ScreenView(
+            navController = navController,
+            title = if (titleResId > -1) stringResource(titleResId) else R.string.app_name,
+            menuItems = menuState
+        ) { screenState ->
+
+            Pager(
+                navController=navController,
+                screenState = screenState,
+                menuItems = menuState,
+                startIndex = 0,
+                pages = pages
+            )
+
+        }
+
+    }
+}

+ 59 - 10
app/src/main/kotlin/org/mjdev/tvapp/base/page/Page.kt

@@ -2,22 +2,28 @@ package org.mjdev.tvapp.base.page
 
 import android.annotation.SuppressLint
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.shape.RoundedCornerShape
 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.graphics.Shape
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.navigation.NavHostController
+import androidx.tv.material3.Text
+import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 import org.mjdev.tvapp.base.navigation.MenuItem
 import org.mjdev.tvapp.base.state.ScreenState
 
-abstract class Page {
-
-
-    open val title: Any? = null
-    open val icon: Any? = null
+@Suppress("LeakingThis")
+open class Page {
 
     var navController: NavHostController? = null
     var screenState: ScreenState? = null
@@ -26,21 +32,31 @@ abstract class Page {
 
     val menuItem get() = title?.let { t -> MenuItem(t, icon) }
 
+    open val title: Any? = null
+    open val icon: Any? = null
+
+    open val backgroundColor: Color = Color.DarkGray
+
+    open val roundRadius: Dp = 0.dp
+
+    open val background: Shape = RoundedCornerShape(roundRadius)
+
     @SuppressLint("ComposableNaming")
+    @TvPreview
     @Composable
     fun content() {
 
+        screenState?.titleState?.value = title
+
         Column(
             Modifier
                 .fillMaxSize()
-                .background(Color.DarkGray, RectangleShape)
+                .background(backgroundColor, background)
                 .touchable {
                     freeFocus()
                 }
         ) {
 
-            screenState?.titleState?.value = title
-
             Content()
 
         }
@@ -48,7 +64,40 @@ abstract class Page {
     }
 
     @Composable
-    abstract fun Content()
+    open fun Content() {
+
+        EMPTY_PAGE
 
+    }
+
+    companion object {
+
+        val EMPTY_PAGE: Page
+            get() = object : Page() {
+
+                @Composable
+                override fun Content() {
+
+                    Column(
+                        modifier = Modifier.fillMaxSize(),
+                        verticalArrangement = Arrangement.Center,
+                        horizontalAlignment = Alignment.CenterHorizontally,
+                    ) {
+
+                        Text(
+
+                            text = "Empty Screen",
+                            fontSize = 64.sp,
+                            fontWeight = FontWeight.Bold,
+                            color = Color.White,
+                        )
+
+                    }
+
+                }
+
+            }
+
+    }
 
 }

+ 9 - 12
app/src/main/kotlin/org/mjdev/tvapp/base/page/PageState.kt

@@ -1,14 +1,11 @@
 package org.mjdev.tvapp.base.page
 
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
-
-class PageState(
-    title: Any? = null,
-    icon: Any? = null
-) {
-
-    val title: MutableState<Any?> = mutableStateOf(title)
-    val icon: MutableState<Any?> = mutableStateOf(icon)
-
-}
+//class PageState(
+//    title: Any? = null,
+//    icon: Any? = null
+//) {
+//
+//    val title: MutableState<Any?> = mutableStateOf(title)
+//    val icon: MutableState<Any?> = mutableStateOf(icon)
+//
+//}

+ 25 - 9
app/src/main/kotlin/org/mjdev/tvapp/base/page/Pager.kt

@@ -2,48 +2,64 @@ package org.mjdev.tvapp.base.page
 
 import android.annotation.SuppressLint
 import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
+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.graphics.Shape
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
 import androidx.navigation.NavHostController
+import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.navigation.MenuItem
+import org.mjdev.tvapp.base.page.Page.Companion.EMPTY_PAGE
 import org.mjdev.tvapp.base.state.ScreenState
 
 @SuppressLint("AutoboxingStateValueProperty")
+@TvPreview
 @Composable
 fun Pager(
     navController: NavHostController? = null,
     screenState: ScreenState? = null,
     menuItems: MutableList<MenuItem> = mutableListOf(),
     startIndex: Int = 0,
+    roundCornerSize: Dp = 0.dp,
+    backGroundColor: Color = Color.Transparent,
+    backGroundShape: Shape = RoundedCornerShape(roundCornerSize),
     pages: @Composable PagerScope.(pagerState: PagerState) -> Unit = {}
 ) {
 
+    val isEdit = isEditMode()
     val pagerScope = PagerScope(navController, screenState, menuItems)
     val pageState = remember { PagerState(startIndex) }
 
     Column(
         modifier = Modifier
             .fillMaxSize()
-            .background(Color.DarkGray, RectangleShape)
+            .background(backGroundColor, backGroundShape),
+        verticalArrangement = Arrangement.Center,
+        horizontalAlignment = Alignment.CenterHorizontally,
     ) {
 
         pages.invoke(pagerScope, pageState)
 
         val index = pageState.index.value
 
-        pagerScope[index].let { page ->
-
-            page.navController = navController
-            page.screenState = screenState
-            page.pagerState = pageState
-
-            page.content()
+        val page = if (pagerScope.size > index) pagerScope[index]
+        else if (isEdit) EMPTY_PAGE
+        else null
 
+        page?.let { p ->
+            p.navController = navController
+            p.screenState = screenState
+            p.pagerState = pageState
+            p.content()
         }
 
     }

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/base/page/PagerScope.kt

@@ -6,8 +6,8 @@ import org.mjdev.tvapp.base.state.ScreenState
 
 class PagerScope(
     val navController: NavHostController? = null,
-    val screenState: ScreenState? = null,
-    val menuItems: MutableList<MenuItem> = mutableListOf(),
+    private val screenState: ScreenState? = null,
+    private val menuItems: MutableList<MenuItem> = mutableListOf(),
 ) : ArrayList<Page>() {
 
     fun page(page: Page) {

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

@@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.CardDefaults
 import androidx.tv.material3.CardScale
@@ -21,13 +22,12 @@ 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.annotations.TvPreview
 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)
-@TvPreview
+@Preview
 @Composable
 @SuppressLint("ModifierParameter")
 fun CarouselCard(

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

@@ -13,14 +13,14 @@ import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.widthIn
 import androidx.compose.runtime.Composable
 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.base.annotations.TvPreview
 import org.mjdev.tvapp.data.Movie
 import org.mjdev.tvapp.base.ui.components.complex.FocusableCard
 
 @OptIn(ExperimentalTvMaterial3Api::class)
-@TvPreview
+@Preview
 @Composable
 @SuppressLint("ModifierParameter")
 fun MovieCard(

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

@@ -21,12 +21,12 @@ import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 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.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 import org.mjdev.tvapp.base.ui.components.card.CarouselCard
 import org.mjdev.tvapp.data.Movie
@@ -34,7 +34,7 @@ import org.mjdev.tvapp.data.Movie
 // todo swipe left and swipe right
 @SuppressLint("AutoboxingStateValueProperty")
 @OptIn(ExperimentalTvMaterial3Api::class)
-@TvPreview
+@Preview
 @Composable
 fun BigCarousel(
     modifier: Modifier = Modifier,

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

@@ -22,7 +22,6 @@ import androidx.compose.ui.graphics.takeOrElse
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
-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
@@ -32,11 +31,12 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.LocalContentColor
 import androidx.tv.material3.LocalTextStyle
 import org.mjdev.tvapp.R
+import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.ui.components.button.Button
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
 @OptIn(ExperimentalTvMaterial3Api::class)
-@Preview
+@TvPreview
 @Composable
 fun ErrorMessage(
     error: Throwable? = null,
@@ -46,7 +46,7 @@ fun ErrorMessage(
     fontSizeMessage: TextUnit = 14.sp,
     fontWeight: FontWeight = FontWeight.Bold,
     color: Color = Color.White,
-    paddingTitle:Dp = 4.dp,
+    paddingTitle: Dp = 4.dp,
     paddingMessage: Dp = paddingTitle,
     backgroundColor: Color = Color.Red,
     roundSize: Dp = 8.dp,
@@ -55,9 +55,7 @@ fun ErrorMessage(
     onDismiss: () -> Unit = {}
 ) {
     Box(
-        modifier = Modifier
-            .fillMaxWidth()
-            .background(Color.DarkGray)
+        modifier = Modifier.fillMaxWidth()
     ) {
 
         ConstraintLayout(

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

@@ -20,17 +20,17 @@ import androidx.compose.ui.focus.onFocusEvent
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.graphics.Shape
+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.ClickableSurfaceDefaults
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Surface
-import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 
 @OptIn(ExperimentalTvMaterial3Api::class)
-@TvPreview
+@Preview
 @Composable
 fun FocusableBox(
     modifier: Modifier = Modifier,

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

@@ -11,6 +11,7 @@ import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.CardBorder
 import androidx.tv.material3.CardColors
@@ -22,7 +23,6 @@ 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.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ModifierExt.touchable
 import org.mjdev.tvapp.base.ui.components.image.ImageAny
 import org.mjdev.tvapp.base.ui.components.text.TextAny
@@ -30,7 +30,7 @@ import org.mjdev.tvapp.data.Movie
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @SuppressLint("ModifierParameter")
-@TvPreview
+@Preview
 @Composable
 fun FocusableCard(
     movie: Movie? = null,

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

@@ -20,15 +20,15 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 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.compose.ui.unit.TextUnit
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.constraintlayout.compose.ConstraintLayout
 import org.mjdev.tvapp.R
+import org.mjdev.tvapp.base.annotations.TvPreview
 
-@Preview
+@TvPreview
 @Composable
 fun Header(
     modifier: Modifier = Modifier,

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

@@ -16,9 +16,11 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.navigation.NavHostController
 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.MenuItem
 import org.mjdev.tvapp.base.navigation.Screen
 import org.mjdev.tvapp.base.state.ScreenState
@@ -30,6 +32,7 @@ import org.mjdev.tvapp.base.ui.components.navigation.Navigation
 @Composable
 fun ScreenView(
     navController: NavHostController? = null,
+    navigationBackgroundColor:Color = Color(0xff202020),
     actions: @Composable RowScope.() -> Unit = {},
     title: Any? = R.string.app_name,
     menuItems: List<MenuItem> = listOf(),
@@ -40,16 +43,18 @@ fun ScreenView(
     }
 ) {
 
+    val isEdit = isEditMode()
     val screenState = rememberScreenState(title)
     val errorState = remember { screenState.errorState }
 
     Box(
         modifier = Modifier.fillMaxSize(),
-        contentAlignment = Alignment.Center,
+        contentAlignment = Alignment.BottomCenter,
     ) {
 
         Navigation(
             navController = navController,
+            backgroundColor = navigationBackgroundColor,
             items = menuItems,
             content = {
                 Column(
@@ -60,7 +65,7 @@ fun ScreenView(
             }
         )
 
-        if (errorState.value != null) {
+        if (isEdit || (errorState.value != null)) {
             ErrorMessage(errorState.value)
         }
 

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

@@ -23,17 +23,17 @@ import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shadow
 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
 import androidx.tv.material3.MaterialTheme
 import org.mjdev.tvapp.R
-import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.ui.components.image.CircleImage
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
-@TvPreview
+@Preview
 @Composable
 fun Title(
     modifier: Modifier = Modifier,

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

@@ -19,16 +19,18 @@ import androidx.compose.runtime.remember
 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.tv.foundation.lazy.list.TvLazyListState
 
 @SuppressLint("AutoboxingStateValueProperty")
+@Preview
 @Composable
 fun TouchBox(
-        modifier: Modifier = Modifier,
-        contentAlignment: Alignment = Alignment.TopStart,
-        propagateMinConstraints: Boolean = false,
-        state: TvLazyListState? = null,
-        content: @Composable BoxScope.() -> Unit
+    modifier: Modifier = Modifier,
+    contentAlignment: Alignment = Alignment.TopStart,
+    propagateMinConstraints: Boolean = false,
+    state: TvLazyListState? = null,
+    content: @Composable BoxScope.() -> Unit = {}
 ) {
     val scrollDelta = remember { mutableFloatStateOf(0f) }
 

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

@@ -23,17 +23,17 @@ import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Icon
 import androidx.tv.material3.LocalContentColor
 import coil.compose.AsyncImage
-import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.DrawableExt.asImageBitmap
 import java.net.URL
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 @SuppressLint("ModifierParameter")
-@TvPreview
+@Preview
 @Composable
 fun IconAny(
     modifier: Modifier = Modifier,

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

@@ -24,7 +24,6 @@ import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 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.navigation.NavHostController
@@ -32,6 +31,7 @@ import androidx.tv.material3.DrawerValue
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.NavigationDrawer
 import androidx.tv.material3.rememberDrawerState
+import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.navigation.MenuItem
 
@@ -40,13 +40,13 @@ const val SEARCH_ITEM = 65534
 
 @SuppressLint("AutoboxingStateValueProperty")
 @OptIn(ExperimentalTvMaterial3Api::class)
-@Preview
+@TvPreview
 @Composable
 fun Navigation(
     modifier: Modifier = Modifier,
     navController: NavHostController? = null,
     items: List<MenuItem> = listOf(),
-    backgroundColor: Color = Color(0xFF202020),
+    backgroundColor: Color = Color.DarkGray,
     roundSize: Dp = 8.dp,
     borderSize: Dp = 0.dp,
     borderColor: Color = Color.White,

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

@@ -9,6 +9,7 @@
 package org.mjdev.tvapp.base.ui.components.navigation
 
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Row
@@ -26,6 +27,7 @@ import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.DrawerValue
 import androidx.tv.material3.ExperimentalTvMaterial3Api
+import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 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
@@ -34,6 +36,7 @@ import org.mjdev.tvapp.base.ui.components.text.TextAny
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 fun NavigationRow(
+    modifier: Modifier = Modifier,
     id: Int = -1,
     drawerValue: DrawerValue = DrawerValue.Open,
     text: Any? = "test",
@@ -43,10 +46,18 @@ fun NavigationRow(
     focusedColor: Color = Color.Black,
     unFocusedColor: Color = Color.Transparent,
     expandedWidth: Dp = 150.dp,
+    contentPadding: Dp = 2.dp,
     onFocus: (id: Int) -> Unit = {},
     onClick: (id: Int) -> Unit = {},
 ) {
+    val isEdit = isEditMode()
+
     FocusableBox(
+        modifier = Modifier
+            .padding(contentPadding)
+            .background(
+                if (isEdit) Color.DarkGray else Color.Transparent
+            ),
         focusedColor = focusedColor,
         unFocusedColor = unFocusedColor,
         onFocus = {
@@ -55,7 +66,6 @@ fun NavigationRow(
         onClick = {
             onClick(id)
         },
-        modifier = Modifier.padding(2.dp)
     ) {
 
         Row(

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

@@ -20,14 +20,14 @@ import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.TextUnit
 import androidx.tv.material3.LocalTextStyle
 import androidx.tv.material3.Text
-import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.textFrom
 
 @SuppressLint("ModifierParameter")
-@TvPreview
+@Preview
 @Composable
 fun TextAny(
     text: Any? = "",

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

@@ -13,6 +13,7 @@ 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.tv.material3.MaterialTheme
 import androidx.tv.material3.Text
 import org.mjdev.tvapp.base.annotations.TvPreview
@@ -27,6 +28,7 @@ fun Loading(modifier: Modifier = Modifier) {
     ) {
         Text(
             text = "Loading...",
+            color = Color.White,
             style = MaterialTheme.typography.headlineMedium
         )
     }

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

@@ -24,6 +24,7 @@ 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
@@ -42,7 +43,7 @@ import org.mjdev.tvapp.ui.screens.PlayerScreen
 import org.mjdev.tvapp.viewmodel.MainViewModel
 
 @SuppressLint("ComposableNaming")
-class MainPage: Page() {
+class MainPage : Page() {
 
     override val title: Int = R.string.title_home
     override val icon: ImageVector = Icons.Default.Home
@@ -51,14 +52,16 @@ class MainPage: Page() {
     @Composable
     override fun Content() {
 
-        val viewModel = appViewModel<MainViewModel>()
+        val isEdit = isEditMode()
+
+        val viewModel: MainViewModel = appViewModel { context ->
+            MainViewModel.mockMainViewModel(context)
+        }
 
         val categoryList = remember { viewModel.categoryList }.collectAsState()
         val featuredMovieList = remember { viewModel.featuredMovieList }.collectAsState()
         val messages = remember { viewModel.messages }.collectAsState()
-        val networkState = remember {
-            viewModel.networkInfo.networkStatus
-        }.collectAsState(null)
+        val networkState = remember { viewModel.networkInfo.networkStatus }.collectAsState(null)
 
         val onItemClick: (movie: Movie?) -> Unit = { movie ->
             if (movie == null) {
@@ -91,7 +94,7 @@ class MainPage: Page() {
 
             }
 
-            if (networkState.value !is NetworkStatus.Connected) item {
+            if (isEdit || (networkState.value !is NetworkStatus.Connected)) item {
                 ErrorMessage(
                     error = stringResource(R.string.error_no_network).asException(),
                     backgroundColor = Color.Black,
@@ -100,22 +103,18 @@ class MainPage: Page() {
             }
 
 
-            if (categoryList.value.isNotEmpty()) item {
-
+            if (isEdit || categoryList.value.isNotEmpty()) item {
                 Tabs(
                     items = categoryList.value.map { it.name }
                 )
-
             }
 
-            if (featuredMovieList.value.isNotEmpty()) item {
-
+            if (isEdit || featuredMovieList.value.isNotEmpty()) item {
                 BigCarousel(
                     modifier = Modifier.touchable(),
                     items = featuredMovieList.value,
                     onItemClicked = onItemClick
                 )
-
             }
 
             items(categoryList.value) { category ->

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

@@ -58,7 +58,10 @@ class DetailScreen : Screen() {
         args: Map<String, Any?>
     ) {
 
-        val viewModel = appViewModel<DetailViewModel>()
+        val viewModel: DetailViewModel = appViewModel { context ->
+            DetailViewModel.mockDetailViewModel(context)
+        }
+
         val movieId: Long? = remember { args[movieId] as Long? }
         val movieState = remember { viewModel.detailsLoadingState(movieId) }.collectAsState()
 

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

@@ -9,58 +9,26 @@
 package org.mjdev.tvapp.ui.screens
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.res.stringResource
-import androidx.navigation.NamedNavArgument
-import androidx.navigation.NavBackStackEntry
-import androidx.navigation.NavHostController
-import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.annotations.TvPreview
-import org.mjdev.tvapp.base.navigation.MenuItem
-import org.mjdev.tvapp.base.navigation.Screen
-import org.mjdev.tvapp.base.page.Pager
-import org.mjdev.tvapp.base.ui.components.complex.ScreenView
+import org.mjdev.tvapp.base.navigation.ScreenWithPages
+import org.mjdev.tvapp.base.page.PagerScope
+import org.mjdev.tvapp.base.page.PagerState
 import org.mjdev.tvapp.ui.pages.AboutPage
 import org.mjdev.tvapp.ui.pages.MainPage
 import org.mjdev.tvapp.ui.pages.SubscriptionPage
 
-class MainScreen : Screen() {
-
-    override val args = listOf<NamedNavArgument>()
+class MainScreen : ScreenWithPages() {
 
     @TvPreview
     @Composable
     override fun Compose() = super.Compose()
 
-    @Composable
-    override fun Compose(
-        navController: NavHostController?,
-        backStackEntry: NavBackStackEntry?,
-        menuItems: List<MenuItem>,
-        args: Map<String, Any?>
-    ) {
-
-        val menuState = remember { mutableListOf<MenuItem>().apply { addAll(menuItems) } }
-
-        ScreenView(
-            navController = navController,
-            title = if (titleResId > -1) stringResource(titleResId) else R.string.app_name,
-            menuItems = menuState
-        ) { screenState ->
+    override val pages: @Composable (PagerScope.(PagerState) -> Unit) = {
 
-            Pager(
-                navController,
-                screenState,
-                menuState
-            ) {
-
-                page(MainPage())
-                page(AboutPage())
-                page(SubscriptionPage())
-
-            }
-
-        }
+        page(MainPage())
+        page(AboutPage())
+        page(SubscriptionPage())
 
     }
+
 }

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

@@ -60,7 +60,10 @@ class PlayerScreen : Screen() {
         args: Map<String, Any?>
     ) {
 
-        val viewModel = appViewModel<DetailViewModel>()
+        val viewModel : DetailViewModel = appViewModel { context ->
+            DetailViewModel.mockDetailViewModel(context)
+        }
+
         val movieId: Long? = remember { args[movieId] as Long? }
         val movieState = remember { viewModel.detailsLoadingState(movieId) }.collectAsState()
 

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

@@ -34,6 +34,7 @@ import androidx.navigation.NavHostController
 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.MenuItem
 import org.mjdev.tvapp.base.navigation.Screen
 import org.mjdev.tvapp.base.ui.components.icon.IconAny
@@ -55,9 +56,8 @@ class SplashScreen : Screen() {
         args: Map<String, Any?>
     ) {
 
-        val scale = remember {
-            Animatable(0f)
-        }
+        val isEdit = isEditMode()
+        val scale = remember { Animatable(if (isEdit) 1f else 0f) }
 
         LaunchedEffect(key1 = true) {
             scale.animateTo(
@@ -111,7 +111,7 @@ class SplashScreen : Screen() {
 
             TextAny(
                 text = R.string.author,
-                fontSize = 18.sp,
+                fontSize = 14.sp,
                 fontWeight = FontWeight.Bold,
                 color = Color.White.copy(alpha = 0.5f)
             )

+ 4 - 1
app/src/main/kotlin/org/mjdev/tvapp/viewmodel/DetailViewModel.kt

@@ -46,7 +46,10 @@ class DetailViewModel @Inject constructor(
     companion object {
 
         @Suppress("unused", "UNUSED_PARAMETER")
-        fun mock(context: Context) = DetailViewModel(MovieRepository(MovieAPI()))
+        fun mockDetailViewModel(
+            context: Context
+        ): DetailViewModel = DetailViewModel(MovieRepository(MovieAPI()))
 
     }
+
 }

+ 2 - 2
app/src/main/kotlin/org/mjdev/tvapp/viewmodel/MainViewModel.kt

@@ -48,9 +48,9 @@ class MainViewModel @Inject constructor(
     companion object {
 
         @Suppress("unused")
-        fun mock(context: Context) = MainViewModel(
+        fun mockMainViewModel(context: Context): MainViewModel = MainViewModel(
             MovieRepository(MovieAPI()),
-            NetworkConnectivityServiceImpl(context),
+            NetworkConnectivityServiceImpl(context)
         )
 
     }

+ 8 - 6
settings.gradle.kts

@@ -1,10 +1,12 @@
+@file:Suppress("UnstableApiUsage")
+
 /*
- * Copyright (c) Milan Jurkulák 2023.
- * Contact:
- * e: mimoccc@gmail.com
- * e: mj@mjdev.org
- * w: https://mjdev.org
- */
+* Copyright (c) Milan Jurkulák 2023.
+* Contact:
+* e: mimoccc@gmail.com
+* e: mj@mjdev.org
+* w: https://mjdev.org
+*/
 
 pluginManagement {
     repositories {