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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +734 -2
  3. package/android/build.gradle +32 -0
  4. package/android/src/main/AndroidManifest.xml +2 -0
  5. package/android/src/main/baseline-prof.txt +4 -0
  6. package/android/src/main/java/expo/modules/themereveal/CapturePlan.kt +199 -0
  7. package/android/src/main/java/expo/modules/themereveal/CaptureRules.kt +50 -0
  8. package/android/src/main/java/expo/modules/themereveal/GpuSnapshot.kt +367 -0
  9. package/android/src/main/java/expo/modules/themereveal/KeptBitmaps.kt +102 -0
  10. package/android/src/main/java/expo/modules/themereveal/KeptTarget.kt +103 -0
  11. package/android/src/main/java/expo/modules/themereveal/Motion.kt +79 -0
  12. package/android/src/main/java/expo/modules/themereveal/Once.kt +13 -0
  13. package/android/src/main/java/expo/modules/themereveal/RenderThreadReveal.kt +107 -0
  14. package/android/src/main/java/expo/modules/themereveal/RevealOverlayView.kt +192 -0
  15. package/android/src/main/java/expo/modules/themereveal/SecondSnapshotWait.kt +112 -0
  16. package/android/src/main/java/expo/modules/themereveal/ShapePath.kt +47 -0
  17. package/android/src/main/java/expo/modules/themereveal/ThemeRevealModule.kt +904 -0
  18. package/android/src/main/java/expo/modules/themereveal/TransitionArgs.kt +50 -0
  19. package/android/src/main/java/expo/modules/themereveal/WindowSnapshot.kt +536 -0
  20. package/build/ThemeRevealModule.d.ts +11 -0
  21. package/build/ThemeRevealModule.d.ts.map +1 -0
  22. package/build/ThemeRevealModule.js +11 -0
  23. package/build/ThemeRevealModule.js.map +1 -0
  24. package/build/createThemeReveal.d.ts +39 -0
  25. package/build/createThemeReveal.d.ts.map +1 -0
  26. package/build/createThemeReveal.js +186 -0
  27. package/build/createThemeReveal.js.map +1 -0
  28. package/build/index.d.ts +9 -0
  29. package/build/index.d.ts.map +1 -0
  30. package/build/index.js +5 -0
  31. package/build/index.js.map +1 -0
  32. package/build/originFromPress.d.ts +12 -0
  33. package/build/originFromPress.d.ts.map +1 -0
  34. package/build/originFromPress.js +26 -0
  35. package/build/originFromPress.js.map +1 -0
  36. package/build/resolveTransition.d.ts +13 -0
  37. package/build/resolveTransition.d.ts.map +1 -0
  38. package/build/resolveTransition.js +43 -0
  39. package/build/resolveTransition.js.map +1 -0
  40. package/build/store.d.ts +8 -0
  41. package/build/store.d.ts.map +1 -0
  42. package/build/store.js +23 -0
  43. package/build/store.js.map +1 -0
  44. package/build/types.d.ts +31 -0
  45. package/build/types.d.ts.map +1 -0
  46. package/build/types.js +2 -0
  47. package/build/types.js.map +1 -0
  48. package/expo-module.config.json +9 -0
  49. package/ios/Motion.swift +171 -0
  50. package/ios/RevealOverlay.swift +115 -0
  51. package/ios/SVGPath.swift +373 -0
  52. package/ios/ThemeReveal.podspec +26 -0
  53. package/ios/ThemeRevealModule.swift +264 -0
  54. package/mocks/ThemeReveal.js +10 -0
  55. package/package.json +67 -4
  56. package/src/ThemeRevealModule.ts +24 -0
  57. package/src/createThemeReveal.ts +262 -0
  58. package/src/index.ts +14 -0
  59. package/src/originFromPress.ts +40 -0
  60. package/src/resolveTransition.ts +63 -0
  61. package/src/store.ts +30 -0
  62. 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
+ }