Milan Jurkulak 3 лет назад
Родитель
Сommit
b9be6da065
43 измененных файлов с 266 добавлено и 120 удалено
  1. 46 28
      app/build.gradle.kts
  2. 2 0
      app/src/main/kotlin/org/mjdev/tvapp/base/activity/ComposableActivity.kt
  3. 80 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ModifierExt.kt
  4. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/badge/Badge.kt
  5. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/button/Button.kt
  6. 2 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/Card.kt
  7. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/CardContent.kt
  8. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/ItemCard.kt
  9. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PhotoCard.kt
  10. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PreviewCard.kt
  11. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/carousel/BigCarousel.kt
  12. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/carousel/CarouselCard.kt
  13. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableBox.kt
  14. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableCard.kt
  15. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/VerticalScrollableBox.kt
  16. 11 10
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/icon/IconAny.kt
  17. 3 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/CircleImage.kt
  18. 11 10
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/ImageAny.kt
  19. 3 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/PhotoImage.kt
  20. 4 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerContainer.kt
  21. 5 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/media/MediaPlayerImpl.kt
  22. 4 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavDrawerContent.kt
  23. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavHostEx.kt
  24. 4 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/Navigation.kt
  25. 5 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/navigation/NavigationRow.kt
  26. 5 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/Page.kt
  27. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/TvPager.kt
  28. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/screen/ScreenView.kt
  29. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/text/AutoHideEmptyText.kt
  30. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/text/CircleText.kt
  31. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/text/TextAny.kt
  32. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/text/TextWithShadow.kt
  33. 3 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/BrowseView.kt
  34. 4 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/CategoryRow.kt
  35. 6 5
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Clock.kt
  36. 6 5
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/ErrorMessage.kt
  37. 8 7
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Header.kt
  38. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/PagingTvLazyRow.kt
  39. 3 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/ScrollableTvLazyRow.kt
  40. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/TVRow.kt
  41. 4 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Tabs.kt
  42. 5 4
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Title.kt
  43. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/UserPic.kt

+ 46 - 28
app/build.gradle.kts

@@ -61,9 +61,6 @@ android {
         versionName = project.versionName
 
         signingConfig = signingConfigs[SIGNING_CONFIG_NAME]
-
-        buildConfigField("String", "SYNC_AUTH", "\"$applicationId.sync\"")
-        resValue("string", "sync_auth", "$applicationId.sync")
     }
 
     buildTypes {
@@ -76,6 +73,17 @@ android {
                 getDefaultProguardFile("proguard-android-optimize.txt"),
                 "proguard-rules.pro"
             )
+            buildConfigField("Boolean", "DEBUG_RECOMPOSE", "false")
+            buildConfigField(
+                "String",
+                "SYNC_AUTH",
+                "\"${defaultConfig.applicationId}${applicationIdSuffix}.sync\""
+            )
+            resValue(
+                "string",
+                "sync_auth",
+                "${defaultConfig.applicationId}${applicationIdSuffix}.sync"
+            )
         }
 
         release {
@@ -86,6 +94,38 @@ android {
                 getDefaultProguardFile("proguard-android-optimize.txt"),
                 "proguard-rules.pro"
             )
+            buildConfigField("Boolean", "DEBUG_RECOMPOSE", "false")
+            buildConfigField(
+                "String",
+                "SYNC_AUTH",
+                "\"${defaultConfig.applicationId}${applicationIdSuffix}.sync\""
+            )
+            resValue(
+                "string",
+                "sync_auth",
+                "${defaultConfig.applicationId}${applicationIdSuffix}.sync"
+            )
+        }
+
+        create("recomposing") {
+            applicationIdSuffix = ".rcmps"
+            isDebuggable = true
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+            buildConfigField("Boolean", "DEBUG_RECOMPOSE", "true")
+            buildConfigField(
+                "String",
+                "SYNC_AUTH",
+                "\"${defaultConfig.applicationId}${applicationIdSuffix}.sync\""
+            )
+            resValue(
+                "string",
+                "sync_auth",
+                "${defaultConfig.applicationId}${applicationIdSuffix}.sync"
+            )
         }
 
     }
@@ -124,25 +164,6 @@ android {
         outputDirectory.set(File(projectDir, "../wiki/documentation"))
     }
 
