Browse Source

remembers

Milan Jurkulak 3 năm trước cách đây
mục cha
commit
f7c666df9e
33 tập tin đã thay đổi với 302 bổ sung và 64 xóa
  1. 11 0
      app/src/main/kotlin/org/mjdev/tvapp/activity/IPTVActivity.kt
  2. 11 0
      app/src/main/kotlin/org/mjdev/tvapp/activity/MainActivity.kt
  3. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/helpers/AuthManager.kt
  4. 8 0
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/MainPage.kt
  5. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/DetailScreen.kt
  6. 1 0
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/IPTVScreen.kt
  7. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/SplashScreen.kt
  8. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/screens/WebScreen.kt
  9. 11 0
      tvlib/src/main/kotlin/org/mjdev/tvlib/activity/ComposableActivity.kt
  10. 17 14
      tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/ComposeExt.kt
  11. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/NavExt.kt
  12. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/apps/AppsManager.kt
  13. 10 2
      tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/CachingCursor.kt
  14. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/lifecycle/LifeCycle.kt
  15. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/media/MetadataRetriever.kt
  16. 15 0
      tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/other/ConfigChangeCallback.kt
  17. 162 0
      tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/other/SystemUIController.kt
  18. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/network/NetworkConnectivityService.kt
  19. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/permission/PermissonManager.kt
  20. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/button/Button.kt
  21. 5 2
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/card/Card.kt
  22. 4 4
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/card/PreviewCard.kt
  23. 10 8
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/carousel/BigCarousel.kt
  24. 4 4
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/carousel/Carousel.kt
  25. 0 10
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/complex/VerticalScrollableBox.kt
  26. 1 0
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/ExoPlayerImpl.kt
  27. 2 2
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerContainer.kt
  28. 4 0
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerContainerDefaults.kt
  29. 6 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerState.kt
  30. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/navigation/NavigationRow.kt
  31. 6 3
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/page/Page.kt
  32. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/tv/Tabs.kt
  33. 1 1
      tvlib/src/main/kotlin/org/mjdev/tvlib/updater/AppUpdater.kt

+ 11 - 0
app/src/main/kotlin/org/mjdev/tvapp/activity/IPTVActivity.kt

@@ -20,6 +20,7 @@ import org.mjdev.tvapp.ui.screens.IPTVScreen
 import org.mjdev.tvapp.ui.screens.LoadingScreen
 import org.mjdev.tvlib.annotations.Previews
 import org.mjdev.tvlib.extensions.NavControllerExt.openAsTop
+import org.mjdev.tvlib.helpers.other.SystemUIController
 import org.mjdev.tvlib.interfaces.ItemAudio
 import org.mjdev.tvlib.interfaces.ItemPhoto
 import org.mjdev.tvlib.interfaces.ItemVideo
