diff --git a/.argent/flows/simple-long-press-test.yaml b/.argent/flows/simple-long-press-test.yaml new file mode 100644 index 0000000000..237b5b13bd --- /dev/null +++ b/.argent/flows/simple-long-press-test.yaml @@ -0,0 +1,49 @@ +steps: + - echo: Launching Expo Example + - launch: com.example.ExpoExample + - await: { visible: { text: "Empty Example" }, timeout: 15000 } + - echo: "On the Home list, search for long press example" + - type: { text: "long", into: { id: "search-examples" }, submit: false } + - echo: "Tap the example to open it" + - tap: "Basic LongPress" + - echo: "Clear console" + - await: { visible: "open-console-button" } + - wait: 1000 # TODO: replace with wait: idle once released + - tap: "open-console-button" + - await: { visible: "clear-console-button" } + - tap: "clear-console-button" + - tap: "close-console-button" + - wait: 1000 # TODO: replace with wait: idle once released + - echo: "Long press the box and check if long press count is updated" + - await: { visible: "Long press count: 0" } + - assert: { visible: "long-press-box" } + - long-press: "long-press-box" + - assert: { visible: "Long press count: 1" } + - echo: "Check if console logs are printed in order" + - tap: "open-console-button" + - await: { visible: "close-console-button" } + - assert: { visible: "1. onBegin" } + - assert: { visible: "2. onActivate" } + - assert: { visible: "3. onDeactivate" } + - assert: { visible: "4. onFinalize" } + - tap: "close-console-button" + - wait: 1000 # TODO: replace with wait: idle once released + - await: { visible: "Long press count: 1" } + - echo: "Long press the box again and check if long press count is updated" + - long-press: "long-press-box" + - assert: { visible: "Long press count: 2" } + - echo: "Clear console" + - await: { visible: "open-console-button" } + - tap: "open-console-button" + - await: { visible: "clear-console-button" } + - tap: "clear-console-button" + - tap: "close-console-button" + - wait: 1000 # TODO: replace with wait: idle once released + - echo: "Tap the box and check if long press count is not updated" + - tap: "long-press-box" + - assert: { visible: "Long press count: 2" } + - echo: "Check if console logs are printed in order" + - tap: "open-console-button" + - await: { visible: "close-console-button" } + - assert: { visible: "9. onBegin" } + - assert: { visible: "10. onFinalize" } diff --git a/.argent/flows/simple-tap-test.yaml b/.argent/flows/simple-tap-test.yaml new file mode 100644 index 0000000000..ee16b43f26 --- /dev/null +++ b/.argent/flows/simple-tap-test.yaml @@ -0,0 +1,49 @@ +steps: + - echo: Launching Expo Example + - launch: com.example.ExpoExample + - await: { visible: { text: "Empty Example" }, timeout: 15000 } + - echo: "On the Home list, search for tap example" + - type: { text: "tap", into: { id: "search-examples" }, submit: false } + - echo: "Tap the example to open it" + - tap: "Basic Tap" + - echo: "Clear console" + - await: { visible: "open-console-button" } + - wait: 1000 # TODO: replace with wait: idle once released + - tap: "open-console-button" + - await: { visible: "clear-console-button" } + - tap: "clear-console-button" + - tap: "close-console-button" + - wait: 1000 # TODO: replace with wait: idle once released + - echo: "Tap the box and check if tap count is updated" + - await: { visible: "Tap count: 0" } + - assert: { visible: "tap-box" } + - tap: "tap-box" + - assert: { visible: "Tap count: 1" } + - echo: "Check if console logs are printed in order" + - tap: "open-console-button" + - await: { visible: "close-console-button" } + - assert: { visible: "1. onBegin" } + - assert: { visible: "2. onActivate" } + - assert: { visible: "3. onDeactivate" } + - assert: { visible: "4. onFinalize" } + - tap: "close-console-button" + - wait: 1000 # TODO: replace with wait: idle once released + - await: { visible: "Tap count: 1" } + - echo: "Tap the box again and check if tap count is updated" + - tap: "tap-box" + - assert: { visible: "Tap count: 2" } + - echo: "Clear console" + - await: { visible: "open-console-button" } + - tap: "open-console-button" + - await: { visible: "clear-console-button" } + - tap: "clear-console-button" + - tap: "close-console-button" + - wait: 1000 # TODO: replace with wait: idle once released + - echo: "Long press the box and check if tap count is not updated" + - long-press: "tap-box" + - assert: { visible: "Tap count: 2" } + - echo: "Check if console logs are printed in order" + - tap: "open-console-button" + - await: { visible: "close-console-button" } + - assert: { visible: "9. onBegin" } + - assert: { visible: "10. onFinalize" } \ No newline at end of file diff --git a/.github/actions/argent-server/action.yml b/.github/actions/argent-server/action.yml new file mode 100644 index 0000000000..81c4cff452 --- /dev/null +++ b/.github/actions/argent-server/action.yml @@ -0,0 +1,68 @@ +name: Start Argent tool-server +description: >- + Installs the Argent CLI and starts a tool-server for later steps and + ARGENT_SERVER_LOG for the caller to print on failure. + +inputs: + port: + description: Port to serve the tool-server on. + required: false + default: '3001' + simulator-server-log: + description: >- + SIMSERVER_LOG level for the simulator-server backend the tool-server + spawns, e.g. `debug`. Left unset when empty. + required: false + default: '' + +runs: + using: composite + steps: + - name: Install Argent + shell: bash + run: npx @swmansion/argent@0.20 init --yes + + # Start the server here and export its URL, so `flow run` reuses it instead + # of starting one in-process, which has to come up within a fixed window a + # cold runner can miss — the first run downloads the simulator-server binary. + # + # Not `--detach`: it gives the server a hard 15s to come up and then kills + # its whole process group, and backgrounding the CLI does not help because + # the timeout lives in the process we backgrounded. In the foreground there + # is no deadline and the server's own output lands in our log. + - name: Start tool-server + shell: bash + env: + TOOL_SERVER_PORT: ${{ inputs.port }} + SIMULATOR_SERVER_LOG: ${{ inputs.simulator-server-log }} + run: | + LOG="$RUNNER_TEMP/argent-server.log" + # Exported before anything can fail, so the caller's log-printing step + # can find it even when startup is what went wrong. + echo "ARGENT_SERVER_LOG=$LOG" >> "$GITHUB_ENV" + + if [ -n "$SIMULATOR_SERVER_LOG" ]; then + export SIMSERVER_LOG="$SIMULATOR_SERVER_LOG" + fi + + nohup argent server start --no-auth --port "$TOOL_SERVER_PORT" > "$LOG" 2>&1 & + SERVER_PID=$! + + echo "Waiting for Argent tool-server on :$TOOL_SERVER_PORT..." + # 3 min budget. Poll liveness too, so a server that dies on startup + # fails right away with its log instead of at the end of the budget. + for _ in $(seq 1 90); do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + echo "Argent tool-server exited during startup" >&2 + cat "$LOG" >&2 + exit 1 + fi + if curl -fsS "http://127.0.0.1:$TOOL_SERVER_PORT/tools" >/dev/null 2>&1; then + echo "tool-server is ready" + exit 0 + fi + sleep 2 + done + echo "Argent tool-server failed to become ready in time" >&2 + cat "$LOG" >&2 + exit 1 diff --git a/.github/workflows/android-e2e.yml b/.github/workflows/android-e2e.yml new file mode 100644 index 0000000000..52fe39e546 --- /dev/null +++ b/.github/workflows/android-e2e.yml @@ -0,0 +1,161 @@ +name: Run Android e2e tests + +# Reusable: runs the Argent flows against a prebuilt APK. Called by `android.yml`. +on: + workflow_call: + inputs: + artifact-name: + description: Artifact holding the APK to test. + required: true + type: string + abi: + description: Emulator ABI. Must match the ABI the APK was built for. + required: true + type: string + +jobs: + e2e: + runs-on: ubuntu-latest + timeout-minutes: 60 + + env: + # The emulator advertises its gRPC console in + # $XDG_RUNTIME_DIR/avd/running/pid_*.ini, which is where Argent's Android + # backend looks for it. The variable is unset on runners and the two fall + # back to different dirs, so pin it to a path they both check first. + XDG_RUNTIME_DIR: /tmp/xdg-runtime + AVD_NAME: test + SYSTEM_IMAGE: system-images;android-34;google_apis;${{ inputs.abi }} + + steps: + - name: checkout + uses: actions/checkout@v4 + + # sdkmanager and avdmanager are Java tools. + - name: Use Java 17 + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: 17 + + # Only needed to run the Argent CLI — the app is downloaded, not built. + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Download APK built by the build workflow + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ${{ runner.temp }}/apk + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # The emulator ignores XDG_RUNTIME_DIR if it does not exist, so create it + # before booting. 0700 per the XDG spec. + - name: Prepare shared XDG_RUNTIME_DIR + run: | + mkdir -p "$XDG_RUNTIME_DIR" + chmod 700 "$XDG_RUNTIME_DIR" + + # The runner's preinstalled SDK has neither the emulator nor a system + # image (asking for platform-tools on top of it is a no-op). + - name: Install Android emulator and system image + run: | + CMDLINE_BIN=$(dirname "$(command -v sdkmanager || echo "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager")") + yes | "$CMDLINE_BIN/sdkmanager" --licenses > /dev/null || true + "$CMDLINE_BIN/sdkmanager" --install "emulator" "platform-tools" "$SYSTEM_IMAGE" > /dev/null + { + echo "$CMDLINE_BIN" + echo "$ANDROID_HOME/emulator" + echo "$ANDROID_HOME/platform-tools" + } >> "$GITHUB_PATH" + # By default avdmanager writes to $ANDROID_SDK_HOME/.android/avd while + # the emulator looks in $ANDROID_SDK_HOME/avd, which fails the boot + # with "Unknown AVD name". Both check $ANDROID_AVD_HOME first. + echo "ANDROID_AVD_HOME=$HOME/.android/avd" >> "$GITHUB_ENV" + + - name: Create AVD + run: | + mkdir -p "$ANDROID_AVD_HOME" + echo no | avdmanager create avd --name "$AVD_NAME" --package "$SYSTEM_IMAGE" --force + test -f "$ANDROID_AVD_HOME/$AVD_NAME.ini" || { + echo "avdmanager did not create $AVD_NAME.ini in $ANDROID_AVD_HOME; found instead:" >&2 + find "$HOME" "$ANDROID_HOME" -name "$AVD_NAME.ini" 2>/dev/null >&2 + exit 1 + } + + # `-grpc` makes the emulator write grpc.port to the discovery ini Argent + # reads. `-accel on` fails the boot when KVM didn't take, instead of + # falling back to a software boot slow enough to hit the job timeout. + - name: Boot emulator + run: | + nohup emulator -avd "$AVD_NAME" -no-snapshot -accel on \ + -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim \ + -grpc 8554 -camera-back none > emulator.log 2>&1 & + EMULATOR_PID=$! + adb start-server + # Poll boot state and process liveness (10 min budget): a backgrounded + # emulator that dies cannot fail the step on its own, and + # `adb wait-for-device` would just hang without saying why. + for _ in $(seq 1 120); do + if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then + echo "Emulator process exited during boot:" >&2 + cat emulator.log >&2 + exit 1 + fi + if [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = 1 ]; then + BOOTED=1 + break + fi + sleep 5 + done + if [ -z "${BOOTED:-}" ]; then + echo "Emulator did not finish booting within 10 minutes" >&2 + adb devices >&2 + cat emulator.log >&2 + exit 1 + fi + adb shell input keyevent 82 + adb shell settings put global window_animation_scale 0.0 + adb shell settings put global transition_animation_scale 0.0 + adb shell settings put global animator_duration_scale 0.0 + SERIAL=$(adb devices | awk 'NR>1 && $2=="device"{print $1; exit}') + echo "Emulator ready: $SERIAL" + echo "ANDROID_SERIAL=$SERIAL" >> "$GITHUB_ENV" + + - name: Install app on emulator + run: | + APK=$(find "$RUNNER_TEMP/apk" -name '*.apk' | head -1) + if [ -z "$APK" ]; then + echo "Downloaded artifact does not contain an APK" >&2 + ls -la "$RUNNER_TEMP/apk" >&2 + exit 1 + fi + echo "Installing $APK on $ANDROID_SERIAL" + adb install -r "$APK" + + - name: Install Argent and start the tool-server + uses: ./.github/actions/argent-server + with: + # Logs how the backend discovers the emulator; drop once this is green. + simulator-server-log: debug + + - name: Run E2E tests + run: argent flow run ./.argent/flows --device "$ANDROID_SERIAL" + + - name: Print emulator log + if: ${{ !success() }} + run: cat emulator.log 2>/dev/null || echo "no emulator.log" + + # The tool-server log also carries the device backend's stderr + # (`[sim ]` lines), where a failed connection to the emulator is + # explained. + - name: Print Argent tool-server log + if: ${{ !success() }} + run: cat "$ARGENT_SERVER_LOG" 2>/dev/null || echo "no Argent tool-server log" diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 6ad16bb9b2..2dde81f4c4 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -3,8 +3,11 @@ name: Build Android (expo-example) on: pull_request: paths: + - .argent/flows/* + - .github/actions/argent-server/action.yml - .github/workflows/android.yml - .github/workflows/android-build.yml + - .github/workflows/android-e2e.yml - packages/react-native-gesture-handler/package.json - packages/react-native-gesture-handler/android/** - packages/react-native-gesture-handler/shared/** @@ -31,3 +34,12 @@ jobs: app: expo-example artifact-name: android-apk-expo-example abi: x86_64 + + e2e: + if: github.repository == 'software-mansion/react-native-gesture-handler' + needs: expo-example + + uses: ./.github/workflows/android-e2e.yml + with: + artifact-name: android-apk-expo-example + abi: x86_64 diff --git a/.github/workflows/ios-e2e.yml b/.github/workflows/ios-e2e.yml new file mode 100644 index 0000000000..71ebd4823b --- /dev/null +++ b/.github/workflows/ios-e2e.yml @@ -0,0 +1,175 @@ +name: Run iOS e2e tests + +# Reusable: runs the Argent flows against a prebuilt app bundle. Called by `ios.yml`. +on: + workflow_call: + inputs: + artifact-name: + description: Artifact holding the packaged .app bundle to test. + required: true + type: string + xcode-version: + description: >- + Xcode to run on. Must match the version the app was built with, so the + simulator runtime matches the SDK. + required: true + type: string + artifact-suffix: + description: >- + Keeps uploaded artifact names unique when the caller runs this + workflow in a matrix. + required: false + type: string + default: '' + +jobs: + e2e: + runs-on: macos-26 + timeout-minutes: 60 + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Select Xcode + env: + XCODE_VERSION: ${{ inputs.xcode-version }} + run: | + XCODE_APP="/Applications/Xcode_${XCODE_VERSION}.app" + if [ ! -d "$XCODE_APP" ]; then + echo "Xcode ${XCODE_VERSION} is not installed on this runner. Available:" >&2 + ls -d /Applications/Xcode*.app >&2 + exit 1 + fi + echo "Using $XCODE_APP" + echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV" + DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version + + # Only needed to run the Argent CLI — the app is downloaded, not built. + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Download app built by the build workflow + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ${{ runner.temp }}/app + + - name: Unpack app bundle + run: | + tar -xzf "$RUNNER_TEMP/app/app.tar.gz" -C "$RUNNER_TEMP/app" + APP_PATH=$(find "$RUNNER_TEMP/app" -maxdepth 1 -name '*.app' -type d | head -1) + if [ -z "$APP_PATH" ]; then + echo "Downloaded artifact does not contain an .app" >&2 + ls -la "$RUNNER_TEMP/app" >&2 + exit 1 + fi + echo "APP_PATH=$APP_PATH" >> "$GITHUB_ENV" + + - name: Set up iPhone 17 Pro simulator + run: | + UDID=$(xcrun simctl list devices available | grep -E "iPhone 17 Pro \(" | head -1 | grep -oE "[0-9A-Fa-f-]{36}" || true) + if [ -z "$UDID" ]; then + echo "iPhone 17 Pro not found, creating one" + RUNTIME=$(xcrun simctl list runtimes ios -j | jq -r '.runtimes | map(select(.isAvailable)) | last | .identifier') + UDID=$(xcrun simctl create "iPhone 17 Pro" "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro" "$RUNTIME") + fi + echo "Using simulator $UDID" + xcrun simctl boot "$UDID" || true + xcrun simctl bootstatus "$UDID" + echo "SIMULATOR_UDID=$UDID" >> "$GITHUB_ENV" + + - name: Disable keyboard intelligence on the simulator + run: | + KEYS=( + KeyboardPrediction + KeyboardAutocorrection + KeyboardCheckSpelling + KeyboardAutocapitalization + KeyboardPeriodShortcut + KeyboardCapsLock + KeyboardContinuousPathEnabled + ) + for key in "${KEYS[@]}"; do + xcrun simctl spawn "$SIMULATOR_UDID" defaults write "Apple Global Domain" "$key" -bool false + xcrun simctl spawn "$SIMULATOR_UDID" defaults write com.apple.Preferences "$key" -bool false + done + # Suppress the slide-to-type intro overlay a fresh sim shows on first keyboard use + xcrun simctl spawn "$SIMULATOR_UDID" defaults write com.apple.keyboard.preferences \ + DidShowContinuousPathIntroduction -bool true + xcrun simctl spawn "$SIMULATOR_UDID" defaults read "Apple Global Domain" | grep Keyboard || true + + - name: Install app on simulator + run: | + echo "Installing $APP_PATH on $SIMULATOR_UDID" + xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH" + + - name: Install Argent and start the tool-server + uses: ./.github/actions/argent-server + + - name: Run E2E tests + run: argent flow run ./.argent/flows --device $SIMULATOR_UDID + + # Interpreting the artifacts: + # - screen.png taken first, closest to the failure state (software keyboard + # still visible = the dismissal never completed). + # - probe-flow.txt distinguishes wedged from recovered: "ViewInspector RPC + # timed out" = the app STILL cannot serve a main-thread read minutes after + # the failure (true wedge); a plain "not visible" assert failure = the + # tree was readable again (bounded stall that recovered). + # - sample-app.txt runs WHILE the probe is in flight, so a wedged main + # thread is caught parked in the culprit frames. + # - spindump-system.txt covers the XPC peers (keyboard daemon, backboardd) + # in case main is waiting on another process. + - name: Capture app diagnostics + if: ${{ !success() }} + run: | + DIAG="$RUNNER_TEMP/diagnostics" + mkdir -p "$DIAG" + + xcrun simctl io "$SIMULATOR_UDID" screenshot "$DIAG/screen.png" || true + + BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP_PATH/Info.plist") + PID=$(xcrun simctl spawn "$SIMULATOR_UDID" launchctl list 2>/dev/null \ + | awk -v label="UIKitApplication:$BUNDLE_ID" '$3 ~ label {print $1}' | head -1) + + # One-step throwaway flow: reads the UI tree through the same + # native-devtools path the real flows use. The assert is expected to + # fail either way; only the failure REASON matters (see above). + mkdir -p "$RUNNER_TEMP/probe-flow" + cat > "$RUNNER_TEMP/probe-flow/probe.yaml" <<'EOF' + steps: + - assert: { visible: "argent-diagnostic-probe-nonexistent" } + EOF + + if [ -n "$PID" ] && [ "$PID" != "-" ]; then + echo "Sampling $BUNDLE_ID (pid $PID) while probing the UI tree" + sample "$PID" 15 -file "$DIAG/sample-app.txt" & + SAMPLE_JOB=$! + argent flow run "$RUNNER_TEMP/probe-flow" --device "$SIMULATOR_UDID" \ + 2>&1 | tee "$DIAG/probe-flow.txt" || true + wait "$SAMPLE_JOB" || true + else + echo "$BUNDLE_ID is not running; nothing to sample" | tee "$DIAG/app-not-running.txt" + argent flow run "$RUNNER_TEMP/probe-flow" --device "$SIMULATOR_UDID" \ + 2>&1 | tee "$DIAG/probe-flow.txt" || true + fi + + sudo spindump -notarget 5 20 -file "$DIAG/spindump-system.txt" || true + + - name: Upload diagnostics + if: ${{ !success() }} + uses: actions/upload-artifact@v4 + with: + name: e2e-ios-diagnostics-${{ inputs.artifact-name }}${{ inputs.artifact-suffix }} + path: ${{ runner.temp }}/diagnostics/ + if-no-files-found: ignore + + # The tool-server log also carries the device backend's stderr + # (`[sim ]` lines), where a failed connection to the simulator is + # explained. + - name: Print Argent tool-server log + if: ${{ !success() }} + run: cat "$ARGENT_SERVER_LOG" 2>/dev/null || echo "no Argent tool-server log" diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index a40c21d9ac..b023413ae7 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -3,8 +3,11 @@ name: Build iOS (expo-example) on: pull_request: paths: + - .argent/flows/* + - .github/actions/argent-server/action.yml - .github/workflows/ios.yml - .github/workflows/ios-build.yml + - .github/workflows/ios-e2e.yml - packages/react-native-gesture-handler/package.json - packages/react-native-gesture-handler/RNGestureHandler.podspec - packages/react-native-gesture-handler/apple/** @@ -24,12 +27,45 @@ concurrency: cancel-in-progress: true jobs: + # Single source of truth for versions shared by the build and e2e workflows. + # A job output is the only way to reach them both: `jobs..with` resolves + # `needs`, `matrix` and `vars`, but not a workflow-level `env`. + config: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + runs-on: ubuntu-latest + outputs: + xcode-version: ${{ steps.versions.outputs.xcode }} + + steps: + # The e2e job has to run the same Xcode the app was built with, so its + # simulator runtime matches the SDK. + - name: Pin versions + id: versions + run: echo "xcode=26.6" >> "$GITHUB_OUTPUT" + expo-example: if: github.repository == 'software-mansion/react-native-gesture-handler' + needs: config uses: ./.github/workflows/ios-build.yml with: app: expo-example scheme: ExpoExample artifact-name: ios-app-expo-example - xcode-version: '26.4.1' + xcode-version: ${{ needs.config.outputs.xcode-version }} + + e2e: + if: github.repository == 'software-mansion/react-native-gesture-handler' + needs: [config, expo-example] + + strategy: + fail-fast: false + matrix: + run: [1, 2, 3, 4, 5] + + uses: ./.github/workflows/ios-e2e.yml + with: + artifact-name: ios-app-expo-example + artifact-suffix: -${{ matrix.run }} + xcode-version: ${{ needs.config.outputs.xcode-version }} diff --git a/apps/common-app/src/common.tsx b/apps/common-app/src/common.tsx index 8cdc3e8d5d..da9e92967d 100644 --- a/apps/common-app/src/common.tsx +++ b/apps/common-app/src/common.tsx @@ -14,6 +14,7 @@ import Animated, { useSharedValue, withTiming, } from 'react-native-reanimated'; +import { scheduleOnRN } from 'react-native-worklets'; export interface Example { name: string; @@ -140,6 +141,27 @@ type Props = { style: StyleProp; }; +export function useIndexedLogger() { + const messageCounter = useRef(0); + + const logMessage = useCallback((message: string) => { + messageCounter.current += 1; + const indexedMessage = `${messageCounter.current}. ${message}`; + console.log(indexedMessage); + }, []); + + const logMessageWorklet = useCallback( + (message: string) => { + 'worklet'; + // Schedule log on the JS thread so the console interceptor can pick it up + scheduleOnRN(logMessage, message); + }, + [logMessage] + ); + + return logMessageWorklet; +} + export class LoremIpsum extends React.Component { static defaultProps = { words: 1000, diff --git a/apps/common-app/src/console/ConsoleModal.tsx b/apps/common-app/src/console/ConsoleModal.tsx index d7cabd28b5..546a792861 100644 --- a/apps/common-app/src/console/ConsoleModal.tsx +++ b/apps/common-app/src/console/ConsoleModal.tsx @@ -236,6 +236,7 @@ function ConsoleModalContent({ entries, visible, onClose }: ConsoleModalProps) { setCount((c) => c + 1), []); + const colorProgress = useSharedValue(0); const finalise_color = useSharedValue(COLORS.PURPLE); const animatedStyle = useAnimatedStyle(() => { @@ -30,16 +35,26 @@ export default function LongPressExample() { const longPressGesture = useLongPressGesture({ onBegin: () => { + log('onBegin'); colorProgress.value = withTiming(1, { duration: 100, }); }, onActivate: () => { + log('onActivate'); + scheduleOnRN(incrementCount); colorProgress.value = withTiming(2, { duration: 100, }); }, + onDeactivate: () => { + log('onDeactivate'); + colorProgress.value = withTiming(1, { + duration: 100, + }); + }, onFinalize: (e) => { + log('onFinalize'); finalise_color.value = e.canceled ? COLORS.RED : COLORS.GREEN; colorProgress.value = 1; colorProgress.value = withTiming( @@ -57,7 +72,11 @@ export default function LongPressExample() { return ( - + Long press count: {count} + ); diff --git a/apps/common-app/src/new_api/simple/tap/index.tsx b/apps/common-app/src/new_api/simple/tap/index.tsx index 37e329758c..a090cad9ba 100644 --- a/apps/common-app/src/new_api/simple/tap/index.tsx +++ b/apps/common-app/src/new_api/simple/tap/index.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { View } from 'react-native'; +import React, { useCallback, useState } from 'react'; +import { Text, View } from 'react-native'; import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; import Animated, { interpolateColor, @@ -7,11 +7,16 @@ import Animated, { useSharedValue, withTiming, } from 'react-native-reanimated'; +import { scheduleOnRN } from 'react-native-worklets'; -import { COLORS, commonStyles } from '../../../common'; +import { COLORS, commonStyles, useIndexedLogger } from '../../../common'; export default function TapExample() { + const [count, setCount] = useState(0); const colorProgress = useSharedValue(0); + const log = useIndexedLogger(); + + const incrementCount = useCallback(() => setCount((c) => c + 1), []); const animatedStyle = useAnimatedStyle(() => { return { @@ -25,11 +30,20 @@ export default function TapExample() { const tapGesture = useTapGesture({ onBegin: () => { + log('onBegin'); colorProgress.value = withTiming(1, { duration: 100, }); }, + onActivate: () => { + log('onActivate'); + scheduleOnRN(incrementCount); + }, + onDeactivate: () => { + log('onDeactivate'); + }, onFinalize: () => { + log('onFinalize'); colorProgress.value = withTiming(0, { duration: 100, }); @@ -38,8 +52,12 @@ export default function TapExample() { return ( + Tap count: {count} - + );