react-native-theme-reveal 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +734 -2
- package/android/build.gradle +32 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/baseline-prof.txt +4 -0
- package/android/src/main/java/expo/modules/themereveal/CapturePlan.kt +199 -0
- package/android/src/main/java/expo/modules/themereveal/CaptureRules.kt +50 -0
- package/android/src/main/java/expo/modules/themereveal/GpuSnapshot.kt +367 -0
- package/android/src/main/java/expo/modules/themereveal/KeptBitmaps.kt +102 -0
- package/android/src/main/java/expo/modules/themereveal/KeptTarget.kt +103 -0
- package/android/src/main/java/expo/modules/themereveal/Motion.kt +79 -0
- package/android/src/main/java/expo/modules/themereveal/Once.kt +13 -0
- package/android/src/main/java/expo/modules/themereveal/RenderThreadReveal.kt +107 -0
- package/android/src/main/java/expo/modules/themereveal/RevealOverlayView.kt +192 -0
- package/android/src/main/java/expo/modules/themereveal/SecondSnapshotWait.kt +112 -0
- package/android/src/main/java/expo/modules/themereveal/ShapePath.kt +47 -0
- package/android/src/main/java/expo/modules/themereveal/ThemeRevealModule.kt +904 -0
- package/android/src/main/java/expo/modules/themereveal/TransitionArgs.kt +50 -0
- package/android/src/main/java/expo/modules/themereveal/WindowSnapshot.kt +536 -0
- package/build/ThemeRevealModule.d.ts +11 -0
- package/build/ThemeRevealModule.d.ts.map +1 -0
- package/build/ThemeRevealModule.js +11 -0
- package/build/ThemeRevealModule.js.map +1 -0
- package/build/createThemeReveal.d.ts +39 -0
- package/build/createThemeReveal.d.ts.map +1 -0
- package/build/createThemeReveal.js +186 -0
- package/build/createThemeReveal.js.map +1 -0
- package/build/index.d.ts +9 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/build/originFromPress.d.ts +12 -0
- package/build/originFromPress.d.ts.map +1 -0
- package/build/originFromPress.js +26 -0
- package/build/originFromPress.js.map +1 -0
- package/build/resolveTransition.d.ts +13 -0
- package/build/resolveTransition.d.ts.map +1 -0
- package/build/resolveTransition.js +43 -0
- package/build/resolveTransition.js.map +1 -0
- package/build/store.d.ts +8 -0
- package/build/store.d.ts.map +1 -0
- package/build/store.js +23 -0
- package/build/store.js.map +1 -0
- package/build/types.d.ts +31 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/Motion.swift +171 -0
- package/ios/RevealOverlay.swift +115 -0
- package/ios/SVGPath.swift +373 -0
- package/ios/ThemeReveal.podspec +26 -0
- package/ios/ThemeRevealModule.swift +264 -0
- package/mocks/ThemeReveal.js +10 -0
- package/package.json +67 -4
- package/src/ThemeRevealModule.ts +24 -0
- package/src/createThemeReveal.ts +262 -0
- package/src/index.ts +14 -0
- package/src/originFromPress.ts +40 -0
- package/src/resolveTransition.ts +63 -0
- package/src/store.ts +30 -0
- package/src/types.ts +17 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package expo.modules.themereveal
|
|
2
|
+
|
|
3
|
+
// Where the RenderThread reveal is in its wait for the second snapshot.
|
|
4
|
+
internal enum class WaitState {
|
|
5
|
+
// For the frame after start(), which draws React Native's mount of the new theme.
|
|
6
|
+
WAITING_FOR_FRAME,
|
|
7
|
+
|
|
8
|
+
// For that frame's commit.
|
|
9
|
+
WAITING_FOR_COMMIT,
|
|
10
|
+
|
|
11
|
+
// For the frame after the commit, behind React Native's own frame callbacks.
|
|
12
|
+
WAITING_FOR_CAPTURE_FRAME,
|
|
13
|
+
|
|
14
|
+
// For the capture's result.
|
|
15
|
+
CAPTURING,
|
|
16
|
+
|
|
17
|
+
// Revealed, fell back or cancelled.
|
|
18
|
+
DONE,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// What the wait does at each step. Main thread only.
|
|
22
|
+
internal interface SecondSnapshotSteps<B : Any> {
|
|
23
|
+
// Registers the one-shot frame-commit callback. False if the window won't take it.
|
|
24
|
+
fun registerCommit(): Boolean
|
|
25
|
+
|
|
26
|
+
fun postCaptureFrame()
|
|
27
|
+
|
|
28
|
+
// Starts the capture. Its result goes to SecondSnapshotWait.result, even from inside this call.
|
|
29
|
+
fun capture()
|
|
30
|
+
|
|
31
|
+
// Shows the second snapshot and starts its reveal. Null if it's running, otherwise the reason it isn't (it has
|
|
32
|
+
// then freed the bitmap).
|
|
33
|
+
fun reveal(bitmap: B): String?
|
|
34
|
+
|
|
35
|
+
// Runs the UI-thread reveal instead.
|
|
36
|
+
fun fallBack(reason: String)
|
|
37
|
+
|
|
38
|
+
// Ends the wait: removes its frame callbacks and timeout, and frees the target prepared for the capture.
|
|
39
|
+
fun release()
|
|
40
|
+
|
|
41
|
+
// Frees a bitmap no reveal uses.
|
|
42
|
+
fun free(bitmap: B)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The RenderThread reveal's wait for its second snapshot: frame, commit, capture frame, capture, result. It ends once,
|
|
46
|
+
// with the reveal, with one fallback to the UI-thread reveal (no window, no commit callback, no result, a result that
|
|
47
|
+
// can't reveal, the timeout), or with cancel (overlay is gone, nothing to fall back on). release runs once, first, at
|
|
48
|
+
// that end. A callback that comes late or out of order is a no-op, and a late result is freed. Main thread only.
|
|
49
|
+
internal class SecondSnapshotWait<B : Any>(private val steps: SecondSnapshotSteps<B>) {
|
|
50
|
+
var state = WaitState.WAITING_FOR_FRAME
|
|
51
|
+
private set
|
|
52
|
+
|
|
53
|
+
// A target prepared for the capture is still usable.
|
|
54
|
+
val beforeCapture: Boolean
|
|
55
|
+
get() = state != WaitState.CAPTURING && state != WaitState.DONE
|
|
56
|
+
|
|
57
|
+
fun frame(windowOk: Boolean) {
|
|
58
|
+
if (!moveOn(WaitState.WAITING_FOR_FRAME, WaitState.WAITING_FOR_COMMIT, windowOk)) return
|
|
59
|
+
if (!steps.registerCommit()) end("cannot wait for the frame commit")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fun commit(windowOk: Boolean) {
|
|
63
|
+
if (moveOn(WaitState.WAITING_FOR_COMMIT, WaitState.WAITING_FOR_CAPTURE_FRAME, windowOk)) steps.postCaptureFrame()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fun captureFrame(windowOk: Boolean) {
|
|
67
|
+
if (moveOn(WaitState.WAITING_FOR_CAPTURE_FRAME, WaitState.CAPTURING, windowOk)) steps.capture()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun result(bitmap: B?) {
|
|
71
|
+
if (state != WaitState.CAPTURING) {
|
|
72
|
+
bitmap?.let(steps::free)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
if (bitmap == null) {
|
|
76
|
+
end("no second snapshot")
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
end(null)
|
|
80
|
+
steps.reveal(bitmap)?.let(steps::fallBack)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
fun timeout() {
|
|
84
|
+
if (state != WaitState.DONE) end("no second snapshot within $TIMEOUT_MS ms")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fun cancel() {
|
|
88
|
+
if (state != WaitState.DONE) end(null)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Moves from one state to the next. A callback in any other state is a no-op. Without a window the wait ends.
|
|
92
|
+
private fun moveOn(from: WaitState, to: WaitState, windowOk: Boolean): Boolean {
|
|
93
|
+
if (state != from) return false
|
|
94
|
+
if (!windowOk) {
|
|
95
|
+
end("no window")
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
state = to
|
|
99
|
+
return true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun end(fallBack: String?) {
|
|
103
|
+
state = WaitState.DONE
|
|
104
|
+
steps.release()
|
|
105
|
+
fallBack?.let(steps::fallBack)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
companion object {
|
|
109
|
+
// Longest the wait can take from start(): 3 frames, the commit and the capture.
|
|
110
|
+
const val TIMEOUT_MS = 200L
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package expo.modules.themereveal
|
|
2
|
+
|
|
3
|
+
import android.graphics.Path
|
|
4
|
+
import android.graphics.PointF
|
|
5
|
+
import android.graphics.RectF
|
|
6
|
+
import androidx.core.graphics.PathParser
|
|
7
|
+
import kotlin.math.hypot
|
|
8
|
+
|
|
9
|
+
internal class ShapePath private constructor(
|
|
10
|
+
val path: Path,
|
|
11
|
+
val bounds: RectF,
|
|
12
|
+
val anchorX: Float,
|
|
13
|
+
val anchorY: Float,
|
|
14
|
+
val effectiveInscribedRadius: Float,
|
|
15
|
+
) {
|
|
16
|
+
companion object {
|
|
17
|
+
fun parse(path: String, anchor: PointF, inscribedRadius: Float): ShapePath {
|
|
18
|
+
val shape = of(PathParser.createPathFromPathData(path), anchor.x, anchor.y, inscribedRadius)
|
|
19
|
+
require(shape.bounds.width() > 0f && shape.bounds.height() > 0f) { "The shape path is empty" }
|
|
20
|
+
return shape
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fun circle(): ShapePath = of(Path().apply { addCircle(0f, 0f, 1f, Path.Direction.CW) }, 0f, 0f, 1f)
|
|
24
|
+
|
|
25
|
+
private fun of(path: Path, anchorX: Float, anchorY: Float, inscribedRadius: Float): ShapePath {
|
|
26
|
+
val bounds = RectF()
|
|
27
|
+
path.computeBounds(bounds, true)
|
|
28
|
+
val radius = effectiveInscribedRadius(
|
|
29
|
+
bounds.left, bounds.top, bounds.right, bounds.bottom, anchorX, anchorY, inscribedRadius,
|
|
30
|
+
)
|
|
31
|
+
return ShapePath(path, bounds, anchorX, anchorY, radius)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
internal fun effectiveInscribedRadius(
|
|
37
|
+
left: Float,
|
|
38
|
+
top: Float,
|
|
39
|
+
right: Float,
|
|
40
|
+
bottom: Float,
|
|
41
|
+
anchorX: Float,
|
|
42
|
+
anchorY: Float,
|
|
43
|
+
inscribedRadius: Float,
|
|
44
|
+
): Float {
|
|
45
|
+
val anchorInside = anchorX in left..right && anchorY in top..bottom
|
|
46
|
+
return if (inscribedRadius > 0f && anchorInside) inscribedRadius else hypot(right - left, bottom - top) / 2
|
|
47
|
+
}
|