Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: onProgress in android #362

Merged
merged 2 commits into from
Oct 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 42 additions & 7 deletions android/src/main/java/com/turboimage/TurboImageViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,49 @@ import coil.load
import coil.memory.MemoryCache
import coil.size.Dimension
import coil.size.Size
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.common.MapBuilder
import com.facebook.react.uimanager.PixelUtil
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.annotations.ReactProp
import okhttp3.Headers
import com.turboimage.decoder.APNGDecoder
import com.turboimage.events.ProgressEvent
import com.turboimage.events.interceptor.ProgressInterceptor
import com.turboimage.events.interceptor.ProgressListener
import okhttp3.OkHttpClient

class TurboImageViewManager : SimpleViewManager<TurboImageView>() {
override fun getName() = REACT_CLASS

override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? {
return MapBuilder.of(
"onProgress", MapBuilder.of("registrationName", "onProgress")
)
}

override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
return mapOf(
"onFailure" to mapOf(
"onStart" to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onFailure"
"bubbled" to "onStart"
)
), "onSuccess" to mapOf(
),
"onSuccess" to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onSuccess"
)
), "onStart" to mapOf(
),
"onFailure" to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onStart"
"bubbled" to "onFailure"
)
), "onCompletion" to mapOf(
),
"onCompletion" to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onCompletion"
)
Expand All @@ -58,8 +75,26 @@ class TurboImageViewManager : SimpleViewManager<TurboImageView>() {
CrossfadeDrawable.DEFAULT_DURATION
}

val okHttpClient = OkHttpClient.Builder()
.addInterceptor(ProgressInterceptor(object : ProgressListener {
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
val reactContext = view.context as ReactContext
UIManagerHelper.getEventDispatcher(reactContext, view.id)?.let {
val payload = Arguments.createMap().apply {
putDouble("loaded", bytesRead.toDouble())
putDouble("total", contentLength.toDouble())
}
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
it.dispatchEvent(ProgressEvent(surfaceId, view.id, payload))
}
}
}))
.build()

val imageLoader = Coil.imageLoader(view.context).newBuilder()
.respectCacheHeaders(view.cachePolicy == "urlCache").build()
.respectCacheHeaders(view.cachePolicy == "urlCache")
.okHttpClient(okHttpClient)
.build()

view.load(view.uri, imageLoader) {
view.headers?.let { headers(it) }
Expand Down
17 changes: 17 additions & 0 deletions android/src/main/java/com/turboimage/events/ProgressEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.turboimage.events

import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event

class ProgressEvent(surfaceId: Int, viewId: Int, private val payload: WritableMap):
Event<StartEvent>(surfaceId, viewId) {
override fun getEventName(): String {
return EVENT_NAME
}

override fun getEventData() = payload

companion object {
const val EVENT_NAME = "onProgress"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.turboimage.events.interceptor

import okhttp3.Interceptor
import okhttp3.Response

class ProgressInterceptor(private val listener: ProgressListener) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalResponse = chain.proceed(chain.request())
return originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body!!, listener))
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.turboimage.events.interceptor

interface ProgressListener {
fun update(bytesRead: Long, contentLength: Long, done: Boolean)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.turboimage.events.interceptor

import okhttp3.ResponseBody
import okio.Buffer
import okio.BufferedSource
import okio.ForwardingSource
import okio.Source
import okio.buffer

class ProgressResponseBody(
private val responseBody: ResponseBody,
private val progressListener: ProgressListener
) : ResponseBody() {

private val bufferedSource: BufferedSource by lazy {
source(responseBody.source()).buffer()
}

override fun contentType() = responseBody.contentType()

override fun contentLength() = responseBody.contentLength()

override fun source(): BufferedSource = bufferedSource

private fun source(source: Source): Source {
return object : ForwardingSource(source) {
var totalBytesRead = 0L

override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
totalBytesRead += if (bytesRead != -1L) bytesRead else 0
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
return bytesRead
}
}
}
}
17 changes: 14 additions & 3 deletions example/src/screens/events/SuccessScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
type NativeSyntheticEvent,
} from 'react-native';
import React, { useState } from 'react';
import type { Success, TaskState } from 'react-native-turbo-image';
import type { Progress, Success, TaskState } from 'react-native-turbo-image';
import TurboImage from 'react-native-turbo-image';

type Information = {
Expand All @@ -15,6 +15,7 @@ type Information = {
};
const SuccessScreen = () => {
const [start, setStart] = useState(false);
const [progress, setProgress] = useState<string[]>([]);
const [completion, setCompletion] = useState(false);

const [information, setInformation] = useState<Information | null>(null);
Expand All @@ -32,23 +33,33 @@ const SuccessScreen = () => {
setCompletion(nativeEvent.state === 'completed');
};

const handleProgress = ({ nativeEvent }: NativeSyntheticEvent<Progress>) => {
const percentage = `${(
(100 * nativeEvent.loaded) /
nativeEvent.total
).toFixed(2)}%`;
setProgress((prev) => [...prev, percentage]);
};

return (
<View style={styles.container}>
<TurboImage
source={{
uri: 'https://placedog.net/300/300?id=121',
uri: 'https://picsum.photos/id/57/2000',
}}
style={styles.image}
cachePolicy="dataCache"
onStart={handleStart}
onSuccess={handleSuccess}
onCompletion={handleCompletion}
onProgress={handleProgress}
placeholder={{
blurhash: 'UBIr4u9}00Rj?yEzxu%LIQ%1%6xt-ks,tAIU',
thumbhash: 'lOcNFwYGpmmNdIiJh4aHiId4aPwmlm8E',
}}
/>

{start && <Text>Start at {Date()}</Text>}
{progress.length > 0 && <Text>Progress: {progress}</Text>}
{information?.width && <Text>width: {information?.width}</Text>}
{information?.height && <Text>height: {information?.height}</Text>}
{information?.source && <Text>source: {information?.source}</Text>}
Expand Down
1 change: 1 addition & 0 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type {
Format,
Success,
Failure,
Progress,
TaskState,
ResizeMode,
Indicator,
Expand Down
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export type TaskState = {
state: State;
};

export type Progress = {
loaded: number;
total: number;
};

export type Success = {
width: number;
height: number;
Expand Down Expand Up @@ -65,6 +70,7 @@ export interface TurboImageProps extends AccessibilityProps, ViewProps {
format?: Format;
onStart?: (result: NativeSyntheticEvent<TaskState>) => void;
onSuccess?: (result: NativeSyntheticEvent<Success>) => void;
onProgress?: (result: NativeSyntheticEvent<Progress>) => void;
onFailure?: (result: NativeSyntheticEvent<Failure>) => void;
onCompletion?: (result: NativeSyntheticEvent<TaskState>) => void;
}
Expand Down
Loading