@momo-kits/native-kits 0.162.1-gif.10-debug → 0.162.1-gif.11-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.162.1-gif.10-debug"
43
+ version = "0.162.1-gif.11-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -77,7 +77,7 @@ kotlin {
77
77
  implementation(libs.kotlinx.datetime)
78
78
  api(libs.native.max.api)
79
79
  implementation(libs.compottie)
80
- implementation(libs.sketch.compose)
80
+ implementation(libs.sketch.core)
81
81
  implementation(libs.sketch.http.core)
82
82
  implementation(libs.sketch.animated.gif)
83
83
  }
@@ -77,7 +77,7 @@ kotlin {
77
77
  implementation(libs.kotlinx.datetime)
78
78
  api(libs.native.max.api)
79
79
  implementation(libs.compottie)
80
- implementation(libs.sketch.compose)
80
+ implementation(libs.sketch.core)
81
81
  implementation(libs.sketch.http.core)
82
82
  implementation(libs.sketch.animated.gif)
83
83
  }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.162.1-gif.8'
3
+ spec.version = '0.162.1-gif.10'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -0,0 +1,161 @@
1
+ package vn.momo.kits.utils
2
+
3
+ import android.graphics.drawable.Animatable
4
+ import android.graphics.drawable.BitmapDrawable
5
+ import android.graphics.drawable.ColorDrawable
6
+ import android.graphics.drawable.Drawable
7
+ import android.os.Handler
8
+ import android.os.Looper
9
+ import androidx.compose.runtime.Composable
10
+ import androidx.compose.runtime.RememberObserver
11
+ import androidx.compose.runtime.getValue
12
+ import androidx.compose.runtime.mutableIntStateOf
13
+ import androidx.compose.runtime.mutableStateOf
14
+ import androidx.compose.runtime.setValue
15
+ import androidx.compose.ui.geometry.Size
16
+ import androidx.compose.ui.graphics.Color
17
+ import androidx.compose.ui.graphics.ColorFilter
18
+ import androidx.compose.ui.graphics.ImageBitmap
19
+ import androidx.compose.ui.graphics.asAndroidColorFilter
20
+ import androidx.compose.ui.graphics.asImageBitmap
21
+ import androidx.compose.ui.graphics.drawscope.DrawScope
22
+ import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
23
+ import androidx.compose.ui.graphics.nativeCanvas
24
+ import androidx.compose.ui.graphics.painter.BitmapPainter
25
+ import androidx.compose.ui.graphics.painter.ColorPainter
26
+ import androidx.compose.ui.graphics.painter.Painter
27
+ import androidx.compose.ui.graphics.withSave
28
+ import androidx.compose.ui.platform.LocalContext
29
+ import androidx.compose.ui.unit.LayoutDirection
30
+ import com.github.panpf.sketch.Bitmap
31
+ import com.github.panpf.sketch.DrawableImage
32
+ import com.github.panpf.sketch.Image
33
+ import com.github.panpf.sketch.PlatformContext
34
+ import kotlin.math.roundToInt
35
+
36
+ @Composable
37
+ internal actual fun rememberSketchPlatformContext(): PlatformContext {
38
+ return LocalContext.current.applicationContext
39
+ }
40
+
41
+ internal actual fun Bitmap.toComposeImageBitmap(): ImageBitmap {
42
+ return asImageBitmap()
43
+ }
44
+
45
+ internal actual fun Image?.toSketchGifPainter(): Painter? {
46
+ return when (this) {
47
+ is DrawableImage -> drawable.toPainter()
48
+ else -> null
49
+ }
50
+ }
51
+
52
+ private fun Drawable.toPainter(): Painter {
53
+ return when (this) {
54
+ is Animatable -> AnimatableDrawablePainter(mutate())
55
+ is BitmapDrawable -> BitmapPainter(bitmap.asImageBitmap())
56
+ is ColorDrawable -> ColorPainter(Color(color))
57
+ else -> DrawablePainter(mutate())
58
+ }
59
+ }
60
+
61
+ private open class DrawablePainter(
62
+ protected val drawable: Drawable,
63
+ ) : Painter(), RememberObserver {
64
+ private var drawTick by mutableIntStateOf(0)
65
+ private var drawableSize by mutableStateOf(drawable.intrinsicSize)
66
+
67
+ private val callback = object : Drawable.Callback {
68
+ override fun invalidateDrawable(who: Drawable) {
69
+ drawTick++
70
+ drawableSize = who.intrinsicSize
71
+ }
72
+
73
+ override fun scheduleDrawable(who: Drawable, what: Runnable, `when`: Long) {
74
+ mainHandler.postAtTime(what, `when`)
75
+ }
76
+
77
+ override fun unscheduleDrawable(who: Drawable, what: Runnable) {
78
+ mainHandler.removeCallbacks(what)
79
+ }
80
+ }
81
+
82
+ override val intrinsicSize: Size
83
+ get() = drawableSize
84
+
85
+ init {
86
+ if (drawable.intrinsicWidth >= 0 && drawable.intrinsicHeight >= 0) {
87
+ drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
88
+ }
89
+ }
90
+
91
+ override fun DrawScope.onDraw() {
92
+ drawTick
93
+ drawIntoCanvas { canvas ->
94
+ canvas.withSave {
95
+ drawable.setBounds(0, 0, size.width.roundToInt(), size.height.roundToInt())
96
+ drawable.draw(canvas.nativeCanvas)
97
+ }
98
+ }
99
+ }
100
+
101
+ override fun applyAlpha(alpha: Float): Boolean {
102
+ drawable.alpha = (alpha * 255).roundToInt().coerceIn(0, 255)
103
+ return true
104
+ }
105
+
106
+ override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
107
+ drawable.colorFilter = colorFilter?.asAndroidColorFilter()
108
+ return true
109
+ }
110
+
111
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
112
+ return if (android.os.Build.VERSION.SDK_INT >= 23) {
113
+ drawable.setLayoutDirection(
114
+ if (layoutDirection == LayoutDirection.Rtl) {
115
+ android.view.View.LAYOUT_DIRECTION_RTL
116
+ } else {
117
+ android.view.View.LAYOUT_DIRECTION_LTR
118
+ }
119
+ )
120
+ } else {
121
+ false
122
+ }
123
+ }
124
+
125
+ override fun onRemembered() {
126
+ drawable.callback = callback
127
+ drawable.setVisible(true, true)
128
+ }
129
+
130
+ override fun onAbandoned() = onForgotten()
131
+
132
+ override fun onForgotten() {
133
+ drawable.setVisible(false, false)
134
+ drawable.callback = null
135
+ }
136
+ }
137
+
138
+ private class AnimatableDrawablePainter(
139
+ drawable: Drawable,
140
+ ) : DrawablePainter(drawable) {
141
+ private val animatable = drawable as Animatable
142
+
143
+ override fun onRemembered() {
144
+ super.onRemembered()
145
+ animatable.start()
146
+ }
147
+
148
+ override fun onForgotten() {
149
+ animatable.stop()
150
+ super.onForgotten()
151
+ }
152
+ }
153
+
154
+ private val Drawable.intrinsicSize: Size
155
+ get() = if (intrinsicWidth >= 0 && intrinsicHeight >= 0) {
156
+ Size(intrinsicWidth.toFloat(), intrinsicHeight.toFloat())
157
+ } else {
158
+ Size.Unspecified
159
+ }
160
+
161
+ private val mainHandler = Handler(Looper.getMainLooper())
@@ -17,19 +17,11 @@ import androidx.compose.ui.semantics.contentDescription
17
17
  import androidx.compose.ui.semantics.semantics
