react-native-fast-image 9.3.6 → 10.0.0
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.
- package/README.md +87 -61
- package/RNFastImage.podspec +14 -9
- package/android/build.gradle +21 -47
- package/android/consumer-rules.pro +7 -7
- package/android/src/main/AndroidManifest.xml +1 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimatable.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimated.java +2 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageApng.java +2 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageBlur.java +4 -6
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheControl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheLimits.java +4 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheWrite.java +1 -1
- package/android/src/main/java/com/fastimage/FastImageCrop.java +92 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageEvent.java +2 -3
- package/android/src/main/java/com/fastimage/FastImageEvents.java +74 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGif.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlide.java +3 -3
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlideModule.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageKeyedGlideUrl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageOkHttpProgressGlideModule.java +36 -9
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageRequestListener.java +6 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSharedDownloads.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSource.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSourceSize.java +12 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvg.java +3 -3
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvgRenderer.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageUrl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewConverter.java +7 -8
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +591 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewModule.java +31 -28
- package/android/src/main/java/com/fastimage/FastImageViewPackage.java +51 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewWithUrl.java +362 -129
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageWebGlideUrl.java +1 -1
- package/app.plugin.js +2 -7
- package/dist/index.cjs.js +276 -157
- package/dist/index.cjs.js.flow +33 -17
- package/dist/index.cjs.web.js +211 -61
- package/dist/index.cjs.web.js.flow +33 -17
- package/dist/index.d.ts +83 -58
- package/dist/index.js +278 -159
- package/dist/index.js.flow +33 -17
- package/dist/index.web.js +212 -62
- package/dist/index.web.js.flow +33 -17
- package/dist/objectFit.d.ts +2 -2
- package/dist/objectPosition.d.ts +42 -0
- package/dist/specs/FastImageViewNativeComponent.d.ts +48 -0
- package/dist/specs/NativeFastImageModule.d.ts +11 -0
- package/dist/warnings.d.ts +3 -0
- package/ios/FastImage/FFFDownsampledImage.h +4 -8
- package/ios/FastImage/FFFDownsampledImage.m +0 -34
- package/ios/FastImage/FFFastImageModule.h +8 -0
- package/ios/FastImage/{FFFastImageViewManager.m → FFFastImageModule.mm} +70 -59
- package/ios/FastImage/FFFastImageView.h +36 -7
- package/ios/FastImage/FFFastImageView.m +201 -164
- package/ios/FastImage/FFFastImageViewComponentView.h +11 -0
- package/ios/FastImage/FFFastImageViewComponentView.mm +325 -0
- package/ios/FastImage/RCTConvert+FFFastImage.h +1 -1
- package/ios/FastImage/RCTConvert+FFFastImage.m +7 -10
- package/package.json +28 -9
- package/src/constants.ts +28 -0
- package/src/index.js.flow +179 -0
- package/src/index.tsx +1103 -0
- package/src/index.web.tsx +548 -0
- package/src/objectFit.ts +48 -0
- package/src/objectPosition.ts +243 -0
- package/src/specs/FastImageViewNativeComponent.ts +83 -0
- package/src/specs/NativeFastImageModule.ts +35 -0
- package/src/warnings.ts +37 -0
- package/android/src/main/AndroidManifestNew.xml +0 -3
- package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +0 -63
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +0 -49
- package/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java +0 -18
- package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +0 -21
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +0 -379
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewPackage.java +0 -27
- package/ios/FastImage/FFFastImageViewManager.h +0 -5
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// objectPosition, as CSS's object-position, shared by the native (index.tsx)
|
|
2
|
+
// and web (index.web.tsx) versions: a CSS position string or an object of
|
|
3
|
+
// edges, resolved to what CSS computes for each axis.
|
|
4
|
+
import { warnOnce } from './warnings'
|
|
5
|
+
|
|
6
|
+
// Metro's (and React Native's Jest preset's) development flag; other web
|
|
7
|
+
// bundlers may not define it.
|
|
8
|
+
declare const __DEV__: boolean | undefined
|
|
9
|
+
|
|
10
|
+
/** A length in points (dp on Android), or a percentage: see [`objectPosition`](#objectposition). */
|
|
11
|
+
export type PositionLength = number | `${number}%`
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Where the image is in the view: see [`objectPosition`](#objectposition).
|
|
15
|
+
*/
|
|
16
|
+
export type ObjectPosition =
|
|
17
|
+
| string
|
|
18
|
+
| ((
|
|
19
|
+
| { top?: PositionLength; bottom?: never }
|
|
20
|
+
| { bottom: PositionLength; top?: never }
|
|
21
|
+
) &
|
|
22
|
+
(
|
|
23
|
+
| { left?: PositionLength; right?: never }
|
|
24
|
+
| { right: PositionLength; left?: never }
|
|
25
|
+
))
|
|
26
|
+
|
|
27
|
+
// One axis as CSS computes it, calc(fraction * 100% + offset): the image's
|
|
28
|
+
// edge is at (view - image) * fraction + offset, in points.
|
|
29
|
+
export type ResolvedAxis = { fraction: number; offset: number }
|
|
30
|
+
export type ResolvedPosition = { x: ResolvedAxis; y: ResolvedAxis }
|
|
31
|
+
|
|
32
|
+
const START: ResolvedAxis = { fraction: 0, offset: 0 }
|
|
33
|
+
const CENTER: ResolvedAxis = { fraction: 0.5, offset: 0 }
|
|
34
|
+
const END: ResolvedAxis = { fraction: 1, offset: 0 }
|
|
35
|
+
|
|
36
|
+
/** The default, `50% 50%`. */
|
|
37
|
+
export const CENTERED: ResolvedPosition = { x: CENTER, y: CENTER }
|
|
38
|
+
|
|
39
|
+
type Keyword = 'left' | 'right' | 'top' | 'bottom' | 'center'
|
|
40
|
+
type Token = { keyword: Keyword } | { length: ResolvedAxis }
|
|
41
|
+
|
|
42
|
+
const NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i
|
|
43
|
+
|
|
44
|
+
// A length in px, a percentage, or 0 without a unit.
|
|
45
|
+
function length(text: string): ResolvedAxis | null {
|
|
46
|
+
const lower = text.toLowerCase()
|
|
47
|
+
if (lower.endsWith('%')) {
|
|
48
|
+
const number = lower.slice(0, -1)
|
|
49
|
+
return NUMBER.test(number)
|
|
50
|
+
? { fraction: Number(number) / 100, offset: 0 }
|
|
51
|
+
: null
|
|
52
|
+
}
|
|
53
|
+
if (lower.endsWith('px')) {
|
|
54
|
+
const number = lower.slice(0, -2)
|
|
55
|
+
return NUMBER.test(number)
|
|
56
|
+
? { fraction: 0, offset: Number(number) }
|
|
57
|
+
: null
|
|
58
|
+
}
|
|
59
|
+
return NUMBER.test(lower) && Number(lower) === 0 ? START : null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function token(text: string): Token | null {
|
|
63
|
+
const lower = text.toLowerCase()
|
|
64
|
+
if (
|
|
65
|
+
lower === 'left' ||
|
|
66
|
+
lower === 'right' ||
|
|
67
|
+
lower === 'top' ||
|
|
68
|
+
lower === 'bottom' ||
|
|
69
|
+
lower === 'center'
|
|
70
|
+
) {
|
|
71
|
+
return { keyword: lower }
|
|
72
|
+
}
|
|
73
|
+
const axis = length(text)
|
|
74
|
+
return axis ? { length: axis } : null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const X: Partial<Record<Keyword, ResolvedAxis>> = {
|
|
78
|
+
left: START,
|
|
79
|
+
center: CENTER,
|
|
80
|
+
right: END,
|
|
81
|
+
}
|
|
82
|
+
const Y: Partial<Record<Keyword, ResolvedAxis>> = {
|
|
83
|
+
top: START,
|
|
84
|
+
center: CENTER,
|
|
85
|
+
bottom: END,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const x = (t: Token) => ('length' in t ? t.length : X[t.keyword])
|
|
89
|
+
const y = (t: Token) => ('length' in t ? t.length : Y[t.keyword])
|
|
90
|
+
|
|
91
|
+
// An offset from an edge: from the left or top as it is, from the right or
|
|
92
|
+
// bottom as 100% minus it.
|
|
93
|
+
function fromEdge(edge: Keyword, axis: ResolvedAxis): ResolvedAxis {
|
|
94
|
+
return edge === 'left' || edge === 'top'
|
|
95
|
+
? axis
|
|
96
|
+
: { fraction: 1 - axis.fraction, offset: -axis.offset || 0 }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// CSS's <position> (CSS Values 4), as object-position takes it: 1, 2 or 4
|
|
100
|
+
// values, keywords in any case, lengths in px or %, 0 without a unit. Null if
|
|
101
|
+
// it isn't one (CSS ignores it then).
|
|
102
|
+
function parse(text: string): ResolvedPosition | null {
|
|
103
|
+
const tokens: Token[] = []
|
|
104
|
+
for (const part of text.trim().split(/\s+/)) {
|
|
105
|
+
const t = token(part)
|
|
106
|
+
if (!t) return null
|
|
107
|
+
tokens.push(t)
|
|
108
|
+
}
|
|
109
|
+
if (tokens.length === 1) {
|
|
110
|
+
const [a] = tokens
|
|
111
|
+
if ('keyword' in a && (a.keyword === 'top' || a.keyword === 'bottom')) {
|
|
112
|
+
return { x: CENTER, y: Y[a.keyword]! }
|
|
113
|
+
}
|
|
114
|
+
return { x: x(a)!, y: CENTER }
|
|
115
|
+
}
|
|
116
|
+
if (tokens.length === 2) {
|
|
117
|
+
const [a, b] = tokens
|
|
118
|
+
// [left | center | right | <length-percentage>]
|
|
119
|
+
// [top | center | bottom | <length-percentage>]
|
|
120
|
+
const ax = x(a)
|
|
121
|
+
const by = y(b)
|
|
122
|
+
if (ax && by) return { x: ax, y: by }
|
|
123
|
+
// Or two keywords in either order: [left | center | right] &&
|
|
124
|
+
// [top | center | bottom].
|
|
125
|
+
if ('keyword' in a && 'keyword' in b) {
|
|
126
|
+
const bx = X[b.keyword]
|
|
127
|
+
const ay = Y[a.keyword]
|
|
128
|
+
if (bx && ay) return { x: bx, y: ay }
|
|
129
|
+
}
|
|
130
|
+
return null
|
|
131
|
+
}
|
|
132
|
+
if (tokens.length === 4) {
|
|
133
|
+
// [[left | right] <length-percentage>] &&
|
|
134
|
+
// [[top | bottom] <length-percentage>]
|
|
135
|
+
const [a, b, c, d] = tokens
|
|
136
|
+
if (
|
|
137
|
+
!(
|
|
138
|
+
'keyword' in a &&
|
|
139
|
+
'length' in b &&
|
|
140
|
+
'keyword' in c &&
|
|
141
|
+
'length' in d
|
|
142
|
+
)
|
|
143
|
+
) {
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
const horizontal = (k: Keyword) => k === 'left' || k === 'right'
|
|
147
|
+
const vertical = (k: Keyword) => k === 'top' || k === 'bottom'
|
|
148
|
+
if (horizontal(a.keyword) && vertical(c.keyword)) {
|
|
149
|
+
return {
|
|
150
|
+
x: fromEdge(a.keyword, b.length),
|
|
151
|
+
y: fromEdge(c.keyword, d.length),
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (vertical(a.keyword) && horizontal(c.keyword)) {
|
|
155
|
+
return {
|
|
156
|
+
x: fromEdge(c.keyword, d.length),
|
|
157
|
+
y: fromEdge(a.keyword, b.length),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const parsed = new Map<string, ResolvedPosition | null>()
|
|
165
|
+
|
|
166
|
+
function objectLength(value: unknown): ResolvedAxis | null {
|
|
167
|
+
if (typeof value === 'number') {
|
|
168
|
+
return Number.isFinite(value) ? { fraction: 0, offset: value } : null
|
|
169
|
+
}
|
|
170
|
+
return typeof value === 'string' && value.endsWith('%')
|
|
171
|
+
? length(value)
|
|
172
|
+
: null
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The position for objectPosition, or null if it isn't valid. A string is
|
|
177
|
+
* CSS's syntax; an object has one of `top` and `bottom` and one of `left`
|
|
178
|
+
* and `right`, each in points or a percentage, and a missing axis is
|
|
179
|
+
* centered. Unset is centered.
|
|
180
|
+
*/
|
|
181
|
+
export function resolveObjectPosition(
|
|
182
|
+
position: ObjectPosition | null | undefined,
|
|
183
|
+
): ResolvedPosition | null {
|
|
184
|
+
if (position == null) return CENTERED
|
|
185
|
+
if (typeof position === 'string') {
|
|
186
|
+
// The same few strings, in every row of a list.
|
|
187
|
+
let result = parsed.get(position)
|
|
188
|
+
if (result === undefined) {
|
|
189
|
+
result = parse(position)
|
|
190
|
+
if (parsed.size < 100) parsed.set(position, result)
|
|
191
|
+
}
|
|
192
|
+
return result
|
|
193
|
+
}
|
|
194
|
+
if (typeof position !== 'object') return null
|
|
195
|
+
const edges = position as Record<string, unknown>
|
|
196
|
+
const axis = (start: Keyword, end: Keyword): ResolvedAxis | null => {
|
|
197
|
+
const hasStart = edges[start] != null
|
|
198
|
+
const hasEnd = edges[end] != null
|
|
199
|
+
if (hasStart && hasEnd) return null
|
|
200
|
+
if (!hasStart && !hasEnd) return CENTER
|
|
201
|
+
const edge = hasStart ? start : end
|
|
202
|
+
const value = objectLength(edges[edge])
|
|
203
|
+
return value && fromEdge(edge, value)
|
|
204
|
+
}
|
|
205
|
+
const resolvedX = axis('left', 'right')
|
|
206
|
+
const resolvedY = axis('top', 'bottom')
|
|
207
|
+
return resolvedX && resolvedY ? { x: resolvedX, y: resolvedY } : null
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Without float noise (1 - 1.2 is -0.19999999999999996).
|
|
211
|
+
const round = (n: number) => Math.round(n * 1e6) / 1e6
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The position for objectPosition, or centered if it isn't valid (as CSS
|
|
215
|
+
* ignores it), with a warning in development.
|
|
216
|
+
*/
|
|
217
|
+
export function objectPositionOrCentered(
|
|
218
|
+
position: ObjectPosition | null | undefined,
|
|
219
|
+
): ResolvedPosition {
|
|
220
|
+
const resolved = resolveObjectPosition(position)
|
|
221
|
+
if (resolved) return resolved
|
|
222
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
223
|
+
const value = JSON.stringify(position)
|
|
224
|
+
warnOnce(
|
|
225
|
+
`objectPosition ${value}`,
|
|
226
|
+
`react-native-fast-image: objectPosition ${value} isn't a position, so the image is centered. Use CSS's syntax with px or % (e.g. 'right 10px bottom 20%'), or { top | bottom, left | right } in points or percentages.`,
|
|
227
|
+
'warn',
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
return CENTERED
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The position as CSS (for the web version's object-position). */
|
|
234
|
+
export function cssPosition({ x, y }: ResolvedPosition) {
|
|
235
|
+
const axis = ({ fraction, offset }: ResolvedAxis) => {
|
|
236
|
+
const percent = round(fraction * 100)
|
|
237
|
+
const px = round(offset)
|
|
238
|
+
if (px === 0) return `${percent}%`
|
|
239
|
+
if (percent === 0) return `${px}px`
|
|
240
|
+
return `calc(${percent}% ${px < 0 ? '-' : '+'} ${Math.abs(px)}px)`
|
|
241
|
+
}
|
|
242
|
+
return `${axis(x)} ${axis(y)}`
|
|
243
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// The native image view, from React Native's Codegen (the New Architecture's
|
|
2
|
+
// component). FastImage (index.tsx) renders it as its only view, and turns
|
|
3
|
+
// its props into these.
|
|
4
|
+
import type {
|
|
5
|
+
CodegenTypes,
|
|
6
|
+
ColorValue,
|
|
7
|
+
HostComponent,
|
|
8
|
+
ViewProps,
|
|
9
|
+
} from 'react-native'
|
|
10
|
+
// React Native's Babel plugin replaces the call below with the component's
|
|
11
|
+
// view config, so the import isn't used when the app runs.
|
|
12
|
+
import { codegenNativeComponent } from 'react-native'
|
|
13
|
+
|
|
14
|
+
type LoadStartEvent = Readonly<{}>
|
|
15
|
+
|
|
16
|
+
// progress is loaded / total, from 0 to 1 (the views don't send an unknown
|
|
17
|
+
// total).
|
|
18
|
+
type ProgressEvent = Readonly<{
|
|
19
|
+
loaded: CodegenTypes.Double
|
|
20
|
+
total: CodegenTypes.Double
|
|
21
|
+
progress: CodegenTypes.Double
|
|
22
|
+
}>
|
|
23
|
+
|
|
24
|
+
// The image's own size.
|
|
25
|
+
type LoadEvent = Readonly<{
|
|
26
|
+
width: CodegenTypes.Double
|
|
27
|
+
height: CodegenTypes.Double
|
|
28
|
+
}>
|
|
29
|
+
|
|
30
|
+
type ErrorEvent = Readonly<{
|
|
31
|
+
error: string
|
|
32
|
+
}>
|
|
33
|
+
|
|
34
|
+
// onLoad's or onError's event, with whether it loaded.
|
|
35
|
+
type LoadEndEvent = Readonly<{
|
|
36
|
+
ok: boolean
|
|
37
|
+
width?: CodegenTypes.Double
|
|
38
|
+
height?: CodegenTypes.Double
|
|
39
|
+
error?: string
|
|
40
|
+
}>
|
|
41
|
+
|
|
42
|
+
export interface NativeProps extends ViewProps {
|
|
43
|
+
// A source ({ uri, headers, priority, cache, ... }), as the native
|
|
44
|
+
// sides read it.
|
|
45
|
+
source?: CodegenTypes.UnsafeMixed
|
|
46
|
+
// Several sources (sizes of one image), picked from for the view's size.
|
|
47
|
+
sources?: CodegenTypes.UnsafeMixed
|
|
48
|
+
// A resolved asset source ({ uri, width, height, scale }).
|
|
49
|
+
defaultSource?: CodegenTypes.UnsafeMixed
|
|
50
|
+
// objectFit, resolved (fill, contain, cover, none or scale-down).
|
|
51
|
+
objectFit?: CodegenTypes.WithDefault<string, 'cover'>
|
|
52
|
+
// objectPosition, as CSS computes it: on each axis the image's left or top
|
|
53
|
+
// edge is at (view - image) * fraction + offset, in points.
|
|
54
|
+
objectPositionX?: CodegenTypes.WithDefault<CodegenTypes.Float, 0.5>
|
|
55
|
+
objectPositionXOffset?: CodegenTypes.Float
|
|
56
|
+
objectPositionY?: CodegenTypes.WithDefault<CodegenTypes.Float, 0.5>
|
|
57
|
+
objectPositionYOffset?: CodegenTypes.Float
|
|
58
|
+
tintColor?: ColorValue
|
|
59
|
+
recyclingKey?: string
|
|
60
|
+
loopCount?: CodegenTypes.WithDefault<CodegenTypes.Int32, -1>
|
|
61
|
+
imageRendering?: string
|
|
62
|
+
paused?: boolean
|
|
63
|
+
transitionDuration?: CodegenTypes.Double
|
|
64
|
+
transitionBetweenImages?: boolean
|
|
65
|
+
transitionSkipOnCacheHit?: string
|
|
66
|
+
downsample?: CodegenTypes.WithDefault<boolean, true>
|
|
67
|
+
blurRadius?: CodegenTypes.Float
|
|
68
|
+
// The events JS has a handler for, as bits: 1 onFastImageLoadStart,
|
|
69
|
+
// 2 onFastImageProgress, 4 onFastImageLoad, 8 onFastImageError, 16
|
|
70
|
+
// onFastImageLoadEnd. The native views only send those (most images have
|
|
71
|
+
// no handlers), and only track progress with 2. (Not `events`: iOS's
|
|
72
|
+
// ViewProps already has a member by that name.)
|
|
73
|
+
handledEvents?: CodegenTypes.Int32
|
|
74
|
+
onFastImageLoadStart?: CodegenTypes.DirectEventHandler<LoadStartEvent>
|
|
75
|
+
onFastImageProgress?: CodegenTypes.DirectEventHandler<ProgressEvent>
|
|
76
|
+
onFastImageLoad?: CodegenTypes.DirectEventHandler<LoadEvent>
|
|
77
|
+
onFastImageError?: CodegenTypes.DirectEventHandler<ErrorEvent>
|
|
78
|
+
onFastImageLoadEnd?: CodegenTypes.DirectEventHandler<LoadEndEvent>
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default codegenNativeComponent<NativeProps>(
|
|
82
|
+
'FastImageView',
|
|
83
|
+
) as HostComponent<NativeProps>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// FastImage's functions (preload, the caches), as a TurboModule from React
|
|
2
|
+
// Native's Codegen. FastImage (index.tsx) checks their arguments and shapes
|
|
3
|
+
// their results.
|
|
4
|
+
import type { CodegenTypes, TurboModule } from 'react-native'
|
|
5
|
+
import { TurboModuleRegistry } from 'react-native'
|
|
6
|
+
|
|
7
|
+
export interface Spec extends TurboModule {
|
|
8
|
+
// A result per source, in order: { ok, width, height } or { ok, error }.
|
|
9
|
+
preload(
|
|
10
|
+
sources: Array<CodegenTypes.UnsafeObject>,
|
|
11
|
+
): Promise<Array<CodegenTypes.UnsafeObject>>
|
|
12
|
+
// { ok, path } or { ok, error }.
|
|
13
|
+
getCachePath(
|
|
14
|
+
source: CodegenTypes.UnsafeObject,
|
|
15
|
+
): Promise<CodegenTypes.UnsafeObject>
|
|
16
|
+
// { ok, path } or { ok, error }.
|
|
17
|
+
writeToCache(
|
|
18
|
+
source: CodegenTypes.UnsafeObject,
|
|
19
|
+
file: string,
|
|
20
|
+
): Promise<CodegenTypes.UnsafeObject>
|
|
21
|
+
// { maxDiskSize: number, ..., reset: [names] }: the limits to set, and the
|
|
22
|
+
// names of those to reset (null isn't sent: iOS would drop it). Resolves
|
|
23
|
+
// with the limits in effect.
|
|
24
|
+
configureCache(
|
|
25
|
+
limits: CodegenTypes.UnsafeObject,
|
|
26
|
+
): Promise<CodegenTypes.UnsafeObject>
|
|
27
|
+
// { ok } or { ok, error }.
|
|
28
|
+
clearMemoryCache(): Promise<CodegenTypes.UnsafeObject>
|
|
29
|
+
clearDiskCache(): Promise<CodegenTypes.UnsafeObject>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// null where the app doesn't have the native module (Jest, Expo Go, an app
|
|
33
|
+
// not rebuilt after installing): FastImage's functions throw when they're
|
|
34
|
+
// called, rather than importing FastImage throwing.
|
|
35
|
+
export default TurboModuleRegistry.get<Spec>('FastImageModule')
|
package/src/warnings.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// FastImage's development warnings and errors, each once per app (a list would
|
|
2
|
+
// repeat it for every image), shared by the native (index.tsx) and web
|
|
3
|
+
// (index.web.tsx) versions.
|
|
4
|
+
import { Children } from 'react'
|
|
5
|
+
import type { ReactNode } from 'react'
|
|
6
|
+
|
|
7
|
+
const warned = new Set<string>()
|
|
8
|
+
|
|
9
|
+
export function warnOnce(
|
|
10
|
+
key: string,
|
|
11
|
+
message: string,
|
|
12
|
+
level: 'warn' | 'error',
|
|
13
|
+
) {
|
|
14
|
+
if (!warned.has(key)) {
|
|
15
|
+
warned.add(key)
|
|
16
|
+
console[level](message)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// For the tests (test/setup.ts): `bun test` loads this module once for the
|
|
21
|
+
// native and web versions' tests together.
|
|
22
|
+
export function resetWarnings() {
|
|
23
|
+
warned.clear()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// FastImage doesn't render children: on native it's a single view, and the
|
|
27
|
+
// web version does the same. toArray, as a Touchable clones its child with
|
|
28
|
+
// children: undefined.
|
|
29
|
+
export function warnIfChildren(children: unknown) {
|
|
30
|
+
if (Children.toArray(children as ReactNode).length > 0) {
|
|
31
|
+
warnOnce(
|
|
32
|
+
'children',
|
|
33
|
+
"react-native-fast-image: FastImage doesn't render children (since 10.0): use FastImageBackground for content over an image.",
|
|
34
|
+
'error',
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
package com.dylanvann.fastimage;
|
|
2
|
-
|
|
3
|
-
import android.text.TextUtils;
|
|
4
|
-
import android.webkit.CookieManager;
|
|
5
|
-
|
|
6
|
-
import androidx.annotation.Nullable;
|
|
7
|
-
|
|
8
|
-
import java.net.CookieHandler;
|
|
9
|
-
import java.net.URI;
|
|
10
|
-
import java.util.Collections;
|
|
11
|
-
import java.util.List;
|
|
12
|
-
import java.util.Map;
|
|
13
|
-
|
|
14
|
-
// Reads and stores cookies in Android's cookie store (the WebView's
|
|
15
|
-
// CookieManager), which React Native's networking (fetch) and its Image
|
|
16
|
-
// component use. Image requests then send the cookies the app's other requests
|
|
17
|
-
// got, as on iOS. Does what React Native's ForwardingCookieHandler does,
|
|
18
|
-
// without the ReactContext that one needs (up to at least 0.77). Used through
|
|
19
|
-
// OkHttp's JavaNetCookieJar, which parses the cookies.
|
|
20
|
-
final class FastImageCookieHandler extends CookieHandler {
|
|
21
|
-
@Nullable
|
|
22
|
-
private CookieManager cookieManager;
|
|
23
|
-
|
|
24
|
-
@Override
|
|
25
|
-
public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) {
|
|
26
|
-
CookieManager cookieManager = cookieManager();
|
|
27
|
-
if (cookieManager == null) return Collections.emptyMap();
|
|
28
|
-
String cookies = cookieManager.getCookie(uri.toString());
|
|
29
|
-
if (TextUtils.isEmpty(cookies)) return Collections.emptyMap();
|
|
30
|
-
return Collections.singletonMap("Cookie", Collections.singletonList(cookies));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@Override
|
|
34
|
-
public void put(URI uri, Map<String, List<String>> responseHeaders) {
|
|
35
|
-
CookieManager cookieManager = cookieManager();
|
|
36
|
-
if (cookieManager == null) return;
|
|
37
|
-
String url = uri.toString();
|
|
38
|
-
boolean added = false;
|
|
39
|
-
for (Map.Entry<String, List<String>> header : responseHeaders.entrySet()) {
|
|
40
|
-
String name = header.getKey();
|
|
41
|
-
if (!"Set-Cookie".equalsIgnoreCase(name) && !"Set-Cookie2".equalsIgnoreCase(name)) continue;
|
|
42
|
-
for (String cookie : header.getValue()) {
|
|
43
|
-
cookieManager.setCookie(url, cookie);
|
|
44
|
-
added = true;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (added) cookieManager.flush();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// null while the WebView isn't available (not installed, or being
|
|
51
|
-
// updated): requests go without cookies then, instead of failing.
|
|
52
|
-
@Nullable
|
|
53
|
-
private synchronized CookieManager cookieManager() {
|
|
54
|
-
if (cookieManager == null) {
|
|
55
|
-
try {
|
|
56
|
-
cookieManager = CookieManager.getInstance();
|
|
57
|
-
} catch (Exception e) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return cookieManager;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
package com.dylanvann.fastimage;
|
|
2
|
-
|
|
3
|
-
import android.view.View;
|
|
4
|
-
|
|
5
|
-
import com.facebook.react.bridge.Arguments;
|
|
6
|
-
import com.facebook.react.bridge.ReactContext;
|
|
7
|
-
import com.facebook.react.bridge.WritableMap;
|
|
8
|
-
import com.facebook.react.uimanager.UIManagerHelper;
|
|
9
|
-
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
10
|
-
|
|
11
|
-
// Sends FastImage view events through React Native's EventDispatcher.
|
|
12
|
-
final class FastImageEvents {
|
|
13
|
-
private FastImageEvents() {
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
static void send(View view, String name) {
|
|
17
|
-
send(view, name, Arguments.createMap());
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// onLoadEnd with the load's result: ok with the image's size, as onLoad
|
|
21
|
-
// sent it.
|
|
22
|
-
static void sendLoadEnd(View view, int width, int height) {
|
|
23
|
-
WritableMap event = Arguments.createMap();
|
|
24
|
-
event.putBoolean("ok", true);
|
|
25
|
-
event.putInt("width", width);
|
|
26
|
-
event.putInt("height", height);
|
|
27
|
-
send(view, FastImageRequestListener.REACT_ON_LOAD_END_EVENT, event);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// onLoadEnd with the load's result: not ok, with the error onError sent.
|
|
31
|
-
static void sendLoadEnd(View view, String error) {
|
|
32
|
-
WritableMap event = Arguments.createMap();
|
|
33
|
-
event.putBoolean("ok", false);
|
|
34
|
-
event.putString("error", error);
|
|
35
|
-
send(view, FastImageRequestListener.REACT_ON_LOAD_END_EVENT, event);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static void send(View view, String name, WritableMap data) {
|
|
39
|
-
ReactContext context = FastImageViewManager.getReactContext(view.getContext());
|
|
40
|
-
if (context == null) return;
|
|
41
|
-
// The view's event dispatcher, on either architecture (also
|
|
42
|
-
// bridgeless).
|
|
43
|
-
EventDispatcher dispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.getId());
|
|
44
|
-
if (dispatcher != null) {
|
|
45
|
-
dispatcher.dispatchEvent(new FastImageEvent(
|
|
46
|
-
UIManagerHelper.getSurfaceId(view), view.getId(), name, data));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
package com.dylanvann.fastimage;
|
|
2
|
-
|
|
3
|
-
// No longer used: FastImage sends progress through FastImageViewManager's
|
|
4
|
-
// onDownloadProgress. Kept, as a public type, so code that refers to it still
|
|
5
|
-
// compiles.
|
|
6
|
-
@Deprecated
|
|
7
|
-
public interface FastImageProgressListener {
|
|
8
|
-
|
|
9
|
-
void onProgress(String key, long bytesRead, long expectedLength);
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Control how often the listener needs an update. 0% and 100% will always be dispatched.
|
|
13
|
-
*
|
|
14
|
-
* @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress)
|
|
15
|
-
*/
|
|
16
|
-
float getGranularityPercentage();
|
|
17
|
-
|
|
18
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
package com.dylanvann.fastimage;
|
|
2
|
-
|
|
3
|
-
import com.facebook.react.uimanager.LayoutShadowNode;
|
|
4
|
-
import com.facebook.react.uimanager.UIViewOperationQueue;
|
|
5
|
-
|
|
6
|
-
// Legacy architecture only. React Native only lays out a view when its frame
|
|
7
|
-
// changes, so a view that stays 0×0 at its parent's origin is never laid out,
|
|
8
|
-
// and Glide waits for its size forever (#865). When the computed size is zero,
|
|
9
|
-
// tell the view, so it can lay itself out and load as it does on the New
|
|
10
|
-
// Architecture. Remove with legacy architecture support.
|
|
11
|
-
public class FastImageShadowNode extends LayoutShadowNode {
|
|
12
|
-
static final Object ZERO_LAYOUT = new Object();
|
|
13
|
-
|
|
14
|
-
@Override
|
|
15
|
-
public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) {
|
|
16
|
-
super.onCollectExtraUpdates(uiViewOperationQueue);
|
|
17
|
-
if (Math.round(getLayoutWidth()) == 0 && Math.round(getLayoutHeight()) == 0) {
|
|
18
|
-
uiViewOperationQueue.enqueueUpdateExtraData(getReactTag(), ZERO_LAYOUT);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|