@@ -28,6 +29,16 @@ import org.mjdev.tvlib.navigation.NavHostControllerEx
 @AndroidEntryPoint
 class IPTVActivity : ComposableActivity() {
 
+    private val systemUIController by lazy {
+        SystemUIController(this) {
+            if (isLandscape) hideSystemUI() else showSystemUI()
+        }
+    }
+
+    init {
+        systemUIController.register()
+    }
+
     @Previews
     @Composable
     override fun Compose() = super.Compose()

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

@@ -16,10 +16,21 @@ import org.mjdev.tvlib.navigation.NavGraphBuilderEx
 import org.mjdev.tvapp.ui.screens.MainScreen
 import org.mjdev.tvapp.ui.screens.SplashScreen
 import org.mjdev.tvlib.annotations.Previews
+import org.mjdev.tvlib.helpers.other.SystemUIController
 
 @AndroidEntryPoint
 class MainActivity : ComposableActivity() {
 
+    private val systemUIController by lazy {
+        SystemUIController(this) {
+            if (isLandscape) hideSystemUI() else showSystemUI()
+        }
+    }
+
+    init {
+        systemUIController.register()
+    }
+
     @Previews
     @Composable
     override fun Compose() = super.Compose()

+ 1 - 1
app/src/main/kotlin/org/mjdev/tvapp/helpers/AuthManager.kt

@@ -153,7 +153,7 @@ class AuthManager(
         fun rememberAuthManager(
             onUserChange: AuthManager.(user: User?) -> Unit
         ) = LocalContext.current.let { ctx ->
-            remember {
+            remember(ctx) {
                 AuthManager(ctx, onUserChange)
             }
         }

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

@@ -96,6 +96,14 @@ class MainPage : Page() {
         val authManager = rememberAuthManager { u -> user.value = u }
         val apps = remember { viewModel.apps }.collectAsState(emptyList())
 
+//        val systemUIController = rememberSystemUIController {
+//            if (isLandscape)  {
+//                hideSystemUI()
+//            } else {
+//                showSystemUI()
+//            }
+//        }
+
         Box(
             modifier = Modifier.fillMaxSize(),
         ) {

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

@@ -53,7 +53,7 @@ class DetailScreen : Screen() {
             DetailViewModel.mock(context)
         }
 
-        val data: Any? = remember { args[data] }
+        val data: Any? = remember(args) { args[data] }
 
         val dataList = remember(data) {
             viewModel.mediaItemsFor(data)

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

@@ -37,6 +37,7 @@ class IPTVScreen : Screen() {
     private val data = "data"
 
     override val title = R.string.title_iptv
+
     override val menuIcon: ImageVector get() = Icons.Filled.Tv
     override val immersive: Boolean = true
     override val showOnce: Boolean = true

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

@@ -53,7 +53,7 @@ class SplashScreen : Screen() {
     override fun ComposeScreen() {
 
         val isEdit = isEditMode()
-        val scale = remember { Animatable(if (isEdit) 1f else 0f) }
+        val scale = remember (isEdit) { Animatable(if (isEdit) 1f else 0f) }
         val permissionManager = rememberPermissionManager()
         val composition = rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.splash))
         val progress = animateLottieCompositionAsState(composition.value)

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

@@ -46,7 +46,7 @@ class WebScreen : Screen() {
     @Composable
     override fun ComposeScreen() {
 
-        val data: Any? = remember { args[data] }
+        val data: Any? = remember(args) { args[data] }
 
         Box(
             modifier = Modifier

+ 11 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/activity/ComposableActivity.kt

@@ -37,6 +37,7 @@ import org.mjdev.tvlib.extensions.ContextExt.isATv
 import org.mjdev.tvlib.extensions.ModifierExt.detectSwipe
 import org.mjdev.tvlib.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvlib.extensions.NavGraphBuilderExt.screen
+import org.mjdev.tvlib.helpers.other.ConfigChangeCallback
 import org.mjdev.tvlib.navigation.NavGraphBuilderEx
 import org.mjdev.tvlib.navigation.NavHostControllerEx
 import org.mjdev.tvlib.ui.components.navigation.NavHostEx
@@ -51,6 +52,7 @@ open class ComposableActivity : ComponentActivity() {
     val ANR_TIMEOUT = 5000
 
     val activityResultListeners = mutableListOf<ActivityResultHandler<*>>()
+    val configChangeCallbacks = mutableListOf<ConfigChangeCallback>()
 
     open val navGraphBuilder: NavGraphBuilderEx.() -> Unit = {
         screen(route = EmptyScreen())
@@ -126,6 +128,9 @@ open class ComposableActivity : ComponentActivity() {
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         Timber.d("Config change")
+        configChangeCallbacks.forEach { cb ->
+            cb.invoke(newConfig)
+        }
     }
 
     open fun onIntent(navController: NavHostControllerEx, intent: Intent?) {
@@ -155,6 +160,12 @@ open class ComposableActivity : ComponentActivity() {
         }
     }
 
+    override fun onDestroy() {
+        super.onDestroy()
+        configChangeCallbacks.clear()
+        activityResultListeners.clear()
+    }
+
     fun <T> registerForActivityResult(
         onLaunch: (args: List<T>) -> Unit,
         onActivityResult: (requestCode: Int, resultCode: Int, intent: Intent?) -> Unit

+ 17 - 14
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/ComposeExt.kt

@@ -56,19 +56,21 @@ object ComposeExt {
     fun isEditMode() = LocalInspectionMode.current
 
     @Composable
-    fun isLandscapeMode(): Boolean =
-        LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
+    fun isLandscapeMode(): Boolean = with(LocalConfiguration.current) {
+        (orientation == Configuration.ORIENTATION_LANDSCAPE) || (screenWidthDp > screenHeightDp)
+    }
 
     @Composable
-    fun isPortraitMode(): Boolean =
-        LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
+    fun isPortraitMode(): Boolean = with(LocalConfiguration.current) {
+        (orientation == Configuration.ORIENTATION_PORTRAIT) || (screenHeightDp > screenWidthDp)
+    }
 
     @Composable
     fun isAtv(): Boolean = LocalContext.current.isATv
 
     // todo more types
     @Composable
-    fun Any?.asException() : Exception? = when (this) {
+    fun Any?.asException(): Exception? = when (this) {
         null -> null
         is String -> Exception(this)
         is Int -> Exception(stringResource(this))
@@ -85,8 +87,9 @@ object ComposeExt {
 
     @Composable
     fun <T> rememberDerivedStateOf(
-        function: () -> T
-    ): State<T> = remember {
+        function: () -> T,
+        key: Any? = function
+    ): State<T> = remember(key) {
         derivedStateOf(function)
     }
 
@@ -108,14 +111,16 @@ object ComposeExt {
     }
 
     @Composable
-    fun rememberMutableInteractionSource() = remember {
+    fun rememberMutableInteractionSource(
+        key: Any? = null
+    ) = remember(key) {
         MutableInteractionSource()
     }
 
     @Composable
     fun rememberFocusState(
         initial: FocusState? = null
-    ) = remember {
+    ) = remember(initial) {
         mutableStateOf(initial)
     }
 
@@ -158,11 +163,9 @@ object ComposeExt {
 
     @SuppressLint("RememberReturnType", "ComposableNaming")
     @Composable
-    fun rememberContentResolver(
-        key: Any? = Unit
-    ): ContentResolver {
+    fun rememberContentResolver(): ContentResolver {
         val context = LocalContext.current
-        return remember(key) {
+        return remember(context) {
             context.contentResolver
         }
     }
@@ -171,7 +174,7 @@ object ComposeExt {
     @Composable
     fun rememberImageLoader(): ImageLoader {
         val context = LocalContext.current
-        return remember {
+        return remember(context) {
             ImageLoader.Builder(context)
                 .allowHardware(false)
                 .addLastModifiedToFileCacheKey(true)

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/NavExt.kt

@@ -47,7 +47,7 @@ object NavExt {
     ): NavHostControllerEx {
         val context = LocalContext.current
         return rememberSaveable(
-            inputs = navigators,
+            inputs = arrayOf(context, navigators),
             saver = NavControllerSaver(context)
         ) {
             createNavController(context)

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/apps/AppsManager.kt

@@ -28,7 +28,7 @@ fun rememberAppsManager(
     vararg excluded: ComponentName
 ): Flow<List<App>> {
     val context: Context = LocalContext.current
-    return remember {
+    return remember(context) {
         appsManager(context, *excluded)
     }
 }

+ 10 - 2
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/cursor/CachingCursor.kt

@@ -360,11 +360,19 @@ open class CachingCursor<T>(
             if (uri == null) return null
             else {
                 val context = LocalContext.current
-                var cursor by remember {
+                var cursor by remember(
+                    uri,
+                    projection,
+                    selection,
+                    selectionArgs,
+                    sortOrder,
+                    transform,
+                    context
+                ) {
                     mutableStateOf<CachingCursor<RT>?>(null)
                 }
                 DisposableEffect(uri) {
-                    cursor = CachingCursor<RT>(
+                    cursor = CachingCursor(
                         context,
                         uri,
                         projection,

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/lifecycle/LifeCycle.kt

@@ -29,7 +29,7 @@ fun rememberLifeCycleHandler(
     lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
     onEvent: (Lifecycle.Event) -> Unit
 ) {
-    val lifecycleEvent = remember {
+    val lifecycleEvent = remember(lifecycleOwner) {
         mutableStateOf(Lifecycle.Event.ON_ANY)
     }
     DisposableEffect(lifecycleOwner) {

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/media/MetadataRetriever.kt

@@ -96,7 +96,7 @@ class MetadataRetriever(
                 String.format("%s %s", n, v)
             }
         ) = LocalContext.current.let { context ->
-            remember { MetadataRetriever(context, translateMeta) }
+            remember(context) { MetadataRetriever(context, translateMeta) }
         }
 
     }

+ 15 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/other/ConfigChangeCallback.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.tvlib.helpers.other
+
+import android.content.res.Configuration
+
+interface ConfigChangeCallback {
+    fun invoke(newConfig: Configuration)
+}

+ 162 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/other/SystemUIController.kt

@@ -0,0 +1,162 @@
+/*
+ *  Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvlib.helpers.other
+
+import android.annotation.SuppressLint
+import android.content.res.Configuration
+import android.view.View
+import android.view.WindowInsets.Type.navigationBars
+import android.view.WindowInsets.Type.statusBars
+import android.view.WindowInsetsController.BEHAVIOR_DEFAULT
+import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import org.mjdev.tvlib.activity.ComposableActivity
+
+@Suppress("DEPRECATION", "unused", "MemberVisibilityCanBePrivate")
+class SystemUIController(
+    val activity: ComposableActivity?,
+    val onChanges: SystemUIController.() -> Unit
+) : ConfigChangeCallback, DefaultLifecycleObserver {
+
+    val window
+        get() = runCatching {
+            activity?.window
+        }.getOrNull()
+
+    var configuration: Configuration? = runCatching {
+        activity?.resources?.configuration
+    }.getOrNull()
+
+    var systemUIVisible: Boolean = true
+
+    val isLandscape
+        get() = (configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) ||
+                ((configuration?.screenWidthDp ?: 0) > (configuration?.screenHeightDp ?: 0))
+
+
+    val isPortrait
+        get() = (configuration?.orientation == Configuration.ORIENTATION_PORTRAIT) ||
+                ((configuration?.screenHeightDp ?: 0) > (configuration?.screenWidthDp ?: 0))
+
+    init {
+        activity?.lifecycle?.addObserver(this)
+    }
+
+    fun register() {
+        activity?.configChangeCallbacks?.also {
+            it.remove(this)
+            it.add(this)
+        }
+    }
+
+    private fun unregister() {
+        activity?.configChangeCallbacks?.remove(this)
+    }
+
+    override fun onResume(owner: LifecycleOwner) {
+        super.onResume(owner)
+        register()
+    }
+
+    override fun onDestroy(owner: LifecycleOwner) {
+        super.onDestroy(owner)
+        unregister()
+        activity?.lifecycle?.removeObserver(this)
+    }
+
+    override fun invoke(newConfig: Configuration) {
+        this.configuration = newConfig
+        onChanges(this)
+    }
+
+    @SuppressLint("NewApi")
+    fun hideSystemUI() {
+        if (systemUIVisible) {
+            systemUIVisible = false
+
+            window?.setFlags(
+                WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                WindowManager.LayoutParams.FLAG_FULLSCREEN
+            )
+
+            try {
+                window?.decorView?.systemUiVisibility =
+                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+            } catch (t: Throwable) {
+                t.printStackTrace()
+            }
+
+            try {
+                window?.attributes?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+            } catch (t: Throwable) {
+                t.printStackTrace()
+            }
+
+            try {
+                window?.setDecorFitsSystemWindows(false)
+                window?.insetsController?.apply {
+                    hide(statusBars() or navigationBars())
+                    systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+                }
+            } catch (t: Throwable) {
+                t.printStackTrace()
+            }
+        }
+    }
+
+    @SuppressLint("InlinedApi")
+    fun showSystemUI() {
+        if (!systemUIVisible) {
+            systemUIVisible = true
+            window?.clearFlags(
+                WindowManager.LayoutParams.FLAG_FULLSCREEN
+            )
+
+            try {
+                window?.decorView?.systemUiVisibility =
+                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_VISIBLE
+            } catch (t: Throwable) {
+                t.printStackTrace()
+            }
+
+            try {
+                window?.attributes?.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+            } catch (t: Throwable) {
+                t.printStackTrace()
+            }
+
+            try {
+                window?.setDecorFitsSystemWindows(true)
+                window?.insetsController?.apply {
+                    show(statusBars() or navigationBars())
+                    systemBarsBehavior = BEHAVIOR_DEFAULT
+                }
+            } catch (t: Throwable) {
+                t.printStackTrace()
+            }
+        }
+    }
+
+//    companion object {
+//
+//        @Composable
+//        fun rememberSystemUIController(
+//            onChanges: SystemUIController.() -> Unit
+//        ): SystemUIController {
+//            val isEdit = isEditMode()
+//            val activity = if (isEdit) null else LocalContext.current as ComposableActivity
+//            return remember { SystemUIController(activity, onChanges) }
+//        }
+//
+//    }
+
+}

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/network/NetworkConnectivityService.kt

@@ -22,7 +22,7 @@ interface NetworkConnectivityService {
         @Composable
         fun rememberNetworkService(): NetworkConnectivityService {
             val context = LocalContext.current
-            return remember(Unit) {
+            return remember(context) {
                 NetworkConnectivityServiceImpl(context)
             }
         }

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/permission/PermissonManager.kt

@@ -29,7 +29,7 @@ import timber.log.Timber
 @Composable
 fun manifestPermissions(): List<String> {
     val context = LocalContext.current
-    return rememberSaveable {
+    return rememberSaveable (context) {
         try {
             val packageManager = context.packageManager
             arrayListOf<String>().apply {

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/button/Button.kt

@@ -57,7 +57,7 @@ fun Button(
     borderColor: Color = Color.White,
     glow: ButtonGlow = ButtonDefaults.NO_GLOW,
     scale: ButtonScale = ButtonDefaults.NO_SCALE,
-    focusState: MutableState<FocusState?> = rememberFocusState(text),
+    focusState: MutableState<FocusState?> = rememberFocusState(),
     content: @Composable RowScope.() -> Unit = {
         TextAny(text = text)
     },

+ 5 - 2
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/card/Card.kt

@@ -35,6 +35,7 @@ import androidx.tv.material3.CardShape
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Glow
 import org.mjdev.tvlib.annotations.Previews
+import org.mjdev.tvlib.extensions.ComposeExt.rememberMutableInteractionSource
 import org.mjdev.tvlib.extensions.ModifierExt.conditional
 import org.mjdev.tvlib.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvlib.extensions.ModifierExt.tvAspectRatio
@@ -58,7 +59,7 @@ fun Card(
         unFocusGlowColor
     ),
     aspectRatio: Float? = 16f / 9f,
-    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    interactionSource: MutableInteractionSource = rememberMutableInteractionSource(),
     content: @Composable ColumnScope.() -> Unit = {}
 ) = androidx.tv.material3.Card(
     onClick = onClick,
@@ -156,7 +157,9 @@ fun CardDefaults.colorFocusBorder(
 
 @Suppress("unused")
 @Composable
-fun rainbowColorsBrush() = remember {
+fun rainbowColorsBrush(
+    key:Any?=null
+) = remember(key) {
     Brush.sweepGradient(
         listOf(
             Color(0xFF9575CD),

+ 4 - 4
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/card/PreviewCard.kt

@@ -73,12 +73,12 @@ fun PreviewCard(
         modifier = modifier.tvAspectRatio(aspectRatio).recomposeHighlighter()
     ) {
         val context = LocalContext.current
-        var currentlyPlaying by remember { mutableStateOf(false) }
-        var isBuffering by remember { mutableStateOf(true) }
-        var thumbnailBitmap by rememberSaveable {
+        var currentlyPlaying by remember(videoUrl) { mutableStateOf(false) }
+        var isBuffering by remember(videoUrl) { mutableStateOf(true) }
+        var thumbnailBitmap by rememberSaveable(videoUrl) {
             mutableStateOf<Bitmap?>(null)
         }
-        val exoPlayer = remember {
+        val exoPlayer = remember(videoUrl) {
             ExoPlayer.Builder(context).build().apply {
                 repeatMode = REPEAT_MODE_ONE
                 volume = 0f

+ 10 - 8
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/carousel/BigCarousel.kt

@@ -38,16 +38,16 @@ fun BigCarousel(
     modifier: Modifier = Modifier,
     items: List<Any?> = listOf(Unit, Unit, Unit),
     autoScrollDurationMillis: Long = 8000,
-    carouselState: CarouselState = remember { CarouselState() },
-    onItemSelected: (movie: Any?) -> Unit = {},
-    onItemClicked: (movie: Any?) -> Unit = {},
+    carouselState: CarouselState = remember(items) { CarouselState() },
+    onItemSelected: ((movie: Any?) -> Unit)? = null,
+    onItemClicked: ((movie: Any?) -> Unit)? = null,
 ) {
     val height = LocalConfiguration.current.let { config ->
         if (config.orientation == Configuration.ORIENTATION_PORTRAIT) config.screenWidthDp * 0.4f
         else config.screenHeightDp * 0.4f
     }
-    val focusRequester = rememberFocusRequester()
-    val cardFocusState = rememberFocusState()
+    val focusRequester = rememberFocusRequester(items)
+    val cardFocusState = rememberFocusState(items)
     Carousel(
         autoScrollDurationMillis = autoScrollDurationMillis,
         carouselState = carouselState,
@@ -76,7 +76,7 @@ fun BigCarousel(
             },
     ) { indexOfCarouselItem ->
         val selectedItem = items[indexOfCarouselItem]
-        onItemSelected(selectedItem)
+        onItemSelected?.invoke(selectedItem)
         CarouselCard(
             item = selectedItem,
             modifier = modifier
@@ -85,9 +85,11 @@ fun BigCarousel(
             contentScale = ContentScale.Crop,
             scale = CardScale.None,
             focusState = cardFocusState,
-            onFocus = onItemSelected,
+            onFocus = {
+                onItemSelected?.invoke(it)
+            },
             onClick = {
-                onItemClicked(selectedItem)
+                onItemClicked?.invoke(selectedItem)
             }
         )
     }

+ 4 - 4
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/carousel/Carousel.kt

@@ -51,9 +51,9 @@ import org.mjdev.tvlib.ui.components.carousel.ModifierExt.handleKeyEvents
 @Previews
 @Composable
 fun Carousel(
-    itemCount: Int=0,
+    itemCount: Int = 0,
     modifier: Modifier = Modifier,
-    carouselState: CarouselState = remember { CarouselState() },
+    carouselState: CarouselState = remember(itemCount) { CarouselState() },
     autoScrollDurationMillis: Long = CarouselDefaults.TimeToDisplayItemMillis,
     contentTransformStartToEnd: ContentTransform = CarouselDefaults.contentTransform,
     contentTransformEndToStart: ContentTransform = CarouselDefaults.contentTransform,
@@ -66,8 +66,8 @@ fun Carousel(
                 .padding(16.dp),
         )
     },
-    isAutoScrollActive: MutableState<Boolean> = remember { mutableStateOf(false) },
-    focusState: MutableState<FocusState?> = remember { mutableStateOf(null) },
+    isAutoScrollActive: MutableState<Boolean> = remember(itemCount) { mutableStateOf(false) },
+    focusState: MutableState<FocusState?> = remember(itemCount) { mutableStateOf(null) },
     content: @Composable AnimatedContentScope.(index: Int) -> Unit = {}
 ) {
 

+ 0 - 10
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/complex/VerticalScrollableBox.kt

@@ -12,7 +12,6 @@ import android.annotation.SuppressLint
 import androidx.compose.foundation.gestures.detectDragGestures
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
-import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableFloatStateOf
@@ -20,14 +19,10 @@ 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.unit.dp
 import androidx.tv.foundation.lazy.list.TvLazyListState
 import org.mjdev.tvlib.annotations.Previews
-import org.mjdev.tvlib.extensions.ComposeExt.isEditMode
 import org.mjdev.tvlib.extensions.ComposeExt.isLandscapeMode
-import org.mjdev.tvlib.extensions.ModifierExt.conditional
 import org.mjdev.tvlib.extensions.ModifierExt.recomposeHighlighter
-import org.mjdev.tvlib.extensions.ModifierExt.tvAspectRatio
 
 @SuppressLint("AutoboxingStateValueProperty")
 @Previews
@@ -39,14 +34,9 @@ fun VerticalScrollableBox(
     state: TvLazyListState? = null,
     content: @Composable BoxScope.() -> Unit = {}
 ) {
-    val isEdit = isEditMode()
     val scrollDelta = remember { mutableFloatStateOf(0f) }
     Box(
         modifier = modifier
-            .conditional(isEdit) {
-                defaultMinSize(80.dp)
-                    .tvAspectRatio(16f / 9f).defaultMinSize(80.dp)
-            }
             .pointerInput(Unit) {
                 detectDragGestures { _, dragAmount ->
                     scrollDelta.value = dragAmount.y

+ 1 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/ExoPlayerImpl.kt

@@ -267,6 +267,7 @@ class ExoPlayerImpl(
         val height = LocalConfiguration.current.screenHeightDp
         val isEdit = isEditMode()
         val lifecycleObserver = rememberPlayPauseLifeCycleObserver(
+            context,
             ::onPause,
             ::onResume
         )

+ 2 - 2
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerContainer.kt

@@ -37,8 +37,8 @@ fun MediaPlayerContainer(
         )
         PlayerControlView(
             modifier = modifier
-                .recomposeHighlighter()
-                .fillMaxSize(),
+                .fillMaxSize()
+                .recomposeHighlighter(),
             state = state
         )
     }

+ 4 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerContainerDefaults.kt

@@ -14,6 +14,10 @@ import android.content.Context
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 object MediaPlayerContainerDefaults {
 
+    @Suppress("UNUSED_PARAMETER")
+    fun empty(context: Context): IMediaPlayer =
+        IMediaPlayer.EMPTY
+
     fun exoPlayer(context: Context): IMediaPlayer =
         ExoPlayerImpl(context)
 

+ 6 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerState.kt

@@ -24,6 +24,7 @@ import com.squareup.moshi.JsonClass
 import com.squareup.moshi.Moshi
 import com.squareup.moshi.ToJson
 import org.json.JSONObject
+import org.mjdev.tvlib.extensions.ComposeExt.isEditMode
 import org.mjdev.tvlib.extensions.MediaItemExt.mediaItem
 import org.mjdev.tvlib.extensions.MediaItemExt.uri
 import timber.log.Timber
@@ -180,6 +181,7 @@ class MediaPlayerState(
             startSeek: Long? = null,
             context: Context = LocalContext.current,
             onError: (e: Exception) -> Boolean = { false },
+            isEdit: Boolean = isEditMode()
         ) = rememberSaveable(
             key = MediaPlayerState::class.simpleName,
             inputs = arrayOf(items, itemToPlay),
@@ -193,7 +195,10 @@ class MediaPlayerState(
                 playNextOnError,
                 onError
             ).apply {
-                player = MediaPlayerContainerDefaults.exoPlayer(context)
+                player = when (isEdit) {
+                    true -> MediaPlayerContainerDefaults.empty(context)
+                    false -> MediaPlayerContainerDefaults.exoPlayer(context)
+                }
             }
         }
 

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/navigation/NavigationRow.kt

@@ -79,7 +79,7 @@ fun NavigationRow(
         text,
         FocusHelper(focused)
     ),
-    focusRequester: FocusRequester = rememberFocusRequester(),
+    focusRequester: FocusRequester = rememberFocusRequester(id),
     onFocus: (id: Int) -> Unit = {},
     onClick: (id: Int) -> Unit = {},
 ) {

+ 6 - 3
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/page/Page.kt

@@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.sp
 import org.mjdev.tvlib.annotations.Previews
 import org.mjdev.tvlib.extensions.ComposeExt.rememberFocusRequester
@@ -49,18 +50,18 @@ open class Page {
         ) {
             VerticalScrollableBox(
                 modifier = Modifier
-                    .recomposeHighlighter()
                     .fillMaxSize()
                     .requestFocusOnTouch(
                         focusRequester = focusRequester
-                    ),
+                    )
+                    .recomposeHighlighter(),
                 contentAlignment = Alignment.Center,
             ) {
                 Box(
                     modifier = Modifier
-                        .recomposeHighlighter()
                         .fillMaxSize()
                         .focusRequester(focusRequester)
+                        .recomposeHighlighter()
                 ) {
                     Content()
                 }
@@ -78,7 +79,9 @@ open class Page {
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             TextAny(
+                modifier = Modifier.fillMaxSize().recomposeHighlighter(),
                 text = title ?: "Empty Page",
+                textAlign = TextAlign.Center,
                 fontSize = 32.sp,
                 fontWeight = FontWeight.Bold,
                 color = Color.White,

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/tv/Tabs.kt

@@ -50,7 +50,7 @@ fun Tabs(
     activeContentColor: Color = Color.Green,
     selectedContentColor: Color = Color.White,
     focusedContentColor: Color = Color.White,
-    interactionSource: MutableInteractionSource = rememberMutableInteractionSource(),
+    interactionSource: MutableInteractionSource = rememberMutableInteractionSource(items),
     onItemClick: (item: Any?) -> Unit = {}
 ) {
     val selectedTabIndex = remember { mutableIntStateOf(0) }

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/updater/AppUpdater.kt

@@ -205,7 +205,7 @@ class AppUpdater(
             githubRepository: String = "tvapp",
         ): AppUpdater {
             val context = LocalContext.current
-            return remember(Unit) {
+            return remember(context) {
                 AppUpdater(context, githubUser, githubRepository)
             }
         }