18
18
  import androidx.compose.ui.platform.LocalDensity
19
19
  import androidx.compose.ui.unit.dp
20
- import com.github.panpf.sketch.AsyncImage as SketchAsyncImage
21
- import com.github.panpf.sketch.LocalPlatformContext
22
- import com.github.panpf.sketch.PainterState
23
- import com.github.panpf.sketch.fetch.HttpUriFetcher
24
- import com.github.panpf.sketch.request.ImageRequest as SketchImageRequest
25
- import com.github.panpf.sketch.rememberAsyncImageState
26
20
  import coil3.compose.AsyncImage as CoilAsyncImage
27
21
  import vn.momo.kits.application.IsShowBaseLineDebug
28
22
  import vn.momo.kits.const.AppTheme
29
23
  import vn.momo.kits.const.Colors
30
24
  import vn.momo.kits.modifier.conditional
31
- import vn.momo.kits.utils.GifImageHttpClient
32
- import vn.momo.kits.utils.KitsKtorHttpStack
33
25
 
34
26
  data class Options(
35
27
  val alignment: Alignment = Alignment.TopStart,
@@ -124,7 +116,6 @@ fun Image(
124
116
  }
125
117
  .semantics { contentDescription = "img|$source" },
126
118
  ) {
127
- val platformContext = LocalPlatformContext.current
128
119
  val urlToLoad = remember(source, maxWidth, maxHeight, density) {
129
120
  when (source) {
130
121
  is String -> {
@@ -141,34 +132,20 @@ fun Image(
141
132
  }
142
133
 
143
134
  if (isGifUrl(urlToLoad)) {
144
- val asyncImageState = rememberAsyncImageState()
145
- val request = remember(platformContext, urlToLoad) {
146
- SketchImageRequest(platformContext, urlToLoad) {
147
- components {
148
- add(HttpUriFetcher.Factory(KitsKtorHttpStack(GifImageHttpClient.client)))
149
- }
150
- }
151
- }
135
+ val imageState = remember(urlToLoad) { mutableStateOf(ImageState.Loading) }
152
136
 
153
- SketchAsyncImage(
137
+ SketchGifImage(
154
138
  modifier = Modifier.matchParentSize(),
155
- request = request,
156
- state = asyncImageState,
157
- contentDescription = null,
139
+ url = urlToLoad,
158
140
  contentScale = imageOptions.contentScale,
159
141
  alignment = imageOptions.alignment,
160
142
  colorFilter = imageOptions.colorFilter,
161
143
  alpha = imageOptions.alpha,
144
+ onStateChanged = { imageState.value = it },
162
145
  )
163
146
 
164
- val imageState = when (asyncImageState.painterState) {
165
- is PainterState.Success -> ImageState.Success
166
- is PainterState.Error -> ImageState.Error
167
- else -> ImageState.Loading
168
- }
169
-
170
147
  ImageStateContent(
171
- imageState = imageState,
148
+ imageState = imageState.value,
172
149
  loading = loading,
173
150
  onLoadingChanged = currentOnLoadingChanged.value
174
151
  )
@@ -0,0 +1,81 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.Image
4
+ import androidx.compose.runtime.Composable
5
+ import androidx.compose.runtime.DisposableEffect
6
+ import androidx.compose.runtime.LaunchedEffect
7
+ import androidx.compose.runtime.getValue
8
+ import androidx.compose.runtime.mutableStateOf
9
+ import androidx.compose.runtime.remember
10
+ import androidx.compose.runtime.setValue
11
+ import androidx.compose.ui.Alignment
12
+ import androidx.compose.ui.Modifier
13
+ import androidx.compose.ui.graphics.ColorFilter
14
+ import androidx.compose.ui.graphics.DefaultAlpha
15
+ import androidx.compose.ui.graphics.painter.Painter
16
+ import androidx.compose.ui.layout.ContentScale
17
+ import com.github.panpf.sketch.Sketch
18
+ import com.github.panpf.sketch.decode.supportGif
19
+ import com.github.panpf.sketch.fetch.HttpUriFetcher
20
+ import com.github.panpf.sketch.request.ImageRequest
21
+ import com.github.panpf.sketch.request.ImageResult
22
+ import vn.momo.kits.utils.GifImageHttpClient
23
+ import vn.momo.kits.utils.KitsKtorHttpStack
24
+ import vn.momo.kits.utils.toSketchGifPainter
25
+ import vn.momo.kits.utils.rememberSketchPlatformContext
26
+
27
+ @Composable
28
+ internal fun SketchGifImage(
29
+ url: String,
30
+ modifier: Modifier = Modifier,
31
+ contentScale: ContentScale = ContentScale.Crop,
32
+ alignment: Alignment = Alignment.Center,
33
+ colorFilter: ColorFilter? = null,
34
+ alpha: Float = DefaultAlpha,
35
+ onStateChanged: (ImageState) -> Unit,
36
+ ) {
37
+ val platformContext = rememberSketchPlatformContext()
38
+ val sketch = remember(platformContext) { Sketch(platformContext) }
39
+ var painter by remember(url) { mutableStateOf<Painter?>(null) }
40
+
41
+ DisposableEffect(sketch) {
42
+ onDispose { sketch.shutdown() }
43
+ }
44
+
45
+ LaunchedEffect(sketch, platformContext, url) {
46
+ painter = null
47
+ onStateChanged(ImageState.Loading)
48
+
49
+ val request = ImageRequest(platformContext, url) {
50
+ components {
51
+ add(HttpUriFetcher.Factory(KitsKtorHttpStack(GifImageHttpClient.client)))
52
+ supportGif()
53
+ }
54
+ }
55
+
56
+ when (val result = sketch.execute(request)) {
57
+ is ImageResult.Success -> {
58
+ val gifPainter = result.image.toSketchGifPainter()
59
+ if (gifPainter != null) {
60
+ painter = gifPainter
61
+ onStateChanged(ImageState.Success)
62
+ } else {
63
+ onStateChanged(ImageState.Error)
64
+ }
65
+ }
66
+ is ImageResult.Error -> onStateChanged(ImageState.Error)
67
+ }
68
+ }
69
+
70
+ val currentPainter = painter ?: return
71
+ val rememberedPainter = remember(currentPainter) { currentPainter }
72
+ Image(
73
+ modifier = modifier,
74
+ painter = rememberedPainter,
75
+ contentDescription = null,
76
+ contentScale = contentScale,
77
+ alignment = alignment,
78
+ colorFilter = colorFilter,
79
+ alpha = alpha,
80
+ )
81
+ }
@@ -0,0 +1,116 @@
1
+ package vn.momo.kits.utils
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.runtime.RememberObserver
5
+ import androidx.compose.runtime.getValue
6
+ import androidx.compose.runtime.mutableStateOf
7
+ import androidx.compose.runtime.setValue
8
+ import androidx.compose.ui.geometry.Size
9
+ import androidx.compose.ui.graphics.ColorFilter
10
+ import androidx.compose.ui.graphics.DefaultAlpha
11
+ import androidx.compose.ui.graphics.ImageBitmap
12
+ import androidx.compose.ui.graphics.drawscope.DrawScope
13
+ import androidx.compose.ui.graphics.painter.Painter
14
+ import androidx.compose.ui.unit.IntSize
15
+ import com.github.panpf.sketch.AnimatedImage
16
+ import com.github.panpf.sketch.Bitmap
17
+ import com.github.panpf.sketch.Image
18
+ import com.github.panpf.sketch.PlatformContext
19
+ import kotlinx.coroutines.CoroutineScope
20
+ import kotlinx.coroutines.Dispatchers
21
+ import kotlinx.coroutines.Job
22
+ import kotlinx.coroutines.cancel
23
+ import kotlinx.coroutines.delay
24
+ import kotlinx.coroutines.isActive
25
+ import kotlinx.coroutines.launch
26
+ import kotlinx.coroutines.withContext
27
+ import kotlin.math.ceil
28
+
29
+ @Composable
30
+ internal expect fun rememberSketchPlatformContext(): PlatformContext
31
+
32
+ internal expect fun Bitmap.toComposeImageBitmap(): ImageBitmap
33
+
34
+ internal expect fun Image?.toSketchGifPainter(): Painter?
35
+
36
+ internal class SketchAnimatedImagePainter(
37
+ private val animatedImage: AnimatedImage,
38
+ ) : Painter(), RememberObserver {
39
+ private var alpha: Float = DefaultAlpha
40
+ private var colorFilter: ColorFilter? = null
41
+ private var frame by mutableStateOf<ImageBitmap?>(null)
42
+ private var scope: CoroutineScope? = null
43
+ private var playJob: Job? = null
44
+
45
+ override val intrinsicSize: Size = Size(
46
+ width = animatedImage.width.toFloat(),
47
+ height = animatedImage.height.toFloat(),
48
+ )
49
+
50
+ override fun DrawScope.onDraw() {
51
+ val currentFrame = frame ?: return
52
+ drawImage(
53
+ image = currentFrame,
54
+ dstSize = IntSize(
55
+ width = ceil(size.width).toInt(),
56
+ height = ceil(size.height).toInt(),
57
+ ),
58
+ alpha = alpha,
59
+ colorFilter = colorFilter,
60
+ )
61
+ }
62
+
63
+ override fun applyAlpha(alpha: Float): Boolean {
64
+ this.alpha = alpha
65
+ return true
66
+ }
67
+
68
+ override fun applyColorFilter(colorFilter: ColorFilter?): Boolean {
69
+ this.colorFilter = colorFilter
70
+ return true
71
+ }
72
+
73
+ override fun onRemembered() {
74
+ if (scope != null) return
75
+ val newScope = CoroutineScope(Dispatchers.Main)
76
+ scope = newScope
77
+ playJob = newScope.launch {
78
+ play()
79
+ }
80
+ }
81
+
82
+ override fun onAbandoned() = onForgotten()
83
+
84
+ override fun onForgotten() {
85
+ playJob?.cancel()
86
+ playJob = null
87
+ scope?.cancel()
88
+ scope = null
89
+ }
90
+
91
+ private suspend fun play() {
92
+ val frameCount = animatedImage.frameCount
93
+ if (frameCount <= 0) return
94
+
95
+ var loopIndex = 0
96
+ while (scope?.isActive == true && shouldContinue(loopIndex)) {
97
+ for (frameIndex in 0 until frameCount) {
98
+ if (scope?.isActive != true) return
99
+ frame = readFrame(frameIndex)
100
+ delay(animatedImage.frameDurations.getOrNull(frameIndex)?.toLong()?.takeIf { it > 0 } ?: 100L)
101
+ }
102
+ loopIndex++
103
+ }
104
+ }
105
+
106
+ private fun shouldContinue(loopIndex: Int): Boolean {
107
+ val repeatCount = animatedImage.repeatCount
108
+ return repeatCount <= 0 || loopIndex <= repeatCount
109
+ }
110
+
111
+ private suspend fun readFrame(frameIndex: Int): ImageBitmap = withContext(Dispatchers.Default) {
112
+ animatedImage.createFrameBitmap().also {
113
+ animatedImage.readFrame(it, frameIndex)
114
+ }.toComposeImageBitmap()
115
+ }
116
+ }
@@ -0,0 +1,23 @@
1
+ package vn.momo.kits.utils
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.ui.graphics.ImageBitmap
5
+ import androidx.compose.ui.graphics.asComposeImageBitmap
6
+ import androidx.compose.ui.graphics.painter.Painter
7
+ import com.github.panpf.sketch.AnimatedImage
8
+ import com.github.panpf.sketch.Bitmap
9
+ import com.github.panpf.sketch.Image
10
+ import com.github.panpf.sketch.PlatformContext
11
+
12
+ @Composable
13
+ internal actual fun rememberSketchPlatformContext(): PlatformContext {
14
+ return PlatformContext.INSTANCE
15
+ }
16
+
17
+ internal actual fun Bitmap.toComposeImageBitmap(): ImageBitmap {
18
+ return asComposeImageBitmap()
19
+ }
20
+
21
+ internal actual fun Image?.toSketchGifPainter(): Painter? {
22
+ return if (this is AnimatedImage) SketchAnimatedImagePainter(this) else null
23
+ }
@@ -54,7 +54,7 @@ androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "a
54
54
  androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
55
55
  material = { module = "com.google.android.material:material", version.ref = "material" }
56
56
 
57
- sketch-compose = { module = "io.github.panpf.sketch4:sketch-compose", version.ref = "sketch" }
57
+ sketch-core = { module = "io.github.panpf.sketch4:sketch-core", version.ref = "sketch" }
58
58
  sketch-http-core = { module = "io.github.panpf.sketch4:sketch-http-core", version.ref = "sketch" }
59
59
  sketch-animated-gif = { module = "io.github.panpf.sketch4:sketch-animated-gif", version.ref = "sketch" }
60
60
  [plugins]
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.162.1-gif.10
21
+ version=0.162.1-gif.11
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.162.1-gif.10-debug",
3
+ "version": "0.162.1-gif.11-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},