Ver Fonte

ui improvements
ui improvements
ui improvements #1

Milan Jurkulak há 3 anos atrás
pai
commit
f6970e13dd
24 ficheiros alterados com 550 adições e 61 exclusões
  1. 2 1
      app/build.gradle.kts
  2. 17 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/BitmapExt.kt
  3. 47 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ColorFilterExt.kt
  4. 23 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ComposeExt.kt
  5. 3 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/DrawableExt.kt
  6. 8 1
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ModifierExt.kt
  7. 56 0
      app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ViewExt.kt
  8. 37 0
      app/src/main/kotlin/org/mjdev/tvapp/base/helpers/SquashedOval.kt
  9. 17 18
      app/src/main/kotlin/org/mjdev/tvapp/base/network/NetworkConnectivityServiceImpl.kt
  10. 2 1
      app/src/main/kotlin/org/mjdev/tvapp/base/network/NetworkStatus.kt
  11. 74 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/Card.kt
  12. 2 6
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/ItemCard.kt
  13. 54 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/card/PhotoCard.kt
  14. 2 2
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/Badge.kt
  15. 13 11
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/FocusableCard.kt
  16. 4 5
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/NavigationRow.kt
  17. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/complex/UserPic.kt
  18. 4 6
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/icon/IconAny.kt
  19. 10 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/CircleImage.kt
  20. 100 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/image/PhotoImage.kt
  21. 0 1
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/page/PagerScope.kt
  22. 10 3
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/components/text/CircleText.kt
  23. 63 0
      app/src/main/kotlin/org/mjdev/tvapp/base/ui/drawable/PhotoDrawable.kt
  24. 1 1
      app/src/main/kotlin/org/mjdev/tvapp/ui/pages/MainPage.kt

+ 2 - 1
app/build.gradle.kts

@@ -140,8 +140,9 @@ dependencies {
     implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
     // navigation
     implementation("androidx.navigation:navigation-compose:2.7.0-beta01")
-    // async image loading
+    // image loading
     implementation("io.coil-kt:coil-compose:2.4.0")
+    implementation("com.github.skydoves:landscapist-coil:2.2.2")
     // constraint layout
     implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
     // debug

+ 17 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/BitmapExt.kt

@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.extensions
+
+import android.graphics.Bitmap
+
+object BitmapExt {
+
+    fun Bitmap.bimapCopy() = copy(Bitmap.Config.ARGB_8888, false)
+
+}

+ 47 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ColorFilterExt.kt

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.extensions
+
+import androidx.annotation.FloatRange
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ColorMatrix
+
+object ColorFilterExt {
+
+    fun contrastAndBrightness(
+        @FloatRange(from = 0.0, to = 10.0)
+        contrast: Float,
+        @FloatRange(from = -255.0, to = 255.0)
+        brightness: Float,
+    ) = ColorFilter.colorMatrix(
+        ColorMatrix(
+            floatArrayOf(
+                contrast, 0f, 0f, 0f, brightness,
+                0f, contrast, 0f, 0f, brightness,
+                0f, 0f, contrast, 0f, brightness,
+                0f, 0f, 0f, 1f, 0f
+            )
+        )
+    )
+
+    fun BW() = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
+
+    fun colorInvert() = ColorFilter.colorMatrix(
+        ColorMatrix(
+            floatArrayOf(
+                -1f, 0f, 0f, 0f, 255f,
+                0f, -1f, 0f, 0f, 255f,
+                0f, 0f, -1f, 0f, 255f,
+                0f, 0f, 0f, 1f, 0f
+            )
+        )
+    )
+
+
+}

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

@@ -38,4 +38,27 @@ object ComposeExt {
         else -> text.toString()
     }
 
+//    @Composable
+//    fun Any?.toDrawable(): Drawable {
+//        val context = LocalContext.current
+//        val coroutine = rememberCoroutineScope()
+//        return when (this) {
+//            is Int -> ContextCompat.getDrawable(context, this) ?: ColorDrawable(0)
+//            is Color -> ColorDrawable(this.toArgb())
+//            is Drawable -> this
+//            is Bitmap -> BitmapDrawable(context.resources, this)
+//            else -> {
+//                runBlocking {
+//                    withContext(coroutine.coroutineContext) {
+//                        ImageLoader(context).execute(
+//                            ImageRequest.Builder(context)
+//                                .data(this)
+//                                .build()
+//                        ).drawable
+//                    }!!
+//                }
+//            }
+//        }
+//    }
+
 }