-//    tasks.register("prepareReleaseNotes") {
-//        doLast {
-//            exec {
-//                workingDir(rootDir)
-//                args(getVersionName())
-//                executable("./scripts/git_log.sh")
-//            }
-//        }
-//    }
-//
-//    afterEvaluate {
-//        // all versions release notes
-//        tasks.findByName("assembleDebug")?.finalizedBy("prepareReleaseNotes")
-//        tasks.findByName("assembleRelease")?.finalizedBy("prepareReleaseNotes")
-//
-//        // dokka documentation generation
-//        tasks.findByName("prepareReleaseNotes")?.finalizedBy("dokkaGfm")
-//    }
-
 }
 
 dependencies {
@@ -194,7 +215,6 @@ dependencies {
     implementation("androidx.hilt:hilt-navigation-compose:1.1.0-alpha01")
     kapt("com.google.dagger:hilt-compiler:2.46.1")
     // okhttp
-    // define a BOM and its version
     implementation(platform("com.squareup.okhttp3:okhttp-bom:4.11.0"))
     implementation("com.squareup.okhttp3:okhttp")
     implementation("com.squareup.okhttp3:logging-interceptor")
@@ -206,9 +226,7 @@ dependencies {
     implementation("com.squareup.retrofit2:retrofit:2.9.0")
     implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
     implementation("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2")
-    // database
-
-    // db encrypt data
+    // encrypt data
     implementation("com.scottyab:aescrypt:0.0.1")
     // exoplayer
     implementation("androidx.media3:media3-exoplayer:1.1.0")
@@ -230,8 +248,8 @@ dependencies {
     implementation("androidx.media3:media3-decoder:1.1.0")
     implementation("androidx.media3:media3-datasource:1.1.0")
     implementation("androidx.media3:media3-common:1.1.0")
-//    implementation ("com.rubensousa.dpadrecyclerview:dpadrecyclerview:1.1.0-alpha02")
-//    implementation ("com.rubensousa.dpadrecyclerview:dpadrecyclerview-compose:1.1.0-alpha02")
+    // permission
+    implementation("com.google.accompanist:accompanist-permissions:0.31.5-beta")
 
     constraints {
         implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0")?.apply {

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

@@ -58,6 +58,8 @@ open class ComposableActivity : ComponentActivity() {
     @CallSuper
     open fun Compose() {
 
+//        val permissionState = rememberPermissionState(permission = Manifest.permission.READ_SMS)
+
         val navController = rememberNavControllerEx()
 
         TVAppTheme {

+ 80 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ModifierExt.kt

@@ -8,16 +8,35 @@
 
 package org.mjdev.tvapp.base.extensions
 
+import android.annotation.SuppressLint
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.mutableLongStateOf
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.drawWithCache
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.FocusState
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.Fill
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.delay
 import timber.log.Timber
+import java.lang.Float.min
+import org.mjdev.tvapp.BuildConfig
 
 @Suppress("MemberVisibilityCanBePrivate")
 object ModifierExt {
@@ -64,4 +83,65 @@ object ModifierExt {
         }
     }
 
+    @Stable
+    fun Modifier.recomposeHighlighter(): Modifier = conditional(
+        BuildConfig.DEBUG_RECOMPOSE
+    ) { this.then(recomposeModifier) }
+
+    @SuppressLint("AutoboxingStateValueProperty")
+    private val recomposeModifier = Modifier.composed(
+        inspectorInfo = debugInspectorInfo {
+            name = "recomposeHighlighter" }) {
+            val totalCompositions = remember { arrayOf(0L) }
+            totalCompositions[0]++
+            val totalCompositionsAtLastTimeout = remember { mutableLongStateOf(0L) }
+            LaunchedEffect(totalCompositions[0]) {
+                delay(3000)
+                totalCompositionsAtLastTimeout.value = totalCompositions[0]
+            }
+            Modifier.drawWithCache {
+                onDrawWithContent {
+                    drawContent()
+                    val numCompositionsSinceTimeout =
+                        totalCompositions[0] - totalCompositionsAtLastTimeout.value
+                    val hasValidBorderParams = size.minDimension > 0f
+                    if (!hasValidBorderParams || numCompositionsSinceTimeout <= 0) {
+                        return@onDrawWithContent
+                    }
+                    val (color, strokeWidthPx) =
+                        when (numCompositionsSinceTimeout) {
+                            1L -> Color.Blue to 1f
+                            2L -> Color.Green to 2.dp.toPx()
+                            else -> {
+                                lerp(
+                                    Color.Yellow.copy(alpha = 0.8f),
+                                    Color.Red.copy(alpha = 0.5f),
+                                    min(
+                                        1f,
+                                        (numCompositionsSinceTimeout - 1).toFloat() / 100f
+                                    )
+                                ) to numCompositionsSinceTimeout.toInt().dp.toPx()
+                            }
+                        }
+
+                    val halfStroke = strokeWidthPx / 2
+                    val topLeft = Offset(halfStroke, halfStroke)
+                    val borderSize = Size(
+                        size.width - strokeWidthPx,
+                        size.height - strokeWidthPx
+                    )
+                    val fillArea = (strokeWidthPx * 2) > size.minDimension
+                    val rectTopLeft = if (fillArea) Offset.Zero else topLeft
+                    val size = if (fillArea) size else borderSize
+                    val style = if (fillArea) Fill else Stroke(strokeWidthPx)
+                    drawRect(
+                        brush = SolidColor(color),
+                        topLeft = rectTopLeft,
+                        size = size,
+                        style = style
+                    )
+                }
+            }
+        }
+
 }

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

@@ -19,6 +19,7 @@ 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 org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.text.CircleText
 
@@ -37,7 +38,7 @@ fun Badge(
     onClick: () -> Unit = {}
 ) {
     FocusableBox(
-        modifier = modifier.clip(CircleShape),
+        modifier = modifier.clip(CircleShape).recomposeHighlighter(),
         shape = shape,
         onClick = onClick
     ) {

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

@@ -33,6 +33,7 @@ import androidx.tv.material3.ButtonScale
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Glow
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
 
@@ -63,7 +64,7 @@ fun Button(
 ) {
 
     Button(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
 //            .touchable(
 //                focusState = focusState,
 //            )

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

@@ -35,6 +35,7 @@ import androidx.tv.material3.CardScale
 import androidx.tv.material3.CardShape
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Glow
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.ModifierExt.tvAspectRatio
 
 @SuppressLint("ModifierParameter")
@@ -61,6 +62,7 @@ fun Card(
 ) = androidx.tv.material3.Card(
     onClick = onClick,
     modifier = modifier
+        .recomposeHighlighter()
         .widthIn(max = 320.dp)
         .tvAspectRatio(aspectRatio)
         .clickable {

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

@@ -16,6 +16,7 @@ import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.tv.material3.MaterialTheme
 import androidx.tv.material3.ProvideTextStyle
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.text.AutoHideEmptyText
 
 @Preview
@@ -43,7 +44,7 @@ fun CardContent(
     },
 ) {
     Column(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
     ) {
         ProvideTextStyle(MaterialTheme.typography.titleMedium) {
             title.invoke()

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

@@ -27,6 +27,7 @@ import androidx.tv.material3.CardShape
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import org.mjdev.tvapp.base.extensions.ComposeExt.computeCardHeight
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.interfaces.ItemWithDescription
 import org.mjdev.tvapp.base.interfaces.ItemWithImage
 import org.mjdev.tvapp.base.ui.components.complex.FocusableCard
@@ -60,7 +61,7 @@ fun ItemCard(
     focusState: MutableState<FocusState?> = rememberFocusState(item),
     onClick: (item: Any?) -> Unit = {},
 ) = FocusableCard(
-    modifier = modifier,
+    modifier = modifier.recomposeHighlighter(),
     item = item,
     focusState = focusState,
     contentScale = contentScale,

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

@@ -22,6 +22,7 @@ import androidx.tv.material3.CardScale
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import org.mjdev.tvapp.base.extensions.ComposeExt.computeCardHeight
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.interfaces.ItemWithDescription
 import org.mjdev.tvapp.base.interfaces.ItemWithImage
 import org.mjdev.tvapp.base.ui.components.image.PhotoImage
@@ -51,7 +52,7 @@ fun PhotoCard(
 ) {
     ItemCard(
         item = item,
-        modifier = modifier,
+        modifier = modifier.recomposeHighlighter(),
         contentScale = contentScale,
         focusState = focusState,
         aspectRatio = aspectRatio,

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

@@ -50,6 +50,7 @@ import coil.compose.AsyncImage
 import coil.compose.rememberAsyncImagePainter
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.ModifierExt.tvAspectRatio
 
 @OptIn(ExperimentalTvMaterial3Api::class)
@@ -68,7 +69,7 @@ fun PreviewCard(
     thumbnailUrl: String? = null
 ) {
     Card(
-        modifier = modifier.tvAspectRatio(aspectRatio)
+        modifier = modifier.tvAspectRatio(aspectRatio).recomposeHighlighter()
     ) {
         val context = LocalContext.current
         var currentlyPlaying by remember { mutableStateOf(false) }

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

@@ -28,6 +28,7 @@ import androidx.tv.material3.CardScale
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 
 // todo swipe left and swipe right
 @SuppressLint("AutoboxingStateValueProperty")
@@ -50,7 +51,7 @@ fun BigCarousel(
     BoxWithConstraints {
         Carousel(
             itemCount = items.size,
-            modifier = modifier
+            modifier = modifier.recomposeHighlighter()
                 .fillMaxWidth()
                 .height(height.dp),
         ) { indexOfCarouselItem ->

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

@@ -23,6 +23,7 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.interfaces.ItemWithBackground
 import org.mjdev.tvapp.base.interfaces.ItemWithDescription
 import org.mjdev.tvapp.base.ui.components.card.PhotoCard
@@ -55,7 +56,7 @@ fun CarouselCard(
         item = item,
         modifier = modifier.conditional(isEdit) {
             defaultMinSize(260.dp)
-        },
+        }.recomposeHighlighter(),
         aspectRatio = 1f,
         contentScale = contentScale,
         scale = scale,

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

@@ -30,6 +30,7 @@ import org.mjdev.tvapp.base.extensions.ComposeExt.isFocused
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusRequester
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ModifierExt.focusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.ModifierExt.requestFocusOnTouch
 
 @OptIn(ExperimentalTvMaterial3Api::class)
@@ -55,7 +56,7 @@ fun FocusableBox(
 ) {
     Surface(
         onClick = onClick,
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .focusState(focusState)
             .onFocusChanged { state -> onFocusChange(state) }
             .requestFocusOnTouch(focusRequester) {

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

@@ -40,6 +40,7 @@ import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusRequester
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
 import org.mjdev.tvapp.base.extensions.ModifierExt.focusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.ModifierExt.requestFocusOnTouch
 import org.mjdev.tvapp.base.interfaces.ItemWithDescription
 import org.mjdev.tvapp.base.interfaces.ItemWithImage
@@ -87,7 +88,7 @@ fun FocusableCard(
         colors = colors,
         border = border,
         glow = glow,
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .height(cardHeight)
             .width(cardHeight * aspectRatio)
             .focusState(focusState)

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

@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp
 import androidx.tv.foundation.lazy.list.TvLazyListState
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.ModifierExt.tvAspectRatio
 
 @SuppressLint("AutoboxingStateValueProperty")
@@ -40,7 +41,7 @@ fun VerticalScrollableBox(
     val isEdit = isEditMode()
     val scrollDelta = remember { mutableFloatStateOf(0f) }
     Box(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .conditional(isEdit) {
                 tvAspectRatio(16f / 9f).defaultMinSize(80.dp)
             }

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

@@ -30,6 +30,7 @@ import androidx.tv.material3.Icon
 import androidx.tv.material3.LocalContentColor
 import coil.compose.AsyncImage
 import org.mjdev.tvapp.base.extensions.DrawableExt.asImageBitmap
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import java.net.URL
 
 @OptIn(ExperimentalTvMaterial3Api::class)
@@ -52,70 +53,70 @@ fun IconAny(
         null -> Icon(
             ColorDrawable(0).asImageBitmap(width, height),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             tint
         )
 
         is Bitmap -> Icon(
             src.asImageBitmap(),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             tint
         )
 
         is ImageBitmap -> Icon(
             src,
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             tint
         )
 
         is Drawable -> Icon(
             src.asImageBitmap(width, height),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             tint
         )
 
         is Int -> Icon(
             painterResource(src),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             tint
         )
 
         is Color -> Icon(
             ColorDrawable(0).asImageBitmap(width, height),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             tint
         )
 
         is URL -> AsyncImage(
             model = src,
             contentDescription = contentDescription,
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             colorFilter = ColorFilter.tint(tint)
         )
 
         is Uri -> AsyncImage(
             model = src,
             contentDescription = contentDescription,
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             colorFilter = ColorFilter.tint(tint)
         )
 
         is String -> AsyncImage(
             model = src,
             contentDescription = contentDescription,
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             colorFilter = ColorFilter.tint(tint)
         )
 
         is ImageVector -> Icon(
             src,
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             tint
         )
 

+ 3 - 2
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/CircleImage.kt

@@ -25,6 +25,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import org.mjdev.tvapp.R
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 
 @SuppressLint("ModifierParameter")
 @Preview
@@ -39,12 +40,12 @@ fun CircleImage(
     src: Any? = R.drawable.milanj
 ) {
     Box(
-        modifier = modifier.padding(contentPadding),
+        modifier = modifier.padding(contentPadding).recomposeHighlighter(),
         contentAlignment = Alignment.Center,
     ) {
 
         ImageAny(
-            modifier = modifier
+            modifier = modifier.recomposeHighlighter()
                 .clip(CircleShape)
                 .background(backGroundColor, CircleShape)
                 .border(

+ 11 - 10
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/ImageAny.kt

@@ -31,6 +31,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import coil.compose.AsyncImage
 import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.extensions.DrawableExt.asImageBitmap
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import java.net.URL
 
 // todo placeholder
@@ -60,7 +61,7 @@ fun ImageAny(
         Color -> Image(
             ColorDrawable((src as Color).toArgb()).asImageBitmap(width, height),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             alignment,
             contentScale,
             alpha,
@@ -70,7 +71,7 @@ fun ImageAny(
         is Bitmap -> Image(
             src.asImageBitmap(),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             alignment,
             contentScale,
             alpha,
@@ -80,7 +81,7 @@ fun ImageAny(
         is ImageBitmap -> Image(
             src,
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             alignment,
             contentScale,
             alpha,
@@ -90,7 +91,7 @@ fun ImageAny(
         is ColorDrawable -> Image(
             src.asImageBitmap(1, 1),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             alignment,
             contentScale,
             alpha,
@@ -100,7 +101,7 @@ fun ImageAny(
         is Drawable -> Image(
             src.asImageBitmap(),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             alignment,
             contentScale,
             alpha,
@@ -110,7 +111,7 @@ fun ImageAny(
         is Int -> Image(
             painterResource(src),
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             alignment,
             contentScale,
             alpha,
@@ -118,7 +119,7 @@ fun ImageAny(
         )
 
         is URL -> AsyncImage(
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             model = src,
             contentDescription = contentDescription,
             alignment = alignment,
@@ -128,7 +129,7 @@ fun ImageAny(
         )
 
         is Uri -> AsyncImage(
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             model = src,
             contentDescription = contentDescription,
             alignment = alignment,
@@ -138,7 +139,7 @@ fun ImageAny(
         )
 
         is String -> AsyncImage(
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             model = src,
             contentDescription = contentDescription,
             alignment = alignment,
@@ -150,7 +151,7 @@ fun ImageAny(
         is ImageVector -> Image(
             src,
             contentDescription,
-            modifier,
+            modifier.recomposeHighlighter(),
             alignment,
             contentScale,
             alpha,

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

@@ -37,6 +37,7 @@ import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.DrawableExt.asPhoto
 import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 
 @SuppressLint("ModifierParameter")
 @Preview
@@ -68,7 +69,7 @@ fun PhotoImage(
                 .build()
         },
         imageModel = { src },
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .conditional(isEdit) {
                 aspectRatio(16f / 9f).defaultMinSize(80.dp)
             }
@@ -94,7 +95,7 @@ fun PhotoImage(
                     ?.asAndroidBitmap()
                     ?.toDrawable(context.resources)
                     ?.asPhoto(),
-                modifier = modifier,
+                modifier = modifier.recomposeHighlighter(),
                 alignment = alignment,
                 contentScale = contentScale,
                 alpha = alpha,

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

@@ -16,6 +16,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.Modifier
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.media.MediaPlayerState.Companion.rememberMediaPlayerState
 
 @TvPreview
@@ -31,7 +32,9 @@ fun MediaPlayerContainer(
     mediaPlayerControls: @Composable (state: MediaPlayerState) -> Unit = {}
 ) {
     val state = rememberMediaPlayerState(uri ?: Uri.EMPTY, autoPlay, startSeek)
-    Box(modifier = modifier) {
+    Box(
+        modifier = modifier.recomposeHighlighter()
+    ) {
         background(state)
         mediaPlayer.GetPlayerView()
         mediaPlayerOverlay(state)

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

@@ -25,6 +25,7 @@ import androidx.media3.exoplayer.ExoPlayer
 import androidx.media3.ui.PlayerView
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ContextExt.isATv
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 
 class ExoPlayerImpl(
     private val exoPlayer: ExoPlayer
@@ -38,12 +39,15 @@ class ExoPlayerImpl(
         val isEdit = isEditMode()
         Box(
             modifier = Modifier
+                .recomposeHighlighter()
                 .size(width.dp, height.dp)
                 .background(Color.Black, RectangleShape)
         ) {
             if (!isEdit) {
                 AndroidView(
-                    modifier = Modifier.size(width.dp, height.dp),
+                    modifier = Modifier
+                        .size(width.dp, height.dp)
+                        .recomposeHighlighter(),
                     factory = { context ->
                         val isAtv = context.isATv
                         PlayerView(context).apply {

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

@@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.tv.material3.ExperimentalTvMaterial3Api
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.NavControllerExt.open
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.navigation.MenuItem
@@ -68,7 +69,7 @@ fun NavDrawerContent(
         navController.indexOfMenuItem(menuItem)
     }
     Column(
-        modifier = Modifier
+        modifier = Modifier.recomposeHighlighter()
             .fillMaxHeight()
             .background(backgroundColor),
         verticalArrangement = Arrangement.Bottom,
@@ -94,7 +95,7 @@ fun NavDrawerContent(
         }
     }
     Column(
-        modifier = Modifier
+        modifier = Modifier.recomposeHighlighter()
             .fillMaxHeight(),
         verticalArrangement = Arrangement.Top,
         horizontalAlignment = Alignment.Start,
@@ -119,7 +120,7 @@ fun NavDrawerContent(
         }
     }
     Column(
-        modifier = Modifier
+        modifier = Modifier.recomposeHighlighter()
             .fillMaxHeight(),
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.Start,

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

@@ -21,6 +21,7 @@ import androidx.compose.ui.Modifier
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.compose.NavHost
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.NavExt.createGraph
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.extensions.NavGraphBuilderExt.screen
@@ -55,7 +56,7 @@ fun NavHostEx(
         navController = navController,
     ) {
         NavHost(
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             navController = navController,
             graph = remember(route, builder) {
                 navController.createGraph(

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

@@ -25,6 +25,7 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.NavigationDrawer
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.navigation.MenuItem
 import org.mjdev.tvapp.base.navigation.NavHostControllerEx
@@ -50,7 +51,7 @@ fun Navigation(
     if (navController.isMenuEnabled) {
         SettingsDrawer(
             drawerState = navController.settingsDrawerState,
-            modifier = Modifier.fillMaxHeight()
+            modifier = Modifier.fillMaxHeight().recomposeHighlighter()
         ) {
             NavigationDrawer(
                 modifier = modifier
@@ -60,7 +61,7 @@ fun Navigation(
                 drawerState = navController.menuDrawerState,
                 content = {
                     Box(
-                        modifier.fillMaxSize()
+                        modifier.fillMaxSize().recomposeHighlighter()
                     ) {
                         content()
                     }
@@ -76,7 +77,7 @@ fun Navigation(
         }
     } else {
         Box(
-            modifier.fillMaxHeight()
+            modifier.fillMaxHeight().recomposeHighlighter()
         ) {
             content()
         }

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

@@ -38,6 +38,7 @@ import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ComposeExt.isFocused
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ModifierExt.focusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 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
@@ -77,7 +78,7 @@ fun NavigationRow(
 ) {
     val isEdit = isEditMode()
     FocusableBox(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .padding(margin)
             .border(
                 BorderStroke(
@@ -101,14 +102,14 @@ fun NavigationRow(
         },
     ) {
         Row(
-            modifier = Modifier
+            modifier = Modifier.recomposeHighlighter()
                 .padding(padding),
 //                .background(brush!!, shape),
             horizontalArrangement = Arrangement.Start,
             verticalAlignment = Alignment.CenterVertically,
         ) {
             Box(
-                modifier = Modifier.padding(4.dp)
+                modifier = Modifier.padding(4.dp).recomposeHighlighter()
             ) {
                 IconAny(
                     src = icon,
@@ -122,7 +123,7 @@ fun NavigationRow(
                 TextAny(
                     text = text,
                     softWrap = false,
-                    modifier = Modifier
+                    modifier = Modifier.recomposeHighlighter()
                         .padding(8.dp)
                         .width(expandedWidth),
                     color = textColor,

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

@@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusRequester
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.ModifierExt.requestFocusOnTouch
 import org.mjdev.tvapp.base.ui.components.complex.VerticalScrollableBox
 import org.mjdev.tvapp.base.navigation.MenuItem
@@ -53,17 +54,17 @@ open class Page {
     fun content() {
         val focusRequester = rememberFocusRequester(title)
         Column(
-            Modifier.fillMaxSize()
+            Modifier.fillMaxSize().recomposeHighlighter()
         ) {
             VerticalScrollableBox(
-                modifier = Modifier
+                modifier = Modifier.recomposeHighlighter()
                     .fillMaxSize()
                     .background(backgroundColor, background)
                     .requestFocusOnTouch(focusRequester),
                 contentAlignment = Alignment.Center,
             ) {
                 Box(
-                    modifier = Modifier
+                    modifier = Modifier.recomposeHighlighter()
                         .fillMaxSize()
                         .focusRequester(focusRequester)
                 ) {
@@ -76,7 +77,7 @@ open class Page {
     @Composable
     open fun Content() {
         Column(
-            modifier = Modifier.fillMaxSize(),
+            modifier = Modifier.fillMaxSize().recomposeHighlighter(),
             verticalArrangement = Arrangement.Center,
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {

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

@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.launch
 import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.navigation.MenuItemClickListener
 import org.mjdev.tvapp.base.navigation.MenuItem
@@ -76,7 +77,7 @@ fun TvPager(
     navController.addMenuClickListener(listener)
 
     Box(
-        modifier = Modifier
+        modifier = Modifier.recomposeHighlighter()
             .fillMaxSize()
             .background(backGroundColor, backGroundShape),
     ) {

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

@@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.NavExt.rememberNavControllerEx
 import org.mjdev.tvapp.base.navigation.NavHostControllerEx
 import org.mjdev.tvapp.base.ui.components.navigation.Navigation
@@ -26,7 +27,7 @@ fun ScreenView(
     content: @Composable () -> Unit = { EmptyScreen() }
 ) {
     Box(
-        modifier = Modifier.fillMaxSize(),
+        modifier = Modifier.fillMaxSize().recomposeHighlighter(),
         contentAlignment = Alignment.TopStart,
     ) {
         if (navController.menuState.value) {

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

@@ -22,6 +22,7 @@ import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.TextUnit
 import androidx.tv.material3.LocalTextStyle
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 
 @SuppressLint("ModifierParameter")
 @Composable
@@ -46,7 +47,7 @@ fun AutoHideEmptyText(
     if ((text != null) && (text.toString().isNotEmpty())) {
         TextAny(
             text,
-            modifier,
+            modifier.recomposeHighlighter(),
             color,
             fontSize,
             fontStyle,

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

@@ -30,6 +30,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.MaterialTheme
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.ModifierExt.tvAspectRatio
 
 @OptIn(ExperimentalTvMaterial3Api::class)
@@ -48,7 +49,7 @@ fun CircleText(
 ) {
 
     Box(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .size(textSize.value.dp * 2)
             .padding(contentPadding)
             .tvAspectRatio(1f)

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

@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.TextUnit
 import androidx.tv.material3.LocalTextStyle
 import androidx.tv.material3.Text
 import org.mjdev.tvapp.base.extensions.ComposeExt.textFrom
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 
 @SuppressLint("ModifierParameter")
 @Preview
@@ -48,7 +49,7 @@ fun TextAny(
     style: TextStyle = LocalTextStyle.current
 ) = Text(
     text = textFrom(text),
-    modifier = modifier,
+    modifier = modifier.recomposeHighlighter(),
     color = color,
     fontSize = fontSize,
     fontStyle = fontStyle,

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

@@ -21,6 +21,7 @@ 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.base.extensions.ModifierExt.recomposeHighlighter
 
 @Preview
 @Composable
@@ -41,7 +42,7 @@ fun TextWithShadow(
             shadowSize,
             shadowSize,
             shadowSize
-        ),
+        ).recomposeHighlighter(),
         style = MaterialTheme.typography.bodyMedium.copy(
             shadow = Shadow(
                 color = shadowColor,

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

@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp
 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.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.extensions.StringExt.asException
 import org.mjdev.tvapp.base.interfaces.ItemWithTitle
 import org.mjdev.tvapp.base.network.NetworkStatus
@@ -65,7 +66,7 @@ fun BrowseView(
 ) {
     val isEdit = isEditMode()
     ScrollableTvLazyRow(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .fillMaxSize()
             .background(backgroundColor, backgroundShape),
         verticalArrangement = verticalArrangement,
@@ -106,7 +107,7 @@ fun BrowseView(
         }
         if (isEdit || featuredItems.isNotEmpty()) item {
             BigCarousel(
-                modifier = Modifier,
+                modifier = Modifier.recomposeHighlighter(),
                 items = featuredItems,
                 onItemClicked = onItemClick
             )

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

@@ -32,6 +32,7 @@ import androidx.tv.foundation.lazy.list.TvLazyListState
 import androidx.tv.foundation.lazy.list.rememberTvLazyListState
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import org.mjdev.tvapp.base.extensions.ComposeExt.computeCardHeight
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.card.PhotoCard
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
@@ -59,12 +60,12 @@ fun CategoryRow(
 ) {
     val scrollDelta = remember { mutableFloatStateOf(0f) }
     Column(
-        modifier = Modifier
+        modifier = Modifier.recomposeHighlighter()
             .fillMaxWidth()
             .background(backgroundColor, RectangleShape)
     ) {
         TextAny(
-            modifier = Modifier
+            modifier = Modifier.recomposeHighlighter()
                 .fillMaxWidth()
                 .padding(padding / 2, padding / 2),
             text = title,
@@ -77,7 +78,7 @@ fun CategoryRow(
                 detectDragGestures { _, dragAmount ->
                     scrollDelta.value = dragAmount.x
                 }
-            },
+            }.recomposeHighlighter(),
             state = rowState,
             contentPadding = (padding.value * 1.3).dp, // todo due border & glow
         ) {

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

@@ -36,6 +36,7 @@ import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ContextExt.dateAsString
 import org.mjdev.tvapp.base.extensions.ContextExt.timeAsString
 import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
@@ -91,7 +92,7 @@ fun Clock(
     }.collectAsState(initial = "1.1.1970") else null
 
     FocusableBox(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .padding(contentPadding)
             .conditional(isEdit) {
                 background(Color.DarkGray, RectangleShape)
@@ -101,19 +102,19 @@ fun Clock(
     ) {
 
         Box(
-            Modifier
+            Modifier.recomposeHighlighter()
                 .background(backgroundColor, RoundedCornerShape(roundSize))
                 .wrapContentSize()
                 .clip(RoundedCornerShape(roundSize))
         ) {
 
             Column(
-                modifier = Modifier.padding(8.dp, 2.dp, 8.dp, 2.dp),
+                modifier = Modifier.padding(8.dp, 2.dp, 8.dp, 2.dp).recomposeHighlighter(),
                 horizontalAlignment = horizontalAlignment,
             ) {
 
                 if (showTime) TextAny(
-                    modifier = modifier.wrapContentSize(),
+                    modifier = modifier.wrapContentSize().recomposeHighlighter(),
                     text = timeFlow?.value,
                     fontWeight = timeTextWeight,
                     fontSize = timeTextSize,
@@ -121,7 +122,7 @@ fun Clock(
                 )
 
                 if (showDate) TextAny(
-                    modifier = modifier.wrapContentSize(),
+                    modifier = modifier.wrapContentSize().recomposeHighlighter(),
                     text = dateFlow?.value,
                     textAlign = TextAlign.End,
                     fontWeight = dateTextWeight,

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

@@ -32,6 +32,7 @@ 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.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.button.Button
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 
@@ -56,11 +57,11 @@ fun ErrorMessage(
 ) {
 
     Box(
-        modifier = Modifier.fillMaxWidth()
+        modifier = Modifier.fillMaxWidth().recomposeHighlighter()
     ) {
 
         ConstraintLayout(
-            modifier = Modifier
+            modifier = Modifier.recomposeHighlighter()
                 .fillMaxWidth()
                 .padding(contentPadding)
                 .background(backgroundColor, RoundedCornerShape(roundSize))
@@ -77,7 +78,7 @@ fun ErrorMessage(
             }
 
             TextAny(
-                modifier = Modifier
+                modifier = Modifier.recomposeHighlighter()
                     .wrapContentSize()
                     .constrainAs(_title) {
                         top.linkTo(parent.top)
@@ -92,7 +93,7 @@ fun ErrorMessage(
             )
 
             BasicText(
-                modifier = Modifier
+                modifier = Modifier.recomposeHighlighter()
                     .wrapContentSize()
                     .constrainAs(_message) {
                         top.linkTo(parent.top)
@@ -118,7 +119,7 @@ fun ErrorMessage(
             if (dismissible && (cancelText != null)) {
 
                 Button(
-                    modifier = Modifier
+                    modifier = Modifier.recomposeHighlighter()
                         .background(
                             Color.White.copy(
                                 alpha = 0.5f

+ 8 - 7
app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/tv/Header.kt

@@ -31,6 +31,7 @@ import androidx.compose.ui.unit.sp
 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.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.badge.Badge
 
 @TvPreview
@@ -52,18 +53,18 @@ fun Header(
 ) {
     val isEdit = isEditMode()
     Box(
-        modifier = Modifier
+        modifier = Modifier.recomposeHighlighter()
             .fillMaxWidth()
             .background(backgroundColor, RoundedCornerShape(roundSize))
             .padding(padding + roundSize)
     ) {
         Row(
-            modifier = Modifier.fillMaxWidth(),
+            modifier = Modifier.fillMaxWidth().recomposeHighlighter(),
             horizontalArrangement = Arrangement.End,
             verticalAlignment = Alignment.CenterVertically,
         ) {
             Clock(
-                modifier = Modifier.wrapContentSize(),
+                modifier = Modifier.wrapContentSize().recomposeHighlighter(),
                 backgroundColor = Color.Black.copy(alpha = 0.2f),
                 contentPadding = contentPadding,
                 timeTextColor = color,
@@ -72,7 +73,7 @@ fun Header(
             )
             if (isEdit || (messagesCount > 0)) {
                 Badge(
-                    modifier = Modifier
+                    modifier = Modifier.recomposeHighlighter()
                         .wrapContentSize()
                         .clip(CircleShape),
                     contentPadding = contentPadding,
@@ -85,7 +86,7 @@ fun Header(
                 )
             }
             UserPic(
-                modifier = Modifier
+                modifier = Modifier.recomposeHighlighter()
                     .size(56.dp)
                     .clip(CircleShape),
                 borderColor = Color.White,
@@ -95,12 +96,12 @@ fun Header(
             )
         }
         Row(
-            modifier = Modifier.fillMaxWidth(),
+            modifier = Modifier.fillMaxWidth().recomposeHighlighter(),
             horizontalArrangement = Arrangement.Start,
             verticalAlignment = Alignment.CenterVertically,
         ) {
             Title(
-                modifier = Modifier.wrapContentSize(),
+                modifier = Modifier.wrapContentSize().recomposeHighlighter(),
                 title = title ?: R.string.app_name,
                 fontWeight = fontWeight,
                 fontSize = fontSize,

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

@@ -21,6 +21,7 @@ import androidx.tv.foundation.lazy.list.TvLazyRow
 import androidx.tv.foundation.lazy.list.rememberTvLazyListState
 import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.paging.ListPagingSource
 import org.mjdev.tvapp.base.paging.SOURCE
 import org.mjdev.tvapp.base.ui.components.text.TextAny
@@ -59,7 +60,7 @@ fun <T : Any> PagingTvLazyRow(
     }
 
     TvLazyRow(
-        modifier = modifier,
+        modifier = modifier.recomposeHighlighter(),
         state = state,
         contentPadding = PaddingValues(contentPadding),
         reverseLayout = reverseLayout,

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

@@ -20,6 +20,7 @@ import androidx.tv.foundation.lazy.list.TvLazyListScope
 import androidx.tv.foundation.lazy.list.TvLazyListState
 import androidx.tv.foundation.lazy.list.rememberTvLazyListState
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.complex.VerticalScrollableBox
 
 @TvPreview
@@ -37,11 +38,11 @@ fun ScrollableTvLazyRow(
     content: TvLazyListScope.() -> Unit = {}
 ) {
     VerticalScrollableBox(
-        modifier = modifier,
+        modifier = modifier.recomposeHighlighter(),
         state = state
     ) {
         TvLazyColumn(
-            modifier = modifier,
+            modifier = modifier.recomposeHighlighter(),
             state = state,
             contentPadding = contentPadding,
             reverseLayout = reverseLayout,

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

@@ -22,6 +22,7 @@ import androidx.tv.foundation.lazy.list.TvLazyListState
 import androidx.tv.foundation.lazy.list.TvLazyRow
 import androidx.tv.foundation.lazy.list.rememberTvLazyListState
 import org.mjdev.tvapp.base.annotations.TvPreview
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 
 // todo : scroll horizontally by touch
 @TvPreview
@@ -39,7 +40,7 @@ fun TVRow(
     content: TvLazyListScope.() -> Unit = {}
 ) {
     TvLazyRow(
-        modifier = modifier,
+        modifier = modifier.recomposeHighlighter(),
         state = state,
         contentPadding = PaddingValues(contentPadding),
         reverseLayout = reverseLayout,

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

@@ -33,6 +33,7 @@ import org.mjdev.tvapp.base.annotations.TvPreview
 import org.mjdev.tvapp.base.extensions.ComposeExt.isFocused
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberMutableInteractionSource
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.interfaces.ItemWithTitle
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.text.TextAny
@@ -64,7 +65,7 @@ fun Tabs(
         disabledSelectedContentColor = selectedContentColor,
     )
     TabRow(
-        modifier = modifier.fillMaxWidth(),
+        modifier = modifier.fillMaxWidth().recomposeHighlighter(),
         selectedTabIndex = selectedTabIndex.value,
         contentColor = activeContentColor,
         separator = {
@@ -82,7 +83,7 @@ fun Tabs(
     ) {
         items.forEachIndexed { index, tab ->
             Tab(
-                modifier = Modifier,
+                modifier = Modifier.recomposeHighlighter(),
                 colors = colors,
                 selected = index == selectedTabIndex.value,
                 onFocus = {
@@ -104,7 +105,7 @@ fun Tabs(
                     onClick = { onItemClick(items[selectedTabIndex.value]) }
                 ) {
                     TextAny(
-                        modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
+                        modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp).recomposeHighlighter(),
                         text = if (tab is ItemWithTitle<*>) tab.title else tab.toString(),
                         fontSize = fontSize,
                         color = Color.White,

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

@@ -30,6 +30,7 @@ import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ComposeExt.isFocused
 import org.mjdev.tvapp.base.extensions.ComposeExt.rememberFocusState
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.image.CircleImage
 import org.mjdev.tvapp.base.ui.components.text.TextWithShadow
@@ -51,18 +52,18 @@ fun Title(
     val focusState = rememberFocusState(title)
     val shadowColor =if (isEdit || focusState.isFocused) focusShadowColor else unfocusedShadowColor
     FocusableBox(
-        modifier = modifier,
+        modifier = modifier.recomposeHighlighter(),
         onClick = onClick,
         shape = RoundedCornerShape(8.dp),
         focusedColor = Color.Transparent,
         onFocusChange = { state -> focusState.value = state }
     ) {
         Row(
-            modifier = modifier.padding(8.dp, 8.dp, 8.dp, 8.dp),
+            modifier = modifier.padding(8.dp, 8.dp, 8.dp, 8.dp).recomposeHighlighter(),
             verticalAlignment = CenterVertically,
         ) {
             CircleImage(
-                modifier = Modifier
+                modifier = Modifier.recomposeHighlighter()
                     .height((fontSize.value).dp)
                     .aspectRatio(1f)
                     .scale(1.3f)
@@ -77,7 +78,7 @@ fun Title(
                 src = R.mipmap.ic_launcher
             )
             TextWithShadow(
-                modifier = modifier,
+                modifier = modifier.recomposeHighlighter(),
                 shadowColor = shadowColor,
                 shadowSize = shadowSize,
                 text = title ?: R.string.app_name,

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

@@ -23,6 +23,7 @@ import androidx.tv.material3.MaterialTheme
 import org.mjdev.tvapp.R
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
 import org.mjdev.tvapp.base.extensions.ModifierExt.conditional
+import org.mjdev.tvapp.base.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.image.CircleImage
 
@@ -45,7 +46,7 @@ fun UserPic(
 ) {
     val isEdit = isEditMode()
     CircleImage(
-        modifier = modifier
+        modifier = modifier.recomposeHighlighter()
             .conditional(isEdit) {
                 size(64.dp)
             },