+ 3 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/DrawableExt.kt

@@ -12,6 +12,7 @@ import android.graphics.drawable.Drawable
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.core.graphics.drawable.toBitmap
+import org.mjdev.tvapp.base.ui.drawable.PhotoDrawable
 
 object DrawableExt {
 
@@ -24,4 +25,6 @@ object DrawableExt {
         if (height == 0) 1 else height
     ).asImageBitmap()
 
+    fun Drawable.asPhoto(): Drawable = PhotoDrawable(this)
+
 }

+ 8 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ModifierExt.kt

@@ -8,6 +8,7 @@
 
 package org.mjdev.tvapp.base.extensions
 
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.gestures.detectTapGestures
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
@@ -20,10 +21,16 @@ import timber.log.Timber
 object ModifierExt {
 
     fun Modifier.touchable(
-        onTouch: FocusRequester.() -> Unit = {}
+        focusable: Boolean = false,
+        onTouch: FocusRequester.() -> Unit = {},
     ): Modifier = composed {
         val focusRequester = remember { FocusRequester() }
         this
+            .apply {
+                if (focusable) {
+                    this.focusable()
+                }
+            }
             .focusRequester(focusRequester)
             .pointerInput(Unit) {
                 detectTapGestures {

+ 56 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/extensions/ViewExt.kt

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.extensions
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.view.PixelCopy
+import android.view.View
+import android.view.Window
+
+@Suppress("unused")
+object ViewExt {
+
+    fun captureView(view: View, window: Window, bitmapCallback: (Bitmap) -> Unit) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
+            val location = IntArray(2)
+            view.getLocationInWindow(location)
+            PixelCopy.request(
+                window,
+                Rect(
+                    location[0],
+                    location[1],
+                    location[0] + view.width,
+                    location[1] + view.height
+                ),
+                bitmap,
+                {
+                    if (it == PixelCopy.SUCCESS) {
+                        bitmapCallback.invoke(bitmap)
+                    }
+                },
+                Handler(Looper.getMainLooper())
+            )
+        } else {
+            val tBitmap = Bitmap.createBitmap(
+                view.width, view.height, Bitmap.Config.RGB_565
+            )
+            val canvas = Canvas(tBitmap)
+            view.draw(canvas)
+            canvas.setBitmap(null)
+            bitmapCallback.invoke(tBitmap)
+        }
+    }
+
+}

+ 37 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/helpers/SquashedOval.kt

@@ -0,0 +1,37 @@
+///*
+// * Copyright (c) Milan Jurkulák 2023.
+// *  Contact:
+// *  e: mimoccc@gmail.com
+// *  e: mj@mjdev.org
+// *  w: https://mjdev.org
+// */
+//
+//package org.mjdev.tvapp.base.helpers
+//
+//import androidx.compose.ui.geometry.Rect
+//import androidx.compose.ui.geometry.Size
+//import androidx.compose.ui.graphics.Outline
+//import androidx.compose.ui.graphics.Path
+//import androidx.compose.ui.graphics.Shape
+//import androidx.compose.ui.unit.Density
+//import androidx.compose.ui.unit.LayoutDirection
+//
+//class SquashedOval : Shape {
+//    override fun createOutline(
+//        size: Size,
+//        layoutDirection: LayoutDirection,
+//        density: Density
+//    ): Outline {
+//        val path = Path().apply {
+//            addOval(
+//                Rect(
+//                    left = size.width / 4f,
+//                    top = 0f,
+//                    right = size.width * 3 / 4f,
+//                    bottom = size.height
+//                )
+//            )
+//        }
+//        return Outline.Generic(path = path)
+//    }
+//}

+ 17 - 18
app/src/main/kotlin/org/mjdev/tvapp/base/network/NetworkConnectivityServiceImpl.kt

@@ -9,9 +9,6 @@
 package org.mjdev.tvapp.base.network
 
 import android.content.Context
-import android.net.ConnectivityManager
-import android.net.NetworkCapabilities
-import android.net.NetworkRequest
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
@@ -19,27 +16,29 @@ import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
 
+@Suppress("UNUSED_PARAMETER")
 class NetworkConnectivityServiceImpl constructor(
     context: Context
 ) : NetworkConnectivityService {
+//    private val connectivityManager = context
+//        .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
 
-    private val connectivityManager = context
-        .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
-
+    // todo error ConnectivityManager$TooManyRequestsException
     override val networkStatus: Flow<NetworkStatus> = callbackFlow {
-        val connectivityCallback = ConnectivityCallback { status ->
-            trySend(status)
-        }
-        val request = NetworkRequest.Builder()
-            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
-            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
-            .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
-            .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
-            .build()
-        connectivityManager.registerNetworkCallback(request, connectivityCallback)
+        trySend(NetworkStatus.Connected(null))
+//        val connectivityCallback = ConnectivityCallback { status ->
+//            trySend(status)
+//        }
+//        val request = NetworkRequest.Builder()
+//            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+//            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+//            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+//            .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+//            .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+//            .build()
+//        connectivityManager.registerNetworkCallback(request, connectivityCallback)
         awaitClose {
-            connectivityManager.unregisterNetworkCallback(connectivityCallback)
+//            connectivityManager.unregisterNetworkCallback(connectivityCallback)
         }
     }.distinctUntilChanged().flowOn(Dispatchers.IO)
 

+ 2 - 1
app/src/main/kotlin/org/mjdev/tvapp/base/network/NetworkStatus.kt

@@ -10,11 +10,12 @@ package org.mjdev.tvapp.base.network
 
 import android.net.Network
 
+@Suppress("unused")
 sealed class NetworkStatus {
 
     object Unknown: NetworkStatus()
 
-    class Connected(val network: Network): NetworkStatus()
+    class Connected(val network: Network?): NetworkStatus()
 
     object Disconnected: NetworkStatus()
 

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

@@ -8,12 +8,19 @@
 
 package org.mjdev.tvapp.base.ui.components.card
 
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.border
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
@@ -84,4 +91,70 @@ fun CardDefaults.colorFocusGlow(
         elevation = elevation
     ),
     pressedGlow = Glow.None
-)
+)
+
+@Suppress("UNUSED_PARAMETER")
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun CardDefaults.colorFocusBorder(
+    colorFocused: Color = Color.Green,
+    colorUnFocused: Color = Color.Black,
+    colorPressed: Color = Color.Yellow,
+    width: Dp = 3.dp,
+    roundCornerSize: Dp = 8.dp
+) = border(
+//    border = Border(
+//        border = BorderStroke(
+//            width = width,
+//            color = colorUnFocused
+//        ),
+//        shape = RoundedCornerShape(roundCornerSize)
+//    ),
+    focusedBorder = Border(
+        border = BorderStroke(
+            width = width,
+            color = colorFocused
+        ),
+        shape = RoundedCornerShape(roundCornerSize)
+    ),
+//    pressedBorder = Border(
+//        border = BorderStroke(
+//            width = width,
+//            color = colorPressed
+//        ),
+//        shape = RoundedCornerShape(roundCornerSize)
+//    )
+)
+
+@Suppress("unused")
+@Composable
+fun rainbowColorsBrush() = remember {
+    Brush.sweepGradient(
+        listOf(
+            Color(0xFF9575CD),
+            Color(0xFFBA68C8),
+            Color(0xFFE57373),
+            Color(0xFFFFB74D),
+            Color(0xFFFFF176),
+            Color(0xFFAED581),
+            Color(0xFF4DD0E1),
+            Color(0xFF9575CD)
+        )
+    )
+}
+
+@Suppress("unused")
+fun Modifier.rainbowBorder(
+    borderSize: Dp = 4.dp,
+    borderRound: Dp = 8.dp,
+    shape: Shape = RoundedCornerShape(borderRound)
+) = composed { border(
+    BorderStroke(
+        borderSize,
+        rainbowColorsBrush()
+    ),
+    shape
+) }
+
+@Suppress("unused")
+fun Modifier.tvAspectRatio() = aspectRatio(16f / 9f)

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

@@ -34,12 +34,8 @@ fun ItemCard(
             .widthIn(max = 320.dp)
             .aspectRatio(16f / 9f),
         item = item,
-        onFocus = {
-            onFocus(item)
-        },
-        onClick = {
-            onClick(item)
-        }
+        onFocus = onFocus,
+        onClick = onClick,
     )
 
 }

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

@@ -0,0 +1,54 @@
+///*
+// * Copyright (c) Milan Jurkulák 2023.
+// *  Contact:
+// *  e: mimoccc@gmail.com
+// *  e: mj@mjdev.org
+// *  w: https://mjdev.org
+// */
+//
+//package org.mjdev.tvapp.base.ui.components.card
+//
+//import android.annotation.SuppressLint
+//import androidx.compose.runtime.Composable
+//import androidx.compose.ui.Modifier
+//import androidx.compose.ui.layout.ContentScale
+//import androidx.compose.ui.tooling.preview.Preview
+//import androidx.tv.material3.CardDefaults
+//import androidx.tv.material3.CardScale
+//import androidx.tv.material3.ExperimentalTvMaterial3Api
+//import org.mjdev.tvapp.base.interfaces.ItemWithDescription
+//import org.mjdev.tvapp.base.interfaces.ItemWithImage
+//import org.mjdev.tvapp.base.ui.components.complex.FocusableCard
+//import org.mjdev.tvapp.base.ui.components.image.PhotoImage
+//
+//@OptIn(ExperimentalTvMaterial3Api::class)
+//@SuppressLint("ModifierParameter")
+//@Preview
+//@Composable
+//fun PhotoCard(
+//    item: Any? = null,
+//    modifier: Modifier = Modifier,
+//    contentScale: ContentScale = ContentScale.Crop,
+//    imageRenderer: @Composable (modifier: Modifier) -> Unit = { m ->
+//        PhotoImage(
+//            modifier = m,
+//            src = (item as? ItemWithImage)?.imageUrl,
+//            contentDescription = (item as? ItemWithDescription)?.description?.toString(),
+//        )
+//    },
+//    placeholder: @Composable () -> Unit = {},
+//    scale: CardScale = CardDefaults.scale(),
+//    onFocus: (item: Any?) -> Unit = {},
+//    onClick: (item: Any?) -> Unit = {},
+//) {
+//    ItemCard(
+//        item = item,
+//        modifier = modifier,
+//        contentScale = contentScale,
+//        scale = scale,
+//        imageRenderer = imageRenderer,
+//        placeholder = placeholder,
+//        onClick = onClick,
+//        onFocus = onFocus
+//    )
+//}

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

@@ -31,8 +31,8 @@ fun Badge(
     shape: Shape = CircleShape,
     count: Int = 0,
     backGroundColor: Color = MaterialTheme.colorScheme.secondary,
-    borderColor: Color = Color.Transparent,
-    borderSize: Dp = 0.dp,
+    borderColor: Color = Color.White,
+    borderSize: Dp = 2.dp,
     textColor: Color = Color.White,
     textSize: TextUnit = 20.sp,
     contentPadding: Dp = 2.dp,

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

@@ -52,8 +52,18 @@ fun FocusableCard(
     shape: CardShape = CardDefaults.shape(),
     colors: CardColors = CardDefaults.colors(),
     border: CardBorder = CardDefaults.border(),
-    glow: CardGlow = CardDefaults.glow(),
+    glow: CardGlow = CardDefaults.colorFocusGlow(Color.Green),
+    placeholder: @Composable () -> Unit = {},
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    imageRenderer: @Composable (modifier:Modifier) -> Unit = {
+        ImageAny(
+            modifier = modifier,
+            src = (item as? ItemWithImage)?.imageUrl,
+            contentDescription = (item as? ItemWithDescription)?.description?.toString(),
+            contentScale = contentScale,
+            placeholder = placeHolder
+        )
+    },
     onFocus: (item: Any?) -> Unit = {},
     onClick: (item: Any?) -> Unit = {},
 ) {
@@ -61,7 +71,6 @@ fun FocusableCard(
     val isFocused: () -> Boolean = {
         ((focusState.value?.isFocused == true) || (focusState.value?.hasFocus == true))
     }
-
     CompactCard(
         interactionSource = interactionSource,
         scale = scale,
@@ -80,13 +89,7 @@ fun FocusableCard(
                     onFocus(item)
             },
         image = {
-            ImageAny(
-                modifier = modifier,
-                src = (item as? ItemWithImage)?.imageUrl,
-                contentDescription = (item as? ItemWithDescription)?.description?.toString(),
-                contentScale = contentScale,
-                placeholder = placeHolder
-            )
+            imageRenderer(modifier)
         },
         title = {
             TextAny(
@@ -101,11 +104,10 @@ fun FocusableCard(
             )
         },
         description = {
-          // todo
+            // todo
         },
         onClick = {
             onClick(item)
         },
     )
-
 }

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

@@ -1,9 +1,9 @@
 /*
  * Copyright (c) Milan Jurkulák 2023.
- * Contact:
- * e: mimoccc@gmail.com
- * e: mj@mjdev.org
- * w: https://mjdev.org
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
  */
 
 package org.mjdev.tvapp.base.ui.components.complex
@@ -32,7 +32,6 @@ import androidx.tv.material3.DrawerValue
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.rememberDrawerState
 import org.mjdev.tvapp.base.extensions.ComposeExt.isEditMode
-import org.mjdev.tvapp.base.ui.components.complex.FocusableBox
 import org.mjdev.tvapp.base.ui.components.icon.IconAny
 import org.mjdev.tvapp.base.ui.components.text.TextAny
 

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

@@ -30,7 +30,7 @@ fun UserPic(
     shape: Shape = CircleShape,
     src: Any? = R.drawable.milanj,
     backGroundColor: Color = MaterialTheme.colorScheme.secondary,
-    borderColor: Color = Color.Transparent,
+    borderColor: Color = Color.White,
     borderSize: Dp = 2.dp,
     contentPadding: Dp = 2.dp,
     onClick: () -> Unit = {}

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

@@ -19,6 +19,7 @@ import androidx.compose.material.icons.filled.Menu
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.asImageBitmap
 import androidx.compose.ui.graphics.vector.ImageVector
@@ -90,28 +91,25 @@ fun IconAny(
             tint
         )
 
-        // todo tint
         is URL -> AsyncImage(
             model = src,
             contentDescription = contentDescription,
             modifier = modifier,
-//            tint = tint
+            colorFilter = ColorFilter.tint(tint)
         )
 
-        // todo tint
         is Uri -> AsyncImage(
             model = src,
             contentDescription = contentDescription,
             modifier = modifier,
-//            tint = tint
+            colorFilter = ColorFilter.tint(tint)
         )
 
-// todo tint
         is String -> AsyncImage(
             model = src,
             contentDescription = contentDescription,
             modifier = modifier,
-//            tint = tint
+            colorFilter = ColorFilter.tint(tint)
         )
 
         is ImageVector -> Icon(

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

@@ -9,6 +9,7 @@
 package org.mjdev.tvapp.base.ui.components.image
 
 import android.annotation.SuppressLint
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
@@ -31,8 +32,8 @@ import org.mjdev.tvapp.R
 fun CircleImage(
     modifier: Modifier = Modifier,
     backGroundColor: Color = Color.Black,
-    borderColor: Color = Color.Transparent,
-    borderSize: Dp = 0.dp,
+    borderColor: Color = Color.White,
+    borderSize: Dp = 3.dp,
     contentDescription: String = "",
     contentPadding: Dp = 2.dp,
     src: Any? = R.drawable.milanj
@@ -47,7 +48,13 @@ fun CircleImage(
             modifier = modifier
                 .clip(CircleShape)
                 .background(backGroundColor, CircleShape)
-                .border(borderSize, borderColor, CircleShape),
+                .border(
+                    BorderStroke(
+                        borderSize,
+                        borderColor
+                    ),
+                    CircleShape
+                ),
             src = src,
             contentDescription = contentDescription,
             contentScale = ContentScale.Crop,

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

@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Milan Jurkulák 2023.
+ *  Contact:
+ *  e: mimoccc@gmail.com
+ *  e: mj@mjdev.org
+ *  w: https://mjdev.org
+ */
+
+package org.mjdev.tvapp.base.ui.components.image
+
+import android.annotation.SuppressLint
+import androidx.annotation.FloatRange
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.border
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import org.mjdev.tvapp.base.extensions.ColorFilterExt.contrastAndBrightness
+import androidx.compose.ui.graphics.DefaultAlpha
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toDrawable
+import coil.ImageLoader
+import com.skydoves.landscapist.ImageOptions
+import com.skydoves.landscapist.coil.CoilImage
+import org.mjdev.tvapp.R
+import org.mjdev.tvapp.base.extensions.DrawableExt.asPhoto
+
+@SuppressLint("ModifierParameter")
+@Preview
+@Composable
+fun PhotoImage(
+    src: Any? = R.drawable.milanj,
+    placeholder: @Composable () -> Unit = {},
+    modifier: Modifier = Modifier,
+    alignment: Alignment = Alignment.Center,
+    contentScale: ContentScale = ContentScale.Fit,
+    alpha: Float = DefaultAlpha,
+    @FloatRange(from = 0.0, to = 10.0)
+    contrast: Float = 5f,
+    @FloatRange(from = -255.0, to = 255.0)
+    brightness: Float = -255f,
+    borderSize: Dp = 4.dp,
+    borderColor: Color = Color.Black,
+    roundCornerSize: Dp = 8.dp,
+    shape: Shape = RoundedCornerShape(roundCornerSize),
+    colorFilter: ColorFilter? = null,
+    contentDescription: String? = null,
+) {
+    val context = LocalContext.current
+    CoilImage(
+        imageLoader = {
+            ImageLoader(context).newBuilder()
+                .allowHardware(false)
+                .build()
+        },
+        imageModel = { src },
+        modifier = modifier.border(
+            BorderStroke(borderSize, borderColor),
+            shape
+        ),
+        imageOptions = ImageOptions(
+            contentScale = contentScale,
+            alignment = alignment,
+            contentDescription = contentDescription,
+            alpha = alpha,
+            colorFilter = colorFilter,
+        ),
+        failure = {
+            placeholder()
+        },
+        onImageStateChanged = {
+        },
+        success = { state, _ ->
+            ImageAny(
+                src = state.imageBitmap
+                    ?.asAndroidBitmap()
+                    ?.toDrawable(context.resources)
+                    ?.asPhoto(),
+                modifier = modifier,
+                alignment = alignment,
+                contentScale = contentScale,
+                alpha = alpha,
+                colorFilter = contrastAndBrightness(
+                    contrast,
+                    brightness
+                ),
+                contentDescription = contentDescription,
+            )
+        }
+    )
+}

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

@@ -11,7 +11,6 @@ package org.mjdev.tvapp.base.ui.components.page
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import org.mjdev.tvapp.base.extensions.ListExt.addUnique
 import org.mjdev.tvapp.base.ui.components.navigation.MenuItem

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

@@ -9,6 +9,7 @@
 package org.mjdev.tvapp.base.ui.components.text
 
 import android.annotation.SuppressLint
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
@@ -38,8 +39,8 @@ import androidx.tv.material3.MaterialTheme
 fun CircleText(
     modifier: Modifier = Modifier,
     backGroundColor: Color = MaterialTheme.colorScheme.secondary,
-    borderColor: Color = Color.Transparent,
-    borderSize: Dp = 0.dp,
+    borderColor: Color = Color.White,
+    borderSize: Dp = 2.dp,
     text: String = "0",
     textColor: Color = Color.White,
     textSize: TextUnit = 20.sp,
@@ -53,7 +54,13 @@ fun CircleText(
             .aspectRatio(1f)
             .clip(CircleShape)
             .background(backGroundColor, CircleShape)
-            .border(borderSize, borderColor, CircleShape),
+            .border(
+                BorderStroke(
+                    borderSize,
+                    borderColor
+                ),
+                CircleShape
+            ),
         contentAlignment = Alignment.Center,
     ) {
 

+ 63 - 0
app/src/main/kotlin/org/mjdev/tvapp/base/ui/drawable/PhotoDrawable.kt

@@ -0,0 +1,63 @@
+package org.mjdev.tvapp.base.ui.drawable
+
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.LayerDrawable
+
+class PhotoDrawable(
+    d: Drawable?
+) : LayerDrawable(arrayOf(d, getPDrawable(d), getLight(d))) {
+
+    constructor(bmp: Bitmap) : this(BitmapDrawable(bmp))
+
+    init {
+        setDither(true)
+        setLayerInset(0, 2, 2, 2, 2)
+    }
+
+    companion object {
+
+        private fun getPDrawable(bmp: Bitmap): Drawable {
+            val gd = GradientDrawable(
+                GradientDrawable.Orientation.TR_BL, intArrayOf(
+                    centerColor,
+                    roundColor
+                )
+            )
+            gd.gradientType = GradientDrawable.RADIAL_GRADIENT
+            gd.gradientRadius = (bmp.width / 2).toFloat()
+            return gd
+        }
+
+        private fun getLight(d: Drawable?): Drawable {
+//        ShapeDrawable sd = new ShapeDrawable(new OvalShape());
+//        sd.setIntrinsicHeight(d.getIntrinsicWidth());
+//        sd.setIntrinsicWidth(d.getIntrinsicHeight());
+//        sd.setBounds(new Rect(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()));
+//        sd.getPaint().setColor(getLightColor());
+//        return sd;
+            return ColorDrawable(0)
+        }
+
+        private fun getPDrawable(d: Drawable?): Drawable {
+            val gd = GradientDrawable(
+                GradientDrawable.Orientation.TR_BL,
+                intArrayOf(centerColor, roundColor)
+            )
+            gd.gradientType = GradientDrawable.RADIAL_GRADIENT
+            if (d != null) {
+                gd.gradientRadius = (d.intrinsicWidth / 2).toFloat()
+            }
+            return gd
+        }
+
+        private val roundColor: Int = 0xd0000000.toInt()
+        private val lightColor: Int = 0x20ffffff
+        private val centerColor: Int = 0x00000000
+
+    }
+
+}

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

@@ -40,11 +40,11 @@ import org.mjdev.tvapp.base.interfaces.ItemWithId
 import org.mjdev.tvapp.base.interfaces.ItemWithVideoUri
 import org.mjdev.tvapp.base.ui.components.navigation.Screen.Companion.open
 import org.mjdev.tvapp.base.network.NetworkStatus
+import org.mjdev.tvapp.base.ui.components.card.ItemCard
 import org.mjdev.tvapp.base.ui.components.page.Page
 import org.mjdev.tvapp.base.ui.components.complex.BigCarousel
 import org.mjdev.tvapp.base.ui.components.complex.ScrollableTvLazyRow
 import org.mjdev.tvapp.base.ui.components.complex.Tabs
-import org.mjdev.tvapp.base.ui.components.card.ItemCard
 import org.mjdev.tvapp.base.ui.components.complex.ErrorMessage
 import org.mjdev.tvapp.base.ui.components.complex.Header
 import org.mjdev.tvapp.base.ui.components.text.TextAny