Merge branch '0.227' into universalBuild
This commit is contained in:
@@ -1,28 +1,32 @@
|
|||||||
version: 2.1
|
version: 2.1
|
||||||
executors:
|
orbs:
|
||||||
default-executor:
|
android: circleci/android@2.3.0
|
||||||
docker:
|
|
||||||
- image: circleci/android:api-30-ndk
|
|
||||||
resource_class: large
|
|
||||||
|
|
||||||
environment:
|
|
||||||
_JAVA_OPTIONS: "-Xmx1500m -XX:+UnlockExperimentalVMOptions -XX:+UseContainerSupport -XX:ParallelGCThreads=2 -XX:ConcGCThreads=2 -XX:ParallelGCThreads=2 -Djava.util.concurrent.ForkJoinPool.common.parallelism=2"
|
|
||||||
TERM: 'dumb'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
snapshot:
|
snapshot:
|
||||||
executor: default-executor
|
environment:
|
||||||
|
TERM: 'dumb'
|
||||||
|
executor:
|
||||||
|
name: android/android-machine
|
||||||
|
tag: 2023.04.1
|
||||||
|
resource-class: large
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
- android/restore-gradle-cache:
|
||||||
|
cache-prefix: v1a
|
||||||
- run:
|
- run:
|
||||||
name: install retry
|
name: install retry
|
||||||
command: scripts/install-retry.sh
|
command: scripts/install-retry.sh
|
||||||
- run:
|
- run:
|
||||||
name: build and deploy
|
name: build
|
||||||
|
command: |
|
||||||
|
yes | sdkmanager "platforms;android-33" || true
|
||||||
|
/tmp/retry -m 3 ./gradlew :android:assembleRelease --info
|
||||||
|
- run:
|
||||||
|
name: deploy snapshot
|
||||||
command: |
|
command: |
|
||||||
yes | sdkmanager "platforms;android-27" || true
|
|
||||||
/tmp/retry -m 3 ./gradlew :android:assembleRelease
|
|
||||||
/tmp/retry -m 3 scripts/publish-android-snapshot.sh
|
/tmp/retry -m 3 scripts/publish-android-snapshot.sh
|
||||||
|
- android/save-gradle-cache:
|
||||||
|
cache-prefix: v1a
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
|
|||||||
13
.github/workflows/android-sample.yml
vendored
13
.github/workflows/android-sample.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: Build Android Sample App
|
name: Build Android Sample App
|
||||||
|
# This action runs on 'git push' and PRs
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -7,14 +7,15 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: set up JDK
|
- name: set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v3.11.0
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
distribution: 'temurin'
|
||||||
|
java-version: 17
|
||||||
- name: Compute build cache
|
- name: Compute build cache
|
||||||
run: ./scripts/checksum-android.sh checksum-android.txt
|
run: ./scripts/checksum-android.sh checksum-android.txt
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3.3.1
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches/modules-*
|
~/.gradle/caches/modules-*
|
||||||
@@ -30,7 +31,7 @@ jobs:
|
|||||||
- name: Build remaining artifacts with Gradle
|
- name: Build remaining artifacts with Gradle
|
||||||
run: ./gradlew assembleDebug
|
run: ./gradlew assembleDebug
|
||||||
- name: upload artifact
|
- name: upload artifact
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
with:
|
with:
|
||||||
name: sample-app.apk
|
name: sample-app.apk
|
||||||
path: android/sample/build/outputs/apk/debug/sample-debug.apk
|
path: android/sample/build/outputs/apk/debug/sample-debug.apk
|
||||||
|
|||||||
9
.github/workflows/docs.yml
vendored
9
.github/workflows/docs.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Docs
|
name: Docs
|
||||||
|
# This action runs on push to 'main'
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -9,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2.3.1
|
uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -19,10 +20,12 @@ jobs:
|
|||||||
yarn build
|
yarn build
|
||||||
working-directory: website/
|
working-directory: website/
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy to GitHub Pages
|
||||||
uses: JamesIves/github-pages-deploy-action@4.1.3
|
uses: JamesIves/github-pages-deploy-action@v4.4.2
|
||||||
with:
|
with:
|
||||||
branch: gh-pages
|
branch: gh-pages
|
||||||
folder: website/build
|
folder: website/build
|
||||||
clean: true
|
clean: true
|
||||||
|
clean-exclude: |
|
||||||
|
.circleci
|
||||||
commit-message: "[ci skip] Deploying documentation update"
|
commit-message: "[ci skip] Deploying documentation update"
|
||||||
|
|||||||
7
.github/workflows/iOS-Sample.yml
vendored
7
.github/workflows/iOS-Sample.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Build iOS apps
|
name: Build iOS apps
|
||||||
|
# This action runs on 'git push' and PRs to below specified paths
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
@@ -21,7 +22,7 @@ jobs:
|
|||||||
working-directory: iOS/Sample
|
working-directory: iOS/Sample
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pod install --repo-update
|
run: pod install --repo-update
|
||||||
- name: Build Sample app
|
- name: Build Sample app
|
||||||
@@ -36,7 +37,7 @@ jobs:
|
|||||||
working-directory: iOS/SampleSwift
|
working-directory: iOS/SampleSwift
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pod install --repo-update
|
run: pod install --repo-update
|
||||||
- name: Build SampleSwift app
|
- name: Build SampleSwift app
|
||||||
@@ -51,7 +52,7 @@ jobs:
|
|||||||
working-directory: iOS/Tutorial
|
working-directory: iOS/Tutorial
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pod install --repo-update
|
run: pod install --repo-update
|
||||||
- name: Build Tutorial app
|
- name: Build Tutorial app
|
||||||
|
|||||||
5
.github/workflows/iOS-dependent-pod-lint.yml
vendored
5
.github/workflows/iOS-dependent-pod-lint.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Validate Dependent Podspecs
|
name: Validate Dependent Podspecs
|
||||||
|
# This action runs on push and PRs to below specified paths
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
@@ -14,7 +15,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: iOS/Podspecs
|
working-directory: iOS/Podspecs
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint Folly
|
- name: Lint Folly
|
||||||
@@ -26,7 +27,7 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: iOS/Podspecs
|
working-directory: iOS/Podspecs
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint Peertalk
|
- name: Lint Peertalk
|
||||||
|
|||||||
69
.github/workflows/iOS-pod-lint.yml
vendored
69
.github/workflows/iOS-pod-lint.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Validate Podspecs
|
name: Validate Podspecs
|
||||||
|
# This action runs on 'git push' and PRs to below specified paths.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
@@ -19,11 +20,11 @@ jobs:
|
|||||||
lint-flipperkit_fbdefines_pod:
|
lint-flipperkit_fbdefines_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FBDefines
|
- name: Lint FlipperKit/FBDefines
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -32,11 +33,11 @@ jobs:
|
|||||||
lint-flipperkit_core_pod:
|
lint-flipperkit_core_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/Core
|
- name: Lint FlipperKit/Core
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -45,11 +46,11 @@ jobs:
|
|||||||
lint-flipperkit_cppbridge_pod:
|
lint-flipperkit_cppbridge_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/CppBridge
|
- name: Lint FlipperKit/CppBridge
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -58,11 +59,11 @@ jobs:
|
|||||||
lint-flipperkit_dynamic_convert_pod:
|
lint-flipperkit_dynamic_convert_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FBCxxFollyDynamicConvert
|
- name: Lint FlipperKit/FBCxxFollyDynamicConvert
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -71,11 +72,11 @@ jobs:
|
|||||||
lint-flipperkit_port_forwarding_pod:
|
lint-flipperkit_port_forwarding_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FKPortForwarding
|
- name: Lint FlipperKit/FKPortForwarding
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -84,11 +85,11 @@ jobs:
|
|||||||
lint-flipperkit_layout_highlight_pod:
|
lint-flipperkit_layout_highlight_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitHighlightOverlay
|
- name: Lint FlipperKit/FlipperKitHighlightOverlay
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -97,11 +98,11 @@ jobs:
|
|||||||
lint-flipperkit_layout_text_searchable_pod:
|
lint-flipperkit_layout_text_searchable_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitHighlightOverlay
|
- name: Lint FlipperKit/FlipperKitHighlightOverlay
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -110,11 +111,11 @@ jobs:
|
|||||||
lint-flipperkit_layout_helpers_pod:
|
lint-flipperkit_layout_helpers_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitLayoutHelpers
|
- name: Lint FlipperKit/FlipperKitLayoutHelpers
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -123,11 +124,11 @@ jobs:
|
|||||||
lint-flipperkit_layout_ios_descriptors_pod:
|
lint-flipperkit_layout_ios_descriptors_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitLayoutIOSDescriptors
|
- name: Lint FlipperKit/FlipperKitLayoutIOSDescriptors
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -136,11 +137,11 @@ jobs:
|
|||||||
lint-flipperkit_layout_plugin_pod:
|
lint-flipperkit_layout_plugin_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitLayoutPlugin
|
- name: Lint FlipperKit/FlipperKitLayoutPlugin
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -149,11 +150,11 @@ jobs:
|
|||||||
lint-flipperkit_layout_ck_plugin_pod:
|
lint-flipperkit_layout_ck_plugin_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitLayoutComponentKitSupport
|
- name: Lint FlipperKit/FlipperKitLayoutComponentKitSupport
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -162,11 +163,11 @@ jobs:
|
|||||||
lint-flipperkit_network_pod:
|
lint-flipperkit_network_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitNetworkPlugin
|
- name: Lint FlipperKit/FlipperKitNetworkPlugin
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -175,11 +176,11 @@ jobs:
|
|||||||
lint-flipperkit_skiosnetwork_pod:
|
lint-flipperkit_skiosnetwork_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/SKIOSNetworkPlugin
|
- name: Lint FlipperKit/SKIOSNetworkPlugin
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -188,11 +189,11 @@ jobs:
|
|||||||
lint-flipperkit_user_default_pod:
|
lint-flipperkit_user_default_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitUserDefaultsPlugin
|
- name: Lint FlipperKit/FlipperKitUserDefaultsPlugin
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -201,11 +202,11 @@ jobs:
|
|||||||
lint-flipperkit_example_plugin_pod:
|
lint-flipperkit_example_plugin_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitExamplePlugin
|
- name: Lint FlipperKit/FlipperKitExamplePlugin
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -214,11 +215,11 @@ jobs:
|
|||||||
lint-flipperkit_react_plugin_pod:
|
lint-flipperkit_react_plugin_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint FlipperKit/FlipperKitReactPlugin
|
- name: Lint FlipperKit/FlipperKitReactPlugin
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
@@ -227,11 +228,11 @@ jobs:
|
|||||||
lint-flipper_pod:
|
lint-flipper_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
- name: Lint Flipper
|
- name: Lint Flipper
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 60
|
timeout_minutes: 60
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
|
|||||||
15
.github/workflows/issues.yml
vendored
15
.github/workflows/issues.yml
vendored
@@ -1,17 +1,26 @@
|
|||||||
name: Label issues
|
name: Label issues
|
||||||
|
|
||||||
# This workflow is triggered on issue comments.
|
# This workflow is triggered on issue comments.
|
||||||
on:
|
on:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: created
|
types: created
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
applyNeedsAttentionLabel:
|
applyNeedsAttentionLabel:
|
||||||
|
permissions:
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
issues: write # for hramos/needs-attention to label issues
|
||||||
name: Apply Needs Attention Label
|
name: Apply Needs Attention Label
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository == 'facebook/flipper'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Apply Needs Attention Label
|
- name: Apply Needs Attention Label
|
||||||
uses: hramos/needs-attention@v1
|
uses: hramos/needs-attention@v2.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
id: needs-attention
|
||||||
|
- name: Result
|
||||||
|
run: echo '${{ steps.needs-attention.outputs.result }}'
|
||||||
|
|||||||
20
.github/workflows/js.yml
vendored
20
.github/workflows/js.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: js-flipper
|
name: js-flipper
|
||||||
|
# This action runs on 'git push' and PRs
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -9,12 +9,12 @@ jobs:
|
|||||||
working-directory: js/js-flipper
|
working-directory: js/js-flipper
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: yarn install (with retry)
|
- name: yarn install (with retry)
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
command: cd js/js-flipper && yarn
|
command: cd js/js-flipper && yarn
|
||||||
timeout_minutes: 30
|
timeout_minutes: 30
|
||||||
@@ -30,12 +30,12 @@ jobs:
|
|||||||
working-directory: js/react-flipper-example
|
working-directory: js/react-flipper-example
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: js-flipper - yarn install (with retry)
|
- name: js-flipper - yarn install (with retry)
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
command: cd js/js-flipper && yarn
|
command: cd js/js-flipper && yarn
|
||||||
timeout_minutes: 30
|
timeout_minutes: 30
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
run: yarn build
|
run: yarn build
|
||||||
working-directory: js/js-flipper
|
working-directory: js/js-flipper
|
||||||
- name: yarn install (with retry)
|
- name: yarn install (with retry)
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
command: cd js/react-flipper-example && yarn
|
command: cd js/react-flipper-example && yarn
|
||||||
timeout_minutes: 30
|
timeout_minutes: 30
|
||||||
|
|||||||
11
.github/workflows/nodejs-doctor.yml
vendored
11
.github/workflows/nodejs-doctor.yml
vendored
@@ -1,20 +1,17 @@
|
|||||||
name: Doctor Node CI
|
name: Doctor Node CI
|
||||||
|
# This action runs on 'git push' and PRs
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
doctor-directory: ./desktop/doctor
|
doctor-directory: ./desktop/doctor
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: install
|
- name: install
|
||||||
working-directory: ${{env.doctor-directory}}
|
working-directory: ${{env.doctor-directory}}
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|||||||
11
.github/workflows/nodejs-pkg.yml
vendored
11
.github/workflows/nodejs-pkg.yml
vendored
@@ -1,20 +1,17 @@
|
|||||||
name: PKG Node CI
|
name: PKG Node CI
|
||||||
|
# This actions runs on 'git push' and PRs
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
pkg-directory: ./desktop/pkg
|
pkg-directory: ./desktop/pkg
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: install
|
- name: install
|
||||||
working-directory: ${{env.pkg-directory}}
|
working-directory: ${{env.pkg-directory}}
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|||||||
20
.github/workflows/nodejs.yml
vendored
20
.github/workflows/nodejs.yml
vendored
@@ -1,25 +1,21 @@
|
|||||||
name: Desktop Node CI
|
name: Desktop Node CI
|
||||||
|
# This action run on 'git push' and PRs
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
desktop-directory: ./desktop
|
desktop-directory: ./desktop
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
node-version: [18.x]
|
||||||
os: ['ubuntu-latest', 'windows-latest', 'macos-latest']
|
os: ['ubuntu-latest', 'windows-latest', 'macos-latest']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- name: Get yarn cache directory path
|
- name: Get yarn cache directory path
|
||||||
@@ -64,25 +60,25 @@ jobs:
|
|||||||
run: yarn build --win
|
run: yarn build --win
|
||||||
working-directory: ${{env.desktop-directory}}
|
working-directory: ${{env.desktop-directory}}
|
||||||
- name: upload linux artifact
|
- name: upload linux artifact
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
with:
|
with:
|
||||||
name: Flipper-linux.zip
|
name: Flipper-linux.zip
|
||||||
path: dist/Flipper-linux.zip
|
path: dist/Flipper-linux.zip
|
||||||
- name: upload windows artifact
|
- name: upload windows artifact
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
with:
|
with:
|
||||||
name: Flipper-win.zip
|
name: Flipper-win.zip
|
||||||
path: dist/Flipper-win.zip
|
path: dist/Flipper-win.zip
|
||||||
- name: upload mac zip artifact
|
- name: upload mac zip artifact
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
with:
|
with:
|
||||||
name: Flipper-mac.zip
|
name: Flipper-mac.zip
|
||||||
path: dist/Flipper-mac.zip
|
path: dist/Flipper-mac.zip
|
||||||
- name: upload mac dmg artifact
|
- name: upload mac dmg artifact
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
if: matrix.os == 'macos-latest'
|
if: matrix.os == 'macos-latest'
|
||||||
with:
|
with:
|
||||||
name: Flipper-mac.dmg
|
name: Flipper-mac.dmg
|
||||||
|
|||||||
12
.github/workflows/packer.yml
vendored
12
.github/workflows/packer.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: Packer
|
name: Packer
|
||||||
|
# This action runs on 'git push' and PRs
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -7,11 +7,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: Setup toolchain
|
- name: Setup toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
# The selection of Rust toolchain is made based on the particular '@rev'
|
||||||
toolchain: 1.48.0
|
# of this Action being requested. For example "dtolnay/rust-toolchain@nightly"
|
||||||
|
# pulls in the nightly Rust toolchain, while "dtolnay/rust-toolchain@1.42.0"
|
||||||
|
# pulls in '1.42.0'.
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cd packer && cargo test
|
run: cd packer && cargo test
|
||||||
- name: Format
|
- name: Format
|
||||||
|
|||||||
22
.github/workflows/publish-android.yml
vendored
22
.github/workflows/publish-android.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
name: Publish Android
|
name: Publish Android
|
||||||
|
# This action runs on 'git push tag v*' and worflow dispatch as specified below
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -10,25 +10,23 @@ on:
|
|||||||
description: "Tag to upload artifacts to"
|
description: "Tag to upload artifacts to"
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- name: set up JDK
|
- name: set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v3.11.0
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
distribution: 'temurin'
|
||||||
|
java-version: 17
|
||||||
- name: Write GPG Sec Ring
|
- name: Write GPG Sec Ring
|
||||||
run: echo '${{ secrets.GPG_KEY_CONTENTS }}' | base64 -d > /tmp/secring.gpg
|
run: echo '${{ secrets.GPG_KEY_CONTENTS }}' | base64 -d > /tmp/secring.gpg
|
||||||
- name: Update gradle.properties
|
- name: Update gradle.properties
|
||||||
run: echo -e "signing.secretKeyRingFile=/tmp/secring.gpg\nsigning.keyId=${{ secrets.SIGNING_KEY_ID }}\nsigning.password=${{ secrets.SIGNING_PASSWORD }}\nmavenCentralPassword=${{ secrets.SONATYPE_NEXUS_PASSWORD }}\nmavenCentralUsername=${{ secrets.SONATYPE_NEXUS_USERNAME }}" >> gradle.properties
|
run: echo -e "signing.secretKeyRingFile=/tmp/secring.gpg\nsigning.keyId=${{ secrets.SIGNING_KEY_ID }}\nsigning.password=${{ secrets.SIGNING_PASSWORD }}\nmavenCentralPassword=${{ secrets.SONATYPE_NEXUS_PASSWORD }}\nmavenCentralUsername=${{ secrets.SONATYPE_NEXUS_USERNAME }}\nSONATYPE_HOST=DEFAULT\nRELEASE_SIGNING_ENABLED=true\nSONATYPE_AUTOMATIC_RELEASE=true" >> gradle.properties
|
||||||
- name: Compute build cache
|
- name: Compute build cache
|
||||||
run: ./scripts/checksum-android.sh checksum-android.txt
|
run: ./scripts/checksum-android.sh checksum-android.txt
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3.3.1
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches/modules-*
|
~/.gradle/caches/modules-*
|
||||||
@@ -38,9 +36,7 @@ jobs:
|
|||||||
- name: Build artifacts
|
- name: Build artifacts
|
||||||
run: ./gradlew :sample:assembleDebug :sample:assembleRelease && ./gradlew :android:assembleRelease
|
run: ./gradlew :sample:assembleDebug :sample:assembleRelease && ./gradlew :android:assembleRelease
|
||||||
- name: Upload Archives
|
- name: Upload Archives
|
||||||
run: ./gradlew publish -info --no-parallel --no-daemon
|
run: ./gradlew publish -info
|
||||||
- name: Release and close
|
|
||||||
run: ./gradlew closeAndReleaseRepository
|
|
||||||
- name: Clean secrets
|
- name: Clean secrets
|
||||||
if: always()
|
if: always()
|
||||||
run: rm /tmp/secring.gpg
|
run: rm /tmp/secring.gpg
|
||||||
@@ -48,7 +44,7 @@ jobs:
|
|||||||
run: mv android/sample/build/outputs/apk/debug/sample-debug.apk SampleApp-android.apk
|
run: mv android/sample/build/outputs/apk/debug/sample-debug.apk SampleApp-android.apk
|
||||||
- name: Attach sample APK to release
|
- name: Attach sample APK to release
|
||||||
if: ${{ github.event.inputs.tag != '' }}
|
if: ${{ github.event.inputs.tag != '' }}
|
||||||
uses: passy/github-upload-release-artifacts-action@v2.2.2
|
uses: aigoncharov/github-upload-release-artifacts-action@2.2.3
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
7
.github/workflows/publish-npm.yml
vendored
7
.github/workflows/publish-npm.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Publish NPM
|
name: Publish NPM
|
||||||
|
# This action runs on 'git push tag v*'
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -13,10 +14,10 @@ jobs:
|
|||||||
publish:
|
publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: Install
|
- name: Install
|
||||||
run: yarn
|
run: yarn
|
||||||
- name: Set versions
|
- name: Set versions
|
||||||
|
|||||||
9
.github/workflows/publish-pods.yml
vendored
9
.github/workflows/publish-pods.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Publish Pods
|
name: Publish Pods
|
||||||
|
# This action runs on 'git push tag v*'
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -9,7 +10,7 @@ jobs:
|
|||||||
publish_flipper_pod:
|
publish_flipper_pod:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
@@ -29,7 +30,7 @@ jobs:
|
|||||||
needs: publish_flipper_pod
|
needs: publish_flipper_pod
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Install Dependences
|
- name: Install Dependences
|
||||||
run: pod repo update
|
run: pod repo update
|
||||||
@@ -49,7 +50,7 @@ jobs:
|
|||||||
needs: publish_flipperkit_pod
|
needs: publish_flipperkit_pod
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
|
|
||||||
- name: Update Flipper's Podspec
|
- name: Update Flipper's Podspec
|
||||||
run: ./scripts/update-pod-versions.sh ./ ./Flipper.podspec
|
run: ./scripts/update-pod-versions.sh ./ ./Flipper.podspec
|
||||||
@@ -123,7 +124,7 @@ jobs:
|
|||||||
git branch
|
git branch
|
||||||
|
|
||||||
- name: Create PR to Update Podfile.lock
|
- name: Create PR to Update Podfile.lock
|
||||||
uses: peter-evans/create-pull-request@v3
|
uses: peter-evans/create-pull-request@v5.0.2
|
||||||
with:
|
with:
|
||||||
title: "Automated: Update Podfile.lock"
|
title: "Automated: Update Podfile.lock"
|
||||||
body: |
|
body: |
|
||||||
|
|||||||
35
.github/workflows/react-native-example.yml
vendored
35
.github/workflows/react-native-example.yml
vendored
@@ -1,5 +1,7 @@
|
|||||||
name: Build React Native example
|
name: Build React Native example
|
||||||
|
# This action runs on 'git push' and PRs
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-react-native-example-ios:
|
build-react-native-example-ios:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
@@ -7,11 +9,11 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: react-native/ReactNativeFlipperExample
|
working-directory: react-native/ReactNativeFlipperExample
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v2.1.5
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: '18.x'
|
||||||
- uses: maxim-lobanov/setup-cocoapods@v1
|
- uses: maxim-lobanov/setup-cocoapods@v1.3.0
|
||||||
with:
|
with:
|
||||||
# Path to Podfile.lock file to determine Cocoapods version
|
# Path to Podfile.lock file to determine Cocoapods version
|
||||||
# n.b. doesn't seem to respect cwd:
|
# n.b. doesn't seem to respect cwd:
|
||||||
@@ -34,17 +36,18 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: react-native/ReactNativeFlipperExample
|
working-directory: react-native/ReactNativeFlipperExample
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v2.1.5
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: '18.x'
|
||||||
- name: set up JDK
|
- name: set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v3.11.0
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
distribution: 'temurin'
|
||||||
|
java-version: '11'
|
||||||
- name: Compute build cache
|
- name: Compute build cache
|
||||||
run: ${GITHUB_WORKSPACE}/scripts/checksum-android.sh checksum-android.txt
|
run: ${GITHUB_WORKSPACE}/scripts/checksum-android.sh checksum-android.txt
|
||||||
- uses: actions/cache@v2
|
- uses: actions/cache@v3.3.1
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches/modules-*
|
~/.gradle/caches/modules-*
|
||||||
@@ -67,14 +70,16 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
working-directory: react-native/ReactNativeFlipperExample
|
working-directory: react-native/ReactNativeFlipperExample
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
- uses: actions/setup-node@v2.1.5
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: '18.x'
|
||||||
- uses: nuget/setup-nuget@v1
|
- name: Set up NuGet.exe
|
||||||
|
- uses: NuGet/setup-nuget@v1.2.0
|
||||||
with:
|
with:
|
||||||
nuget-version: '5.x'
|
nuget-version: '5.x'
|
||||||
- uses: microsoft/setup-msbuild@v1.1
|
- name: Add msbuild to PATH
|
||||||
|
- uses: microsoft/setup-msbuild@v1.3.1
|
||||||
- name: Gather environment info
|
- name: Gather environment info
|
||||||
run: npx envinfo
|
run: npx envinfo
|
||||||
- name: Install vcpkg packages
|
- name: Install vcpkg packages
|
||||||
|
|||||||
119
.github/workflows/release.yml
vendored
119
.github/workflows/release.yml
vendored
@@ -1,4 +1,5 @@
|
|||||||
name: Release
|
name: Release
|
||||||
|
# This action runs on push to 'main' and below specified paths
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -11,12 +12,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
tag: ${{ steps.tag-version-commit.outputs.tag }}
|
tag: ${{ steps.tag-version-commit.outputs.tag }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: passy/extract-version-commit@v1.0.0
|
- uses: passy/extract-version-commit@v1.0.0
|
||||||
id: extract-version-commit
|
id: extract-version-commit
|
||||||
with:
|
with:
|
||||||
version_regex: '^Flipper Release: v([0-9]+\.[0-9]+\.[0-9]+)(?:\n|$)'
|
version_regex: '^Flipper Release: v([0-9]+\.[0-9]+\.[0-9]+)(?:\n|$)'
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
if: ${{ steps.extract-version-commit.outputs.commit != ''}}
|
if: ${{ steps.extract-version-commit.outputs.commit != ''}}
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.extract-version-commit.outputs.commit }}
|
ref: ${{ steps.extract-version-commit.outputs.commit }}
|
||||||
@@ -31,12 +33,12 @@ jobs:
|
|||||||
version_assertion_command: 'grep -q "\"version\": \"$version\"" desktop/package.json'
|
version_assertion_command: 'grep -q "\"version\": \"$version\"" desktop/package.json'
|
||||||
- name: Create release
|
- name: Create release
|
||||||
if: ${{ steps.tag-version-commit.outputs.tag != '' }}
|
if: ${{ steps.tag-version-commit.outputs.tag != '' }}
|
||||||
uses: actions/create-release@v1
|
uses: softprops/action-gh-release@v0.1.15
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ steps.tag-version-commit.outputs.tag }}
|
tag_name: ${{ steps.tag-version-commit.outputs.tag }}
|
||||||
release_name: ${{ steps.tag-version-commit.outputs.tag }}
|
name: ${{ steps.tag-version-commit.outputs.tag }}
|
||||||
body: |
|
body: |
|
||||||
See https://github.com/facebook/flipper/blob/main/desktop/static/CHANGELOG.md
|
See https://github.com/facebook/flipper/blob/main/desktop/static/CHANGELOG.md
|
||||||
for full notes.
|
for full notes.
|
||||||
@@ -51,30 +53,65 @@ jobs:
|
|||||||
desktop-directory: ./desktop
|
desktop-directory: ./desktop
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: Install
|
- name: Install
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: cd ${{env.desktop-directory}} && yarn
|
command: cd ${{env.desktop-directory}} && yarn
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 30
|
timeout_minutes: 30
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: cd ${{env.desktop-directory}} && yarn build --mac --mac-dmg
|
command: cd ${{env.desktop-directory}} && yarn build --mac --mac-dmg
|
||||||
- name: Upload
|
- name: Upload
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
with:
|
with:
|
||||||
name: 'Flipper-mac.dmg'
|
name: 'Flipper-mac.dmg'
|
||||||
path: 'dist/Flipper-mac.dmg'
|
path: 'dist/Flipper-mac.dmg'
|
||||||
|
|
||||||
|
build-server-mac:
|
||||||
|
needs:
|
||||||
|
- release
|
||||||
|
runs-on: macos-latest
|
||||||
|
env:
|
||||||
|
desktop-directory: ./desktop
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3.5.3
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
|
- uses: actions/setup-node@v3.6.0
|
||||||
|
with:
|
||||||
|
node-version: '18.x'
|
||||||
|
- name: Install
|
||||||
|
uses: nick-invision/retry@v2.0.0
|
||||||
|
with:
|
||||||
|
timeout_minutes: 10
|
||||||
|
max_attempts: 3
|
||||||
|
command: cd ${{env.desktop-directory}} && yarn
|
||||||
|
- name: Build
|
||||||
|
run: cd ${{env.desktop-directory}} && yarn build:flipper-server --mac --dmg
|
||||||
|
- name: List dist artifacts
|
||||||
|
run: ls -l dist/
|
||||||
|
- name: Upload x86-64
|
||||||
|
uses: actions/upload-artifact@v3.1.2
|
||||||
|
with:
|
||||||
|
name: 'Flipper-server-mac-x64.dmg'
|
||||||
|
path: 'dist/Flipper-server-mac-x64.dmg'
|
||||||
|
- name: Upload aarch64
|
||||||
|
uses: actions/upload-artifact@v3.1.2
|
||||||
|
with:
|
||||||
|
name: 'Flipper-server-mac-aarch64.dmg'
|
||||||
|
path: 'dist/Flipper-server-mac-aarch64.dmg'
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
needs:
|
needs:
|
||||||
- release
|
- release
|
||||||
@@ -83,26 +120,26 @@ jobs:
|
|||||||
desktop-directory: ./desktop
|
desktop-directory: ./desktop
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: Install
|
- name: Install
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: cd ${{env.desktop-directory}} && yarn
|
command: cd ${{env.desktop-directory}} && yarn
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 30
|
timeout_minutes: 30
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
command: cd ${{env.desktop-directory}} && yarn build --linux
|
command: cd ${{env.desktop-directory}} && yarn build --linux
|
||||||
- name: Upload Linux
|
- name: Upload Linux
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
with:
|
with:
|
||||||
name: 'Flipper-linux.zip'
|
name: 'Flipper-linux.zip'
|
||||||
path: 'dist/Flipper-linux.zip'
|
path: 'dist/Flipper-linux.zip'
|
||||||
@@ -115,28 +152,28 @@ jobs:
|
|||||||
desktop-directory: ./desktop
|
desktop-directory: ./desktop
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: Install
|
- name: Install
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 10
|
timeout_minutes: 10
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
command: cd ${{env.desktop-directory}}; yarn
|
command: cd ${{env.desktop-directory}}; yarn
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: nick-invision/retry@v2.6.0
|
uses: nick-fields/retry@v2.8.3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 30
|
timeout_minutes: 30
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
command: cd ${{env.desktop-directory}}; yarn build --win
|
command: cd ${{env.desktop-directory}}; yarn build --win
|
||||||
- name: Upload Windows
|
- name: Upload Windows
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v3.1.2
|
||||||
with:
|
with:
|
||||||
name: 'Flipper-win.zip'
|
name: 'Flipper-win.zip'
|
||||||
path: 'dist/Flipper-win.zip'
|
path: 'dist/Flipper-win.zip'
|
||||||
@@ -149,12 +186,12 @@ jobs:
|
|||||||
desktop-directory: ./desktop
|
desktop-directory: ./desktop
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3.5.3
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
node-version: '16.x'
|
node-version: '18.x'
|
||||||
- name: Install
|
- name: Install
|
||||||
uses: nick-invision/retry@v2.0.0
|
uses: nick-invision/retry@v2.0.0
|
||||||
with:
|
with:
|
||||||
@@ -166,7 +203,7 @@ jobs:
|
|||||||
- name: List dist artifacts
|
- name: List dist artifacts
|
||||||
run: ls -l dist/
|
run: ls -l dist/
|
||||||
- name: Upload flipper-server
|
- name: Upload flipper-server
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3.1.2
|
||||||
with:
|
with:
|
||||||
name: 'flipper-server.tgz'
|
name: 'flipper-server.tgz'
|
||||||
path: 'dist/flipper-server.tgz'
|
path: 'dist/flipper-server.tgz'
|
||||||
@@ -176,17 +213,33 @@ jobs:
|
|||||||
- build-win
|
- build-win
|
||||||
- build-linux
|
- build-linux
|
||||||
- build-mac
|
- build-mac
|
||||||
|
- build-server-mac
|
||||||
- build-flipper-server
|
- build-flipper-server
|
||||||
- release
|
- release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v3.5.3
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- name: Download Mac
|
- name: Download Mac
|
||||||
if: ${{ needs.release.outputs.tag != '' }}
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: 'Flipper-mac.dmg'
|
name: 'Flipper-mac.dmg'
|
||||||
path: 'Flipper-mac.dmg'
|
path: 'Flipper-mac.dmg'
|
||||||
|
- name: Download Flipper Server x86-64
|
||||||
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
|
uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: 'Flipper-server-mac-x64.dmg'
|
||||||
|
path: 'Flipper-server-mac-x64.dmg'
|
||||||
|
- name: Download Flipper Server aarch64
|
||||||
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
|
uses: actions/download-artifact@v1
|
||||||
|
with:
|
||||||
|
name: 'Flipper-server-mac-aarch64.dmg'
|
||||||
|
path: 'Flipper-server-mac-aarch64.dmg'
|
||||||
- name: Download Linux
|
- name: Download Linux
|
||||||
if: ${{ needs.release.outputs.tag != '' }}
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
uses: actions/download-artifact@v1
|
uses: actions/download-artifact@v1
|
||||||
@@ -207,12 +260,12 @@ jobs:
|
|||||||
path: 'flipper-server.tgz'
|
path: 'flipper-server.tgz'
|
||||||
- name: GitHub Upload Release Artifacts
|
- name: GitHub Upload Release Artifacts
|
||||||
if: ${{ needs.release.outputs.tag != '' }}
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
uses: passy/github-upload-release-artifacts-action@v2.2.2
|
uses: aigoncharov/github-upload-release-artifacts-action@2.2.3
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
created_tag: ${{ needs.release.outputs.tag }}
|
created_tag: ${{ needs.release.outputs.tag }}
|
||||||
args: Flipper-mac.dmg/Flipper-mac.dmg Flipper-linux.zip/Flipper-linux.zip Flipper-win.zip/Flipper-win.zip flipper-server.tgz/flipper-server.tgz
|
args: Flipper-mac.dmg/Flipper-mac.dmg Flipper-linux.zip/Flipper-linux.zip Flipper-win.zip/Flipper-win.zip flipper-server.tgz/flipper-server.tgz Flipper-server-mac-x64.dmg/Flipper-server-mac-x64.dmg Flipper-server-mac-aarch64.dmg/Flipper-server-mac-aarch64.dmg
|
||||||
- name: Set up npm token
|
- name: Set up npm token
|
||||||
run: echo "//registry.yarnpkg.com/:_authToken=${{ secrets.FLIPPER_NPM_TOKEN }}" >> ~/.npmrc
|
run: echo "//registry.yarnpkg.com/:_authToken=${{ secrets.FLIPPER_NPM_TOKEN }}" >> ~/.npmrc
|
||||||
- name: Publish flipper-server on NPM
|
- name: Publish flipper-server on NPM
|
||||||
@@ -223,7 +276,7 @@ jobs:
|
|||||||
yarn publish
|
yarn publish
|
||||||
- name: Open issue on failure
|
- name: Open issue on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: JasonEtco/create-an-issue@v2.4.0
|
uses: JasonEtco/create-an-issue@v2.9.1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
@@ -231,6 +284,7 @@ jobs:
|
|||||||
WORKFLOW_NAME: "Publish"
|
WORKFLOW_NAME: "Publish"
|
||||||
with:
|
with:
|
||||||
filename: .github/action-failure-template.md
|
filename: .github/action-failure-template.md
|
||||||
|
|
||||||
dispatch:
|
dispatch:
|
||||||
needs:
|
needs:
|
||||||
- release
|
- release
|
||||||
@@ -239,23 +293,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Publish Workflow Dispatch
|
- name: Publish Workflow Dispatch
|
||||||
if: ${{ needs.release.outputs.tag != '' }}
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
uses: benc-uk/workflow-dispatch@v1.1
|
uses: benc-uk/workflow-dispatch@v1.2.2
|
||||||
with:
|
with:
|
||||||
workflow: Publish Pods
|
workflow: Publish Pods
|
||||||
token: ${{ secrets.PERSONAL_TOKEN }}
|
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- name: Publish NPM
|
- name: Publish NPM
|
||||||
if: ${{ needs.release.outputs.tag != '' }}
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
uses: benc-uk/workflow-dispatch@v1.1
|
uses: benc-uk/workflow-dispatch@v1.2.2
|
||||||
with:
|
with:
|
||||||
workflow: Publish NPM
|
workflow: Publish NPM
|
||||||
token: ${{ secrets.PERSONAL_TOKEN }}
|
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
- name: Publish Android
|
- name: Publish Android
|
||||||
if: ${{ needs.release.outputs.tag != '' }}
|
if: ${{ needs.release.outputs.tag != '' }}
|
||||||
uses: benc-uk/workflow-dispatch@v1.1
|
uses: benc-uk/workflow-dispatch@v1.2.2
|
||||||
with:
|
with:
|
||||||
workflow: Publish Android
|
workflow: Publish Android
|
||||||
token: ${{ secrets.PERSONAL_TOKEN }}
|
|
||||||
ref: ${{ needs.release.outputs.tag }}
|
ref: ${{ needs.release.outputs.tag }}
|
||||||
inputs: '{"tag": "${{ needs.release.outputs.tag }}"}'
|
inputs: '{"tag": "${{ needs.release.outputs.tag }}"}'
|
||||||
|
|||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -8,10 +8,20 @@ bundle.map
|
|||||||
*.bundle.map
|
*.bundle.map
|
||||||
.env
|
.env
|
||||||
desktop-branch-*/
|
desktop-branch-*/
|
||||||
|
auth.token
|
||||||
|
manifest.json
|
||||||
|
|
||||||
# conflicts with FB internal infra
|
# conflicts with FB internal infra
|
||||||
.watchmanconfig
|
.watchmanconfig
|
||||||
|
|
||||||
|
# Don't checkout the yarn/npm lock files that we don't need
|
||||||
|
./yarn.lock
|
||||||
|
./package-lock.json
|
||||||
|
js/yarn.lock
|
||||||
|
js/package-lock.json
|
||||||
|
react-native/yarn.lock
|
||||||
|
react-native/package-lock.json
|
||||||
|
|
||||||
# iOS / Xcode
|
# iOS / Xcode
|
||||||
*.xcworkspace
|
*.xcworkspace
|
||||||
**/Pods/
|
**/Pods/
|
||||||
@@ -49,3 +59,5 @@ website/src/embedded-pages/docs/plugins/
|
|||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
**/*/flipper-server-log.out
|
**/*/flipper-server-log.out
|
||||||
|
|
||||||
|
*.salive
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# This source code is licensed under the MIT license found in the
|
# This source code is licensed under the MIT license found in the
|
||||||
# LICENSE file in the root directory of this source tree.
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
flipperkit_version = '0.162.0'
|
flipperkit_version = '0.222.0'
|
||||||
Pod::Spec.new do |spec|
|
Pod::Spec.new do |spec|
|
||||||
spec.name = 'Flipper'
|
spec.name = 'Flipper'
|
||||||
spec.cocoapods_version = '>= 1.10'
|
spec.cocoapods_version = '>= 1.10'
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
# LICENSE file in the root directory of this source tree.
|
# LICENSE file in the root directory of this source tree.
|
||||||
|
|
||||||
folly_compiler_flags = '-DDEBUG=1 -DFLIPPER_OSS=1 -DFB_SONARKIT_ENABLED=1 -DFOLLY_HAVE_BACKTRACE=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0'
|
folly_compiler_flags = '-DDEBUG=1 -DFLIPPER_OSS=1 -DFB_SONARKIT_ENABLED=1 -DFOLLY_HAVE_BACKTRACE=1 -DFOLLY_HAVE_CLOCK_GETTIME=1 -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0'
|
||||||
yogakit_version = '~> 1.18'
|
flipperkit_version = '0.222.0'
|
||||||
flipperkit_version = '0.162.0'
|
|
||||||
Pod::Spec.new do |spec|
|
Pod::Spec.new do |spec|
|
||||||
spec.name = 'FlipperKit'
|
spec.name = 'FlipperKit'
|
||||||
spec.version = flipperkit_version
|
spec.version = flipperkit_version
|
||||||
@@ -17,7 +16,7 @@ Pod::Spec.new do |spec|
|
|||||||
spec.source = { :git => 'https://github.com/facebook/flipper.git',
|
spec.source = { :git => 'https://github.com/facebook/flipper.git',
|
||||||
:tag=> "v"+flipperkit_version }
|
:tag=> "v"+flipperkit_version }
|
||||||
spec.module_name = 'FlipperKit'
|
spec.module_name = 'FlipperKit'
|
||||||
spec.platforms = { :ios => "10.0" }
|
spec.platforms = { :ios => "11.0" }
|
||||||
spec.default_subspecs = "Core"
|
spec.default_subspecs = "Core"
|
||||||
|
|
||||||
# This subspec is necessary since FBDefines.h is imported as <FBDefines/FBDefines.h>
|
# This subspec is necessary since FBDefines.h is imported as <FBDefines/FBDefines.h>
|
||||||
@@ -77,7 +76,7 @@ Pod::Spec.new do |spec|
|
|||||||
ss.dependency 'FlipperKit/CppBridge'
|
ss.dependency 'FlipperKit/CppBridge'
|
||||||
ss.dependency 'FlipperKit/FKPortForwarding'
|
ss.dependency 'FlipperKit/FKPortForwarding'
|
||||||
ss.dependency 'Flipper', '~>'+flipperkit_version
|
ss.dependency 'Flipper', '~>'+flipperkit_version
|
||||||
ss.dependency 'SocketRocket', '~> 0.6.0'
|
ss.dependency 'SocketRocket', '~> 0.7.0'
|
||||||
ss.compiler_flags = folly_compiler_flags
|
ss.compiler_flags = folly_compiler_flags
|
||||||
ss.source_files = 'iOS/FlipperKit/*.{h,m,mm}', 'iOS/FlipperKit/CppBridge/*.{h,mm}'
|
ss.source_files = 'iOS/FlipperKit/*.{h,m,mm}', 'iOS/FlipperKit/CppBridge/*.{h,mm}'
|
||||||
ss.public_header_files = 'iOS/FlipperKit/**/{FlipperDiagnosticsViewController,FlipperStateUpdateListener,FlipperClient,FlipperPlugin,FlipperConnection,FlipperResponder,SKMacros,FlipperKitCertificateProvider}.h'
|
ss.public_header_files = 'iOS/FlipperKit/**/{FlipperDiagnosticsViewController,FlipperStateUpdateListener,FlipperClient,FlipperPlugin,FlipperConnection,FlipperResponder,SKMacros,FlipperKitCertificateProvider}.h'
|
||||||
@@ -119,8 +118,7 @@ Pod::Spec.new do |spec|
|
|||||||
ss.private_header_files = 'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/SKObject.h',
|
ss.private_header_files = 'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/SKObject.h',
|
||||||
'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/UIColor+SKSonarValueCoder.h',
|
'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/UIColor+SKSonarValueCoder.h',
|
||||||
'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/utils/SKObjectHash.h',
|
'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/utils/SKObjectHash.h',
|
||||||
'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/utils/SKSwizzle.h',
|
'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/utils/SKSwizzle.h'
|
||||||
'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutHelpers/FlipperKitLayoutHelpers/utils/SKYogaKitHelper.h'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
spec.subspec 'FlipperKitLayoutIOSDescriptors' do |ss|
|
spec.subspec 'FlipperKitLayoutIOSDescriptors' do |ss|
|
||||||
@@ -128,7 +126,6 @@ Pod::Spec.new do |spec|
|
|||||||
ss.dependency 'FlipperKit/Core'
|
ss.dependency 'FlipperKit/Core'
|
||||||
ss.dependency 'FlipperKit/FlipperKitHighlightOverlay'
|
ss.dependency 'FlipperKit/FlipperKitHighlightOverlay'
|
||||||
ss.dependency 'FlipperKit/FlipperKitLayoutHelpers'
|
ss.dependency 'FlipperKit/FlipperKitLayoutHelpers'
|
||||||
ss.dependency 'YogaKit', yogakit_version
|
|
||||||
ss.compiler_flags = folly_compiler_flags
|
ss.compiler_flags = folly_compiler_flags
|
||||||
ss.source_files = 'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutIOSDescriptors/**/*.{h,mm,m}'
|
ss.source_files = 'iOS/Plugins/FlipperKitPluginUtils/FlipperKitLayoutIOSDescriptors/**/*.{h,mm,m}'
|
||||||
end
|
end
|
||||||
@@ -140,7 +137,6 @@ Pod::Spec.new do |spec|
|
|||||||
ss.dependency 'FlipperKit/FlipperKitHighlightOverlay'
|
ss.dependency 'FlipperKit/FlipperKitHighlightOverlay'
|
||||||
ss.dependency 'FlipperKit/FlipperKitLayoutHelpers'
|
ss.dependency 'FlipperKit/FlipperKitLayoutHelpers'
|
||||||
ss.dependency 'FlipperKit/FlipperKitLayoutIOSDescriptors'
|
ss.dependency 'FlipperKit/FlipperKitLayoutIOSDescriptors'
|
||||||
ss.dependency 'YogaKit', yogakit_version
|
|
||||||
ss.compiler_flags = folly_compiler_flags
|
ss.compiler_flags = folly_compiler_flags
|
||||||
ss.public_header_files = 'iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h',
|
ss.public_header_files = 'iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h',
|
||||||
'iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutPlugin/SKDescriptorMapper.h'
|
'iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutPlugin/SKDescriptorMapper.h'
|
||||||
@@ -149,11 +145,20 @@ Pod::Spec.new do |spec|
|
|||||||
ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)\"/Headers/Private/FlipperKit/**", "ONLY_ACTIVE_ARCH": "YES" }
|
ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)\"/Headers/Private/FlipperKit/**", "ONLY_ACTIVE_ARCH": "YES" }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
spec.subspec "FlipperKitUIDebuggerPlugin" do |ss|
|
||||||
|
ss.header_dir = "FlipperKitUIDebuggerPlugin"
|
||||||
|
ss.dependency 'FlipperKit/Core'
|
||||||
|
ss.public_header_files = 'iOS/Plugins/FlipperKitUIDebuggerPlugin/FlipperKitUIDebuggerPlugin/FlipperKitUIDebuggerPlugin.h'
|
||||||
|
ss.source_files = 'iOS/Plugins/FlipperKitUIDebuggerPlugin/FlipperKitUIDebuggerPlugin/**/*.{h,cpp,m,mm}'
|
||||||
|
ss.exclude_files = ['iOS/Plugins/FlipperKitUIDebuggerPlugin/fb/*','iOS/Plugins/FlipperKitUIDebuggerPlugin/facebook/*','iOS/Plugins/FlipperKitUIDebuggerPlugin/FlipperKitUIDebuggerPlugin/fb/*' ,'iOS/Plugins/FlipperKitUIDebuggerPlugin/FlipperKitUIDebuggerPlugin/facebook/*']
|
||||||
|
ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)\"/Headers/Private/FlipperKit/**", "ONLY_ACTIVE_ARCH": "YES" }
|
||||||
|
end
|
||||||
|
|
||||||
spec.subspec "FlipperKitLayoutComponentKitSupport" do |ss|
|
spec.subspec "FlipperKitLayoutComponentKitSupport" do |ss|
|
||||||
ss.header_dir = "FlipperKitLayoutComponentKitSupport"
|
ss.header_dir = "FlipperKitLayoutComponentKitSupport"
|
||||||
ss.dependency 'FlipperKit/Core'
|
ss.dependency 'FlipperKit/Core'
|
||||||
ss.dependency 'ComponentKit', '0.31'
|
ss.dependency 'ComponentKit', '0.31'
|
||||||
ss.dependency 'RenderCore', '0.31' # Pinning it to 0.30, as there won't be any new releases from CK team.
|
ss.dependency 'RenderCore', '0.31'
|
||||||
ss.dependency 'FlipperKit/FlipperKitLayoutPlugin'
|
ss.dependency 'FlipperKit/FlipperKitLayoutPlugin'
|
||||||
ss.dependency 'FlipperKit/FlipperKitLayoutTextSearchable'
|
ss.dependency 'FlipperKit/FlipperKitLayoutTextSearchable'
|
||||||
ss.dependency 'FlipperKit/FlipperKitHighlightOverlay'
|
ss.dependency 'FlipperKit/FlipperKitHighlightOverlay'
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Flipper (formerly Sonar) is a platform for debugging mobile apps on iOS and Android and, recently, even JS apps in your browser or in Node.js. Visualize, inspect, and control your apps from a simple desktop interface. Use Flipper as is or extend it using the plugin API.
|
Flipper (formerly Sonar) is a platform for debugging mobile apps on iOS and Android and JS apps in your browser or in Node.js. Visualize, inspect, and control your apps from a simple desktop interface. Use Flipper as is or extend it using the plugin API.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||

|

|
||||||
@@ -139,6 +139,8 @@ Start up an android emulator and run the following in the project root:
|
|||||||
|
|
||||||
## React Native SDK + Sample app
|
## React Native SDK + Sample app
|
||||||
|
|
||||||
|
> Requires RN 0.69+!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd react-native/ReactNativeFlipperExample
|
cd react-native/ReactNativeFlipperExample
|
||||||
yarn
|
yarn
|
||||||
|
|||||||
@@ -7,11 +7,10 @@
|
|||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
ndkVersion rootProject.ndkVersion
|
ndkVersion rootProject.ndkVersion
|
||||||
@@ -32,7 +31,7 @@ android {
|
|||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_shared'
|
arguments '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_shared'
|
||||||
targets 'flipper', 'event_shared', 'event_extra_shared', 'event_core_shared'
|
targets 'flipper'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,6 +48,11 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility rootProject.javaTargetVersion
|
||||||
|
sourceCompatibility rootProject.javaTargetVersion
|
||||||
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
prefab true
|
prefab true
|
||||||
}
|
}
|
||||||
@@ -68,6 +72,7 @@ android {
|
|||||||
compileOnly deps.proguardAnnotations
|
compileOnly deps.proguardAnnotations
|
||||||
implementation deps.kotlinStdLibrary
|
implementation deps.kotlinStdLibrary
|
||||||
|
|
||||||
|
implementation deps.kotlinCoroutinesAndroid
|
||||||
implementation deps.openssl
|
implementation deps.openssl
|
||||||
implementation deps.fbjni
|
implementation deps.fbjni
|
||||||
implementation deps.soloader
|
implementation deps.soloader
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.noop'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
@@ -30,10 +31,3 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.vanniktech.maven.publish'
|
apply plugin: 'com.vanniktech.maven.publish'
|
||||||
|
|
||||||
task sourcesJar(type: Jar) {
|
|
||||||
from android.sourceSets.main.java.srcDirs
|
|
||||||
classifier = 'sources'
|
|
||||||
}
|
|
||||||
|
|
||||||
artifacts.add('archives', sourcesJar)
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.facebook.flipper">
|
package="com.facebook.flipper.noop">
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
~ Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
~
|
|
||||||
~ This source code is licensed under the MIT license found in the LICENSE
|
|
||||||
~ file in the root directory of this source tree.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="com.facebook.flipper.plugins.fresco">
|
|
||||||
</manifest>
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.flipper.plugins.fresco;
|
|
||||||
|
|
||||||
public interface FrescoFlipperDebugPrefHelper {
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
void onEnabledStatusChanged(boolean enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDebugOverlayEnabled(boolean enabled);
|
|
||||||
|
|
||||||
boolean isDebugOverlayEnabled();
|
|
||||||
|
|
||||||
void setDebugOverlayEnabledListener(Listener l);
|
|
||||||
}
|
|
||||||
@@ -1,651 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.flipper.plugins.fresco;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Pair;
|
|
||||||
import bolts.Continuation;
|
|
||||||
import bolts.Task;
|
|
||||||
import com.facebook.cache.common.CacheKey;
|
|
||||||
import com.facebook.cache.common.SimpleCacheKey;
|
|
||||||
import com.facebook.cache.disk.DiskStorage;
|
|
||||||
import com.facebook.common.internal.ByteStreams;
|
|
||||||
import com.facebook.common.internal.Preconditions;
|
|
||||||
import com.facebook.common.internal.Predicate;
|
|
||||||
import com.facebook.common.memory.PooledByteBuffer;
|
|
||||||
import com.facebook.common.memory.PooledByteBufferInputStream;
|
|
||||||
import com.facebook.common.memory.manager.DebugMemoryManager;
|
|
||||||
import com.facebook.common.memory.manager.NoOpDebugMemoryManager;
|
|
||||||
import com.facebook.common.references.CloseableReference;
|
|
||||||
import com.facebook.common.references.SharedReference;
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
|
||||||
import com.facebook.drawee.backends.pipeline.info.ImageLoadStatus;
|
|
||||||
import com.facebook.drawee.backends.pipeline.info.ImageOriginUtils;
|
|
||||||
import com.facebook.drawee.backends.pipeline.info.ImagePerfData;
|
|
||||||
import com.facebook.drawee.backends.pipeline.info.ImagePerfDataListener;
|
|
||||||
import com.facebook.flipper.core.FlipperArray;
|
|
||||||
import com.facebook.flipper.core.FlipperConnection;
|
|
||||||
import com.facebook.flipper.core.FlipperObject;
|
|
||||||
import com.facebook.flipper.core.FlipperReceiver;
|
|
||||||
import com.facebook.flipper.core.FlipperResponder;
|
|
||||||
import com.facebook.flipper.perflogger.FlipperPerfLogger;
|
|
||||||
import com.facebook.flipper.perflogger.NoOpFlipperPerfLogger;
|
|
||||||
import com.facebook.flipper.plugins.common.BufferingFlipperPlugin;
|
|
||||||
import com.facebook.flipper.plugins.fresco.objecthelper.FlipperObjectHelper;
|
|
||||||
import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory;
|
|
||||||
import com.facebook.imagepipeline.cache.CountingMemoryCacheInspector;
|
|
||||||
import com.facebook.imagepipeline.cache.CountingMemoryCacheInspector.DumpInfoEntry;
|
|
||||||
import com.facebook.imagepipeline.core.ImagePipelineFactory;
|
|
||||||
import com.facebook.imagepipeline.debug.CloseableReferenceLeakTracker;
|
|
||||||
import com.facebook.imagepipeline.debug.DebugImageTracker;
|
|
||||||
import com.facebook.imagepipeline.debug.FlipperImageTracker;
|
|
||||||
import com.facebook.imagepipeline.image.CloseableBitmap;
|
|
||||||
import com.facebook.imagepipeline.image.CloseableImage;
|
|
||||||
import com.facebook.imagepipeline.image.EncodedImage;
|
|
||||||
import com.facebook.imageutils.BitmapUtil;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows Sonar to display the contents of Fresco's caches. This is useful for developers to debug
|
|
||||||
* what images are being held in cache as they navigate through their app.
|
|
||||||
*/
|
|
||||||
public class FrescoFlipperPlugin extends BufferingFlipperPlugin
|
|
||||||
implements ImagePerfDataListener, CloseableReferenceLeakTracker.Listener {
|
|
||||||
|
|
||||||
private static final String FRESCO_EVENT = "events";
|
|
||||||
private static final String FRESCO_DEBUGOVERLAY_EVENT = "debug_overlay_event";
|
|
||||||
private static final String FRESCO_CLOSEABLE_REFERENCE_LEAK_EVENT =
|
|
||||||
"closeable_reference_leak_event";
|
|
||||||
|
|
||||||
private static final int BITMAP_PREVIEW_WIDTH = 150;
|
|
||||||
private static final int BITMAP_PREVIEW_HEIGHT = 150;
|
|
||||||
private static final int BITMAP_SCALING_THRESHOLD_WIDTH = 200;
|
|
||||||
private static final int BITMAP_SCALING_THRESHOLD_HEIGHT = 200;
|
|
||||||
|
|
||||||
/** Helper for clearing cache. */
|
|
||||||
private static final Predicate<CacheKey> ALWAYS_TRUE_PREDICATE =
|
|
||||||
new Predicate<CacheKey>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(CacheKey cacheKey) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final FlipperImageTracker mFlipperImageTracker;
|
|
||||||
private final PlatformBitmapFactory mPlatformBitmapFactory;
|
|
||||||
@Nullable private final FlipperObjectHelper mSonarObjectHelper;
|
|
||||||
private final DebugMemoryManager mMemoryManager;
|
|
||||||
private final FlipperPerfLogger mPerfLogger;
|
|
||||||
@Nullable private final FrescoFlipperDebugPrefHelper mDebugPrefHelper;
|
|
||||||
private final List<FlipperObject> mEvents = new ArrayList<>();
|
|
||||||
|
|
||||||
public FrescoFlipperPlugin(
|
|
||||||
DebugImageTracker imageTracker,
|
|
||||||
PlatformBitmapFactory bitmapFactory,
|
|
||||||
@Nullable FlipperObjectHelper flipperObjectHelper,
|
|
||||||
DebugMemoryManager memoryManager,
|
|
||||||
FlipperPerfLogger perfLogger,
|
|
||||||
@Nullable FrescoFlipperDebugPrefHelper debugPrefHelper,
|
|
||||||
@Nullable CloseableReferenceLeakTracker closeableReferenceLeakTracker) {
|
|
||||||
mFlipperImageTracker =
|
|
||||||
imageTracker instanceof FlipperImageTracker
|
|
||||||
? (FlipperImageTracker) imageTracker
|
|
||||||
: new FlipperImageTracker();
|
|
||||||
mPlatformBitmapFactory = bitmapFactory;
|
|
||||||
mSonarObjectHelper = flipperObjectHelper;
|
|
||||||
mMemoryManager = memoryManager;
|
|
||||||
mPerfLogger = perfLogger;
|
|
||||||
mDebugPrefHelper = debugPrefHelper;
|
|
||||||
|
|
||||||
if (closeableReferenceLeakTracker != null) {
|
|
||||||
closeableReferenceLeakTracker.setListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public FrescoFlipperPlugin() {
|
|
||||||
this(
|
|
||||||
new FlipperImageTracker(),
|
|
||||||
Fresco.getImagePipelineFactory().getPlatformBitmapFactory(),
|
|
||||||
null,
|
|
||||||
new NoOpDebugMemoryManager(),
|
|
||||||
new NoOpFlipperPerfLogger(),
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FlipperImageTracker getFlipperImageTracker() {
|
|
||||||
return mFlipperImageTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "Fresco";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnect(FlipperConnection connection) {
|
|
||||||
super.onConnect(connection);
|
|
||||||
connection.receive(
|
|
||||||
"getAllImageEventsInfo",
|
|
||||||
new FlipperReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
|
|
||||||
if (!ensureFrescoInitialized()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlipperArray.Builder arrayBuilder = new FlipperArray.Builder();
|
|
||||||
for (FlipperObject obj : mEvents) {
|
|
||||||
arrayBuilder.put(obj);
|
|
||||||
}
|
|
||||||
mEvents.clear();
|
|
||||||
|
|
||||||
FlipperObject object =
|
|
||||||
new FlipperObject.Builder().put("events", arrayBuilder.build()).build();
|
|
||||||
responder.success(object);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.receive(
|
|
||||||
"listImages",
|
|
||||||
new FlipperReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
|
|
||||||
if (!ensureFrescoInitialized()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mPerfLogger.startMarker("Sonar.Fresco.listImages");
|
|
||||||
final boolean showDiskImages = params.getBoolean("showDiskImages");
|
|
||||||
final ImagePipelineFactory imagePipelineFactory = Fresco.getImagePipelineFactory();
|
|
||||||
|
|
||||||
final CountingMemoryCacheInspector.DumpInfo bitmapMemoryCache =
|
|
||||||
new CountingMemoryCacheInspector<>(
|
|
||||||
imagePipelineFactory.getBitmapCountingMemoryCache())
|
|
||||||
.dumpCacheContent();
|
|
||||||
final CountingMemoryCacheInspector.DumpInfo encodedMemoryCache =
|
|
||||||
new CountingMemoryCacheInspector<>(
|
|
||||||
imagePipelineFactory.getEncodedCountingMemoryCache())
|
|
||||||
.dumpCacheContent();
|
|
||||||
|
|
||||||
try {
|
|
||||||
responder.success(
|
|
||||||
getImageList(bitmapMemoryCache, encodedMemoryCache, showDiskImages));
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.listImages");
|
|
||||||
} finally {
|
|
||||||
bitmapMemoryCache.release();
|
|
||||||
encodedMemoryCache.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.receive(
|
|
||||||
"getImage",
|
|
||||||
new FlipperReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(FlipperObject params, final FlipperResponder responder)
|
|
||||||
throws Exception {
|
|
||||||
if (!ensureFrescoInitialized()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mPerfLogger.startMarker("Sonar.Fresco.getImage");
|
|
||||||
final String imageId = params.getString("imageId");
|
|
||||||
final CacheKey cacheKey = mFlipperImageTracker.getCacheKey(imageId);
|
|
||||||
if (cacheKey == null) {
|
|
||||||
respondError(responder, "ImageId " + imageId + " was evicted from cache");
|
|
||||||
mPerfLogger.cancelMarker("Sonar.Fresco.getImage");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final ImagePipelineFactory imagePipelineFactory = Fresco.getImagePipelineFactory();
|
|
||||||
|
|
||||||
// try to load from bitmap cache
|
|
||||||
@Nullable
|
|
||||||
CloseableImage closeableImage =
|
|
||||||
imagePipelineFactory.getBitmapCountingMemoryCache().inspect(cacheKey);
|
|
||||||
if (closeableImage instanceof CloseableBitmap) {
|
|
||||||
@Nullable Bitmap bitmap = ((CloseableBitmap) closeableImage).getUnderlyingBitmap();
|
|
||||||
if (bitmap != null) {
|
|
||||||
loadFromBitmapCache(bitmap, imageId, cacheKey, responder);
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.getImage");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to load from encoded cache
|
|
||||||
PooledByteBuffer encoded =
|
|
||||||
imagePipelineFactory.getEncodedCountingMemoryCache().inspect(cacheKey);
|
|
||||||
if (encoded != null) {
|
|
||||||
loadFromEncodedCache(encoded, imageId, cacheKey, responder);
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.getImage");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to load from disk
|
|
||||||
loadFromDisk(imageId, cacheKey, responder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFromBitmapCache(
|
|
||||||
final Bitmap bitmap,
|
|
||||||
final String imageId,
|
|
||||||
final CacheKey cacheKey,
|
|
||||||
final FlipperResponder responder) {
|
|
||||||
String encodedBitmap = bitmapToBase64Preview(bitmap, mPlatformBitmapFactory);
|
|
||||||
responder.success(
|
|
||||||
getImageData(
|
|
||||||
imageId,
|
|
||||||
mFlipperImageTracker.getUriString(cacheKey),
|
|
||||||
bitmap.getWidth(),
|
|
||||||
bitmap.getHeight(),
|
|
||||||
BitmapUtil.getSizeInBytes(bitmap),
|
|
||||||
encodedBitmap));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFromEncodedCache(
|
|
||||||
final PooledByteBuffer encoded,
|
|
||||||
final String imageId,
|
|
||||||
final CacheKey cacheKey,
|
|
||||||
final FlipperResponder responder)
|
|
||||||
throws Exception {
|
|
||||||
byte[] encodedArray = ByteStreams.toByteArray(new PooledByteBufferInputStream(encoded));
|
|
||||||
Pair<Integer, Integer> dimensions = BitmapUtil.decodeDimensions(encodedArray);
|
|
||||||
if (dimensions == null) {
|
|
||||||
respondError(responder, "can not get dimensions withId=" + imageId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
responder.success(
|
|
||||||
getImageData(
|
|
||||||
imageId,
|
|
||||||
mFlipperImageTracker.getUriString(cacheKey),
|
|
||||||
dimensions.first,
|
|
||||||
dimensions.second,
|
|
||||||
encodedArray.length,
|
|
||||||
dataFromEncodedArray(encodedArray)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadFromDisk(
|
|
||||||
final String imageId, final CacheKey cacheKey, final FlipperResponder responder) {
|
|
||||||
Task<EncodedImage> t =
|
|
||||||
Fresco.getImagePipelineFactory()
|
|
||||||
.getMainBufferedDiskCache()
|
|
||||||
.get(cacheKey, new AtomicBoolean(false));
|
|
||||||
|
|
||||||
t.continueWith(
|
|
||||||
new Continuation<EncodedImage, Void>() {
|
|
||||||
public Void then(Task<EncodedImage> task) throws Exception {
|
|
||||||
if (task.isCancelled() || task.isFaulted()) {
|
|
||||||
respondError(responder, "no bitmap withId=" + imageId);
|
|
||||||
mPerfLogger.cancelMarker("Sonar.Fresco.getImage");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Preconditions.checkNotNull(task);
|
|
||||||
final EncodedImage image = task.getResult();
|
|
||||||
try {
|
|
||||||
InputStream stream = Preconditions.checkNotNull(image.getInputStream());
|
|
||||||
byte[] encodedArray = ByteStreams.toByteArray(stream);
|
|
||||||
|
|
||||||
responder.success(
|
|
||||||
getImageData(
|
|
||||||
imageId,
|
|
||||||
Preconditions.checkNotNull(
|
|
||||||
mFlipperImageTracker.getLocalPath(cacheKey)),
|
|
||||||
image.getWidth(),
|
|
||||||
image.getHeight(),
|
|
||||||
encodedArray.length,
|
|
||||||
dataFromEncodedArray(encodedArray)));
|
|
||||||
} finally {
|
|
||||||
EncodedImage.closeSafely(image);
|
|
||||||
}
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.getImage");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.receive(
|
|
||||||
"clear",
|
|
||||||
new FlipperReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(FlipperObject params, FlipperResponder responder) {
|
|
||||||
if (!ensureFrescoInitialized()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mPerfLogger.startMarker("Sonar.Fresco.clear");
|
|
||||||
final String type = params.getString("type");
|
|
||||||
switch (type) {
|
|
||||||
case "memory":
|
|
||||||
final ImagePipelineFactory imagePipelineFactory = Fresco.getImagePipelineFactory();
|
|
||||||
imagePipelineFactory.getBitmapMemoryCache().removeAll(ALWAYS_TRUE_PREDICATE);
|
|
||||||
break;
|
|
||||||
case "disk":
|
|
||||||
Fresco.getImagePipeline().clearDiskCaches();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.clear");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.receive(
|
|
||||||
"trimMemory",
|
|
||||||
new FlipperReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
|
|
||||||
if (!ensureFrescoInitialized()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mMemoryManager != null) {
|
|
||||||
mMemoryManager.trimMemory(
|
|
||||||
DebugMemoryManager.ON_SYSTEM_LOW_MEMORY_WHILE_APP_IN_FOREGROUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.receive(
|
|
||||||
"enableDebugOverlay",
|
|
||||||
new FlipperReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
|
|
||||||
if (!ensureFrescoInitialized()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean enabled = params.getBoolean("enabled");
|
|
||||||
if (mDebugPrefHelper != null) {
|
|
||||||
mDebugPrefHelper.setDebugOverlayEnabled(enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (mDebugPrefHelper != null) {
|
|
||||||
mDebugPrefHelper.setDebugOverlayEnabledListener(
|
|
||||||
new FrescoFlipperDebugPrefHelper.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onEnabledStatusChanged(boolean enabled) {
|
|
||||||
sendDebugOverlayEnabledEvent(enabled);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
sendDebugOverlayEnabledEvent(mDebugPrefHelper.isDebugOverlayEnabled());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String dataFromEncodedArray(byte[] encodedArray) {
|
|
||||||
return "data:image/jpeg;base64," + Base64.encodeToString(encodedArray, Base64.DEFAULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlipperObject getImageList(
|
|
||||||
final CountingMemoryCacheInspector.DumpInfo bitmapMemoryCache,
|
|
||||||
final CountingMemoryCacheInspector.DumpInfo encodedMemoryCache,
|
|
||||||
final boolean showDiskImages)
|
|
||||||
throws IOException {
|
|
||||||
FlipperArray.Builder levelsBuilder =
|
|
||||||
new FlipperArray.Builder()
|
|
||||||
// bitmap
|
|
||||||
.put(getUsedStats("On screen bitmaps", bitmapMemoryCache))
|
|
||||||
.put(getCachedStats("Bitmap memory cache", bitmapMemoryCache))
|
|
||||||
// encoded
|
|
||||||
.put(getUsedStats("Used encoded images", encodedMemoryCache))
|
|
||||||
.put(getCachedStats("Cached encoded images", encodedMemoryCache));
|
|
||||||
if (showDiskImages) {
|
|
||||||
levelsBuilder.put(
|
|
||||||
getDiskStats(
|
|
||||||
"Disk images",
|
|
||||||
Fresco.getImagePipelineFactory().getMainFileCache().getDumpInfo().entries));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FlipperObject.Builder().put("levels", levelsBuilder.build()).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlipperObject getUsedStats(
|
|
||||||
final String cacheType, final CountingMemoryCacheInspector.DumpInfo memoryCache) {
|
|
||||||
return new FlipperObject.Builder()
|
|
||||||
.put("cacheType", cacheType)
|
|
||||||
.put("sizeBytes", memoryCache.size - memoryCache.lruSize)
|
|
||||||
.put("imageIds", buildImageIdList(memoryCache.sharedEntries))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlipperObject getCachedStats(
|
|
||||||
final String cacheType, final CountingMemoryCacheInspector.DumpInfo memoryCache) {
|
|
||||||
return new FlipperObject.Builder()
|
|
||||||
.put("cacheType", cacheType)
|
|
||||||
.put("clearKey", "memory")
|
|
||||||
.put("sizeBytes", memoryCache.size)
|
|
||||||
.put("maxSizeBytes", memoryCache.maxSize)
|
|
||||||
.put("imageIds", buildImageIdList(memoryCache.lruEntries))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlipperObject getDiskStats(
|
|
||||||
final String cacheType, List<DiskStorage.DiskDumpInfoEntry> diskEntries) {
|
|
||||||
return new FlipperObject.Builder()
|
|
||||||
.put("cacheType", cacheType)
|
|
||||||
.put("clearKey", "disk")
|
|
||||||
.put("sizeBytes", Fresco.getImagePipelineFactory().getMainFileCache().getSize())
|
|
||||||
.put("imageIds", buildImageIdListDisk(diskEntries))
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FlipperObject getImageData(
|
|
||||||
String imageID, String uriString, int width, int height, int sizeBytes, String data) {
|
|
||||||
return new FlipperObject.Builder()
|
|
||||||
.put("imageId", imageID)
|
|
||||||
.put("uri", uriString)
|
|
||||||
.put("width", width)
|
|
||||||
.put("height", height)
|
|
||||||
.put("sizeBytes", sizeBytes)
|
|
||||||
.put("data", data)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean ensureFrescoInitialized() {
|
|
||||||
mPerfLogger.startMarker("Sonar.Fresco.ensureFrescoInitialized");
|
|
||||||
try {
|
|
||||||
Fresco.getImagePipelineFactory();
|
|
||||||
return true;
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.ensureFrescoInitialized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlipperArray buildImageIdList(List<DumpInfoEntry<CacheKey, CloseableImage>> images) {
|
|
||||||
FlipperArray.Builder builder = new FlipperArray.Builder();
|
|
||||||
for (DumpInfoEntry<CacheKey, CloseableImage> entry : images) {
|
|
||||||
final FlipperImageTracker.ImageDebugData imageDebugData =
|
|
||||||
mFlipperImageTracker.getImageDebugData(entry.key);
|
|
||||||
|
|
||||||
if (imageDebugData == null) {
|
|
||||||
builder.put(mFlipperImageTracker.trackImage(entry.key).getUniqueId());
|
|
||||||
} else {
|
|
||||||
builder.put(imageDebugData.getUniqueId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlipperArray buildImageIdListDisk(List<DiskStorage.DiskDumpInfoEntry> diskEntries) {
|
|
||||||
FlipperArray.Builder builder = new FlipperArray.Builder();
|
|
||||||
for (DiskStorage.DiskDumpInfoEntry entry : diskEntries) {
|
|
||||||
final CacheKey entryCacheKey = new SimpleCacheKey(entry.id, true);
|
|
||||||
final FlipperImageTracker.ImageDebugData imageDebugData =
|
|
||||||
mFlipperImageTracker.getImageDebugData(entryCacheKey);
|
|
||||||
|
|
||||||
if (imageDebugData == null) {
|
|
||||||
builder.put(mFlipperImageTracker.trackImage(entry.path, entryCacheKey).getUniqueId());
|
|
||||||
} else {
|
|
||||||
builder.put(imageDebugData.getUniqueId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String bitmapToBase64Preview(Bitmap bitmap, PlatformBitmapFactory bitmapFactory) {
|
|
||||||
if (bitmap.getWidth() < BITMAP_SCALING_THRESHOLD_WIDTH
|
|
||||||
&& bitmap.getHeight() < BITMAP_SCALING_THRESHOLD_HEIGHT) {
|
|
||||||
return bitmapToBase64WithoutScaling(bitmap);
|
|
||||||
}
|
|
||||||
mPerfLogger.startMarker("Sonar.Fresco.bitmap2base64-resize");
|
|
||||||
|
|
||||||
// TODO (t19034797): properly load images
|
|
||||||
CloseableReference<Bitmap> scaledBitmapReference = null;
|
|
||||||
try {
|
|
||||||
float previewAspectRatio = BITMAP_PREVIEW_WIDTH / BITMAP_PREVIEW_HEIGHT;
|
|
||||||
float imageAspectRatio = bitmap.getWidth() / bitmap.getHeight();
|
|
||||||
|
|
||||||
int scaledWidth;
|
|
||||||
int scaledHeight;
|
|
||||||
if (previewAspectRatio > imageAspectRatio) {
|
|
||||||
scaledWidth = bitmap.getWidth() * BITMAP_PREVIEW_HEIGHT / bitmap.getHeight();
|
|
||||||
scaledHeight = BITMAP_PREVIEW_HEIGHT;
|
|
||||||
} else {
|
|
||||||
scaledWidth = BITMAP_PREVIEW_WIDTH;
|
|
||||||
scaledHeight = bitmap.getHeight() * BITMAP_PREVIEW_WIDTH / bitmap.getWidth();
|
|
||||||
}
|
|
||||||
scaledBitmapReference =
|
|
||||||
bitmapFactory.createScaledBitmap(bitmap, scaledWidth, scaledHeight, false);
|
|
||||||
return bitmapToBase64WithoutScaling(scaledBitmapReference.get());
|
|
||||||
} finally {
|
|
||||||
CloseableReference.closeSafely(scaledBitmapReference);
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.bitmap2base64-resize");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String bitmapToBase64WithoutScaling(Bitmap bitmap) {
|
|
||||||
mPerfLogger.startMarker("Sonar.Fresco.bitmap2base64-orig");
|
|
||||||
ByteArrayOutputStream byteArrayOutputStream = null;
|
|
||||||
try {
|
|
||||||
byteArrayOutputStream = new ByteArrayOutputStream();
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
|
|
||||||
|
|
||||||
return "data:image/png;base64,"
|
|
||||||
+ Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT);
|
|
||||||
} finally {
|
|
||||||
if (byteArrayOutputStream != null) {
|
|
||||||
try {
|
|
||||||
byteArrayOutputStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mPerfLogger.endMarker("Sonar.Fresco.bitmap2base64-orig");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onImageLoadStatusUpdated(
|
|
||||||
ImagePerfData imagePerfData, @ImageLoadStatus int imageLoadStatus) {
|
|
||||||
if (imageLoadStatus != ImageLoadStatus.SUCCESS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String requestId = imagePerfData.getRequestId();
|
|
||||||
if (requestId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlipperImageTracker.ImageDebugData data =
|
|
||||||
mFlipperImageTracker.getDebugDataForRequestId(requestId);
|
|
||||||
if (data == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FlipperArray.Builder imageIdsBuilder = new FlipperArray.Builder();
|
|
||||||
Set<CacheKey> cks = data.getCacheKeys();
|
|
||||||
if (cks != null) {
|
|
||||||
for (CacheKey ck : cks) {
|
|
||||||
FlipperImageTracker.ImageDebugData d = mFlipperImageTracker.getImageDebugData(ck);
|
|
||||||
if (d != null) {
|
|
||||||
imageIdsBuilder.put(d.getUniqueId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
imageIdsBuilder.put(data.getUniqueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
FlipperArray attribution;
|
|
||||||
Object callerContext = imagePerfData.getCallerContext();
|
|
||||||
if (callerContext == null) {
|
|
||||||
attribution = new FlipperArray.Builder().put("unknown").build();
|
|
||||||
} else if (mSonarObjectHelper == null) {
|
|
||||||
attribution = new FlipperArray.Builder().put(callerContext.toString()).build();
|
|
||||||
} else {
|
|
||||||
attribution = mSonarObjectHelper.fromCallerContext(callerContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
FlipperObject.Builder response =
|
|
||||||
new FlipperObject.Builder()
|
|
||||||
.put("imageIds", imageIdsBuilder.build())
|
|
||||||
.put("attribution", attribution)
|
|
||||||
.put("startTime", imagePerfData.getControllerSubmitTimeMs())
|
|
||||||
.put("endTime", imagePerfData.getControllerFinalImageSetTimeMs())
|
|
||||||
.put("source", ImageOriginUtils.toString(imagePerfData.getImageOrigin()));
|
|
||||||
|
|
||||||
if (!imagePerfData.isPrefetch()) {
|
|
||||||
response.put(
|
|
||||||
"viewport",
|
|
||||||
new FlipperObject.Builder()
|
|
||||||
// TODO (t31947746): scan times
|
|
||||||
.put("width", imagePerfData.getOnScreenWidthPx())
|
|
||||||
.put("height", imagePerfData.getOnScreenHeightPx())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
FlipperObject responseObject = response.build();
|
|
||||||
mEvents.add(responseObject);
|
|
||||||
send(FRESCO_EVENT, responseObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onImageVisibilityUpdated(ImagePerfData imagePerfData, int visibilityState) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendDebugOverlayEnabledEvent(final boolean enabled) {
|
|
||||||
final FlipperObject.Builder builder = new FlipperObject.Builder().put("enabled", enabled);
|
|
||||||
send(FRESCO_DEBUGOVERLAY_EVENT, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void respondError(FlipperResponder responder, String errorReason) {
|
|
||||||
responder.error(new FlipperObject.Builder().put("reason", errorReason).build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCloseableReferenceLeak(
|
|
||||||
SharedReference<Object> reference, @Nullable Throwable stacktrace) {
|
|
||||||
Object object = reference.get();
|
|
||||||
Preconditions.checkNotNull(object);
|
|
||||||
final FlipperObject.Builder builder =
|
|
||||||
new FlipperObject.Builder()
|
|
||||||
.put("identityHashCode", System.identityHashCode(reference))
|
|
||||||
.put("className", object.getClass().getName());
|
|
||||||
if (stacktrace != null) {
|
|
||||||
builder.put("stacktrace", getStackTraceString(stacktrace));
|
|
||||||
}
|
|
||||||
send(FRESCO_CLOSEABLE_REFERENCE_LEAK_EVENT, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getStackTraceString(Throwable tr) {
|
|
||||||
StringWriter sw = new StringWriter();
|
|
||||||
PrintWriter pw = new PrintWriter(sw);
|
|
||||||
tr.printStackTrace(pw);
|
|
||||||
return sw.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.flipper.plugins.fresco;
|
|
||||||
|
|
||||||
import com.facebook.imagepipeline.debug.DebugImageTracker;
|
|
||||||
import com.facebook.imagepipeline.listener.BaseRequestListener;
|
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
|
||||||
|
|
||||||
/** Fresco image {@link RequestListener} that logs events for Sonar. */
|
|
||||||
public class FrescoFlipperRequestListener extends BaseRequestListener {
|
|
||||||
|
|
||||||
private final DebugImageTracker mDebugImageTracker;
|
|
||||||
|
|
||||||
public FrescoFlipperRequestListener(DebugImageTracker debugImageTracker) {
|
|
||||||
mDebugImageTracker = debugImageTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestStart(
|
|
||||||
ImageRequest request, Object callerContext, String requestId, boolean isPrefetch) {
|
|
||||||
mDebugImageTracker.trackImageRequest(request, requestId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.flipper.plugins.fresco.objecthelper;
|
|
||||||
|
|
||||||
import static com.facebook.flipper.plugins.inspector.InspectorValue.Type.Color;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import com.facebook.drawee.backends.pipeline.info.ImageOriginUtils;
|
|
||||||
import com.facebook.drawee.backends.pipeline.info.ImagePerfData;
|
|
||||||
import com.facebook.drawee.generic.RoundingParams;
|
|
||||||
import com.facebook.flipper.core.FlipperArray;
|
|
||||||
import com.facebook.flipper.core.FlipperObject;
|
|
||||||
import com.facebook.flipper.plugins.inspector.InspectorValue;
|
|
||||||
import com.facebook.imagepipeline.common.ImageDecodeOptions;
|
|
||||||
import com.facebook.imagepipeline.common.ResizeOptions;
|
|
||||||
import com.facebook.imagepipeline.common.RotationOptions;
|
|
||||||
import com.facebook.imagepipeline.debug.FlipperImageTracker;
|
|
||||||
import com.facebook.imagepipeline.image.ImageInfo;
|
|
||||||
import com.facebook.imagepipeline.image.QualityInfo;
|
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/** Serialization helper to create {@link FlipperObject}s. */
|
|
||||||
public abstract class FlipperObjectHelper {
|
|
||||||
|
|
||||||
public FlipperObject keyValuePair(String key, @Nullable String value) {
|
|
||||||
return new FlipperObject.Builder().put(key, value).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(@Nullable Map<String, String> stringMap) {
|
|
||||||
if (stringMap == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder optionsJson = new FlipperObject.Builder();
|
|
||||||
for (Map.Entry<String, String> entry : stringMap.entrySet()) {
|
|
||||||
optionsJson.put(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
return optionsJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(@Nullable ImageRequest imageRequest) {
|
|
||||||
if (imageRequest == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder optionsJson = new FlipperObject.Builder();
|
|
||||||
return addImageRequestProperties(optionsJson, imageRequest).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(
|
|
||||||
@Nullable FlipperImageTracker.ImageDebugData imageDebugData) {
|
|
||||||
if (imageDebugData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder optionsJson = new FlipperObject.Builder();
|
|
||||||
optionsJson.put("imageId", imageDebugData.getUniqueId());
|
|
||||||
optionsJson.put("imageRequest", toFlipperObject(imageDebugData.getImageRequest()));
|
|
||||||
optionsJson.put(
|
|
||||||
"requestId",
|
|
||||||
imageDebugData.getRequestIds() != null
|
|
||||||
? TextUtils.join(", ", imageDebugData.getRequestIds())
|
|
||||||
: "");
|
|
||||||
optionsJson.put("imagePerfData", toFlipperObject(imageDebugData.getImagePerfData()));
|
|
||||||
return optionsJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(@Nullable ImageDecodeOptions options) {
|
|
||||||
if (options == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder optionsJson = new FlipperObject.Builder();
|
|
||||||
optionsJson.put("minDecodeIntervalMs", options.minDecodeIntervalMs);
|
|
||||||
optionsJson.put("decodePreviewFrame", options.decodePreviewFrame);
|
|
||||||
optionsJson.put("useLastFrameForPreview", options.useLastFrameForPreview);
|
|
||||||
optionsJson.put("decodeAllFrames", options.decodeAllFrames);
|
|
||||||
optionsJson.put("forceStaticImage", options.forceStaticImage);
|
|
||||||
optionsJson.put("bitmapConfig", options.bitmapConfig.name());
|
|
||||||
optionsJson.put(
|
|
||||||
"customImageDecoder",
|
|
||||||
options.customImageDecoder == null ? "" : options.customImageDecoder.toString());
|
|
||||||
return optionsJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(@Nullable ResizeOptions resizeOptions) {
|
|
||||||
if (resizeOptions == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder optionsJson = new FlipperObject.Builder();
|
|
||||||
optionsJson.put("width", resizeOptions.width);
|
|
||||||
optionsJson.put("height", resizeOptions.height);
|
|
||||||
optionsJson.put("maxBitmapSize", resizeOptions.maxBitmapSize);
|
|
||||||
optionsJson.put("roundUpFraction", resizeOptions.roundUpFraction);
|
|
||||||
return optionsJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(@Nullable RotationOptions rotationOptions) {
|
|
||||||
if (rotationOptions == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder optionsJson = new FlipperObject.Builder();
|
|
||||||
optionsJson.put("rotationEnabled", rotationOptions.rotationEnabled());
|
|
||||||
optionsJson.put("canDeferUntilRendered", rotationOptions.canDeferUntilRendered());
|
|
||||||
optionsJson.put("useImageMetadata", rotationOptions.useImageMetadata());
|
|
||||||
if (!rotationOptions.useImageMetadata()) {
|
|
||||||
optionsJson.put("forcedAngle", rotationOptions.getForcedAngle());
|
|
||||||
}
|
|
||||||
return optionsJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(@Nullable RoundingParams roundingParams) {
|
|
||||||
if (roundingParams == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder optionsJson = new FlipperObject.Builder();
|
|
||||||
optionsJson.put("borderWidth", roundingParams.getBorderWidth());
|
|
||||||
optionsJson.put("cornersRadii", toSonarArray(roundingParams.getCornersRadii()));
|
|
||||||
optionsJson.put("padding", roundingParams.getPadding());
|
|
||||||
optionsJson.put("roundAsCircle", roundingParams.getRoundAsCircle());
|
|
||||||
optionsJson.put("roundingMethod", roundingParams.getRoundingMethod());
|
|
||||||
optionsJson.put(
|
|
||||||
"borderColor", InspectorValue.immutable(Color, roundingParams.getBorderColor()));
|
|
||||||
optionsJson.put(
|
|
||||||
"overlayColor", InspectorValue.immutable(Color, roundingParams.getOverlayColor()));
|
|
||||||
return optionsJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(@Nullable ImagePerfData imagePerfData) {
|
|
||||||
if (imagePerfData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder objectJson = new FlipperObject.Builder();
|
|
||||||
objectJson.put("requestId", imagePerfData.getRequestId());
|
|
||||||
objectJson.put("controllerSubmitTimeMs", imagePerfData.getControllerSubmitTimeMs());
|
|
||||||
objectJson.put("controllerFinalTimeMs", imagePerfData.getControllerFinalImageSetTimeMs());
|
|
||||||
objectJson.put("imageRequestStartTimeMs", imagePerfData.getImageRequestStartTimeMs());
|
|
||||||
objectJson.put("imageRequestEndTimeMs", imagePerfData.getImageRequestEndTimeMs());
|
|
||||||
objectJson.put("imageOrigin", ImageOriginUtils.toString(imagePerfData.getImageOrigin()));
|
|
||||||
objectJson.put("isPrefetch", imagePerfData.isPrefetch());
|
|
||||||
objectJson.put("callerContext", imagePerfData.getCallerContext());
|
|
||||||
objectJson.put("imageRequest", toFlipperObject(imagePerfData.getImageRequest()));
|
|
||||||
objectJson.put("imageInfo", toFlipperObject(imagePerfData.getImageInfo()));
|
|
||||||
return objectJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(ImageInfo imageInfo) {
|
|
||||||
if (imageInfo == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder objectJson = new FlipperObject.Builder();
|
|
||||||
objectJson.put("imageWidth", imageInfo.getWidth());
|
|
||||||
objectJson.put("imageHeight", imageInfo.getHeight());
|
|
||||||
objectJson.put("qualityInfo", toFlipperObject(imageInfo.getQualityInfo()));
|
|
||||||
return objectJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public FlipperObject toFlipperObject(QualityInfo qualityInfo) {
|
|
||||||
if (qualityInfo == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
FlipperObject.Builder objectJson = new FlipperObject.Builder();
|
|
||||||
objectJson.put("quality", qualityInfo.getQuality());
|
|
||||||
objectJson.put("isGoodEnoughQuality", qualityInfo.isOfGoodEnoughQuality());
|
|
||||||
objectJson.put("isFullQuality", qualityInfo.isOfFullQuality());
|
|
||||||
return objectJson.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public FlipperObject.Builder addImageRequestProperties(
|
|
||||||
FlipperObject.Builder builder, @Nullable ImageRequest request) {
|
|
||||||
if (request == null) {
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
builder
|
|
||||||
.put("sourceUri", request.getSourceUri())
|
|
||||||
.put("preferredWidth", request.getPreferredWidth())
|
|
||||||
.put("preferredHeight", request.getPreferredHeight())
|
|
||||||
.put("cacheChoice", request.getCacheChoice())
|
|
||||||
.put("diskCacheEnabled", request.isDiskCacheEnabled())
|
|
||||||
.put("localThumbnailPreviewsEnabled", request.getLocalThumbnailPreviewsEnabled())
|
|
||||||
.put("lowestPermittedRequestLevel", request.getLowestPermittedRequestLevel())
|
|
||||||
.put("priority", request.getPriority().name())
|
|
||||||
.put("progressiveRenderingEnabled", request.getProgressiveRenderingEnabled())
|
|
||||||
.put("postprocessor", String.valueOf(request.getPostprocessor()))
|
|
||||||
.put("requestListener", String.valueOf(request.getRequestListener()))
|
|
||||||
.put("imageDecodeOptions", toFlipperObject(request.getImageDecodeOptions()))
|
|
||||||
.put("bytesRange", request.getBytesRange())
|
|
||||||
.put("resizeOptions", toFlipperObject(request.getResizeOptions()))
|
|
||||||
.put("rotationOptions", toFlipperObject(request.getRotationOptions()));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FlipperArray toSonarArray(float[] floats) {
|
|
||||||
final FlipperArray.Builder builder = new FlipperArray.Builder();
|
|
||||||
for (float f : floats) {
|
|
||||||
builder.put(f);
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public abstract FlipperArray fromCallerContext(@Nullable Object callerContext);
|
|
||||||
}
|
|
||||||
@@ -6,8 +6,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.plugins.jetpackcompose'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
@@ -16,19 +18,18 @@ android {
|
|||||||
targetSdkVersion rootProject.targetSdkVersion
|
targetSdkVersion rootProject.targetSdkVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility rootProject.javaTargetVersion
|
||||||
|
sourceCompatibility rootProject.javaTargetVersion
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':android')
|
implementation project(':android')
|
||||||
implementation deps.fresco
|
implementation 'androidx.compose.ui:ui:1.4.3'
|
||||||
implementation deps.frescoFlipper
|
implementation 'androidx.compose.ui:ui-tooling:1.4.3'
|
||||||
compileOnly deps.jsr305
|
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0'
|
||||||
|
|
||||||
api deps.boltsTasks
|
|
||||||
|
|
||||||
// Exclude the actual stetho dep as we only want some of the fresco APIs here
|
|
||||||
implementation(deps.frescoStetho) {
|
|
||||||
exclude group: 'com.facebook.stetho'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
apply plugin: 'com.vanniktech.maven.publish'
|
apply plugin: 'com.vanniktech.maven.publish'
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
# file in the root directory of this source tree.
|
# file in the root directory of this source tree.
|
||||||
#
|
#
|
||||||
|
|
||||||
POM_NAME=Flipper Fresco Plugin
|
POM_NAME=Flipper Jetpack Compose UIDebugger Plugin
|
||||||
POM_DESCRIPTION=Images plugin for Flipper
|
POM_DESCRIPTION=Jetpack Compose Plugin for the Flipper UIDebugger
|
||||||
POM_ARTIFACT_ID=flipper-fresco-plugin
|
POM_ARTIFACT_ID=flipper-jetpack-compose-plugin
|
||||||
POM_PACKAGING=aar
|
POM_PACKAGING=aar
|
||||||
|
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.jetpackcompose
|
||||||
|
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import com.facebook.flipper.plugins.jetpackcompose.descriptors.*
|
||||||
|
import com.facebook.flipper.plugins.jetpackcompose.model.*
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
||||||
|
|
||||||
|
const val JetpackComposeTag = "JetpackCompose"
|
||||||
|
|
||||||
|
object UIDebuggerComposeSupport {
|
||||||
|
|
||||||
|
fun enable(context: UIDContext) {
|
||||||
|
addDescriptors(context.descriptorRegister)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDescriptors(register: DescriptorRegister) {
|
||||||
|
register.register(ComposeView::class.java, ComposeViewDescriptor)
|
||||||
|
register.register(ComposeNode::class.java, ComposeNodeDescriptor)
|
||||||
|
register.register(ComposeInnerViewNode::class.java, ComposeInnerViewDescriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.jetpackcompose.descriptors
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeInnerViewNode
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.ViewDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.ViewGroupDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||||
|
import java.lang.System
|
||||||
|
|
||||||
|
object ComposeInnerViewDescriptor : NodeDescriptor<ComposeInnerViewNode> {
|
||||||
|
|
||||||
|
override fun getId(node: ComposeInnerViewNode): Id = System.identityHashCode(node.view)
|
||||||
|
|
||||||
|
override fun getBounds(node: ComposeInnerViewNode): Bounds {
|
||||||
|
return node.bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getName(node: ComposeInnerViewNode): String {
|
||||||
|
if (node.view is ViewGroup) {
|
||||||
|
return ViewGroupDescriptor.getName(node.view)
|
||||||
|
}
|
||||||
|
return ViewDescriptor.getName(node.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getQualifiedName(node: ComposeInnerViewNode): String {
|
||||||
|
if (node.view is ViewGroup) {
|
||||||
|
return ViewGroupDescriptor.getQualifiedName(node.view)
|
||||||
|
}
|
||||||
|
return ViewDescriptor.getQualifiedName(node.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChildren(node: ComposeInnerViewNode): List<Any> {
|
||||||
|
if (node.view is ViewGroup) {
|
||||||
|
return ViewGroupDescriptor.getChildren(node.view)
|
||||||
|
}
|
||||||
|
return ViewDescriptor.getChildren(node.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSnapshot(node: ComposeInnerViewNode, bitmap: Bitmap?): Bitmap? {
|
||||||
|
if (node.view is ViewGroup) {
|
||||||
|
return ViewGroupDescriptor.getSnapshot(node.view, bitmap)
|
||||||
|
}
|
||||||
|
return ViewDescriptor.getSnapshot(node.view, bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActiveChild(node: ComposeInnerViewNode): Any? {
|
||||||
|
if (node.view is ViewGroup) {
|
||||||
|
return ViewGroupDescriptor.getActiveChild(node.view)
|
||||||
|
}
|
||||||
|
return ViewDescriptor.getActiveChild(node.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAttributes(
|
||||||
|
node: ComposeInnerViewNode
|
||||||
|
): MaybeDeferred<Map<MetadataId, InspectableObject>> {
|
||||||
|
if (node.view is ViewGroup) {
|
||||||
|
return ViewGroupDescriptor.getAttributes(node.view)
|
||||||
|
}
|
||||||
|
return ViewDescriptor.getAttributes(node.view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTags(node: ComposeInnerViewNode): Set<String> {
|
||||||
|
if (node.view is ViewGroup) {
|
||||||
|
return ViewGroupDescriptor.getTags(node.view)
|
||||||
|
}
|
||||||
|
return ViewDescriptor.getTags(node.view)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.jetpackcompose.descriptors
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.BaseTags
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Coordinate
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.util.Immediate
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||||
|
|
||||||
|
object ComposeNodeDescriptor : NodeDescriptor<ComposeNode> {
|
||||||
|
|
||||||
|
private const val NAMESPACE = "ComposeNode"
|
||||||
|
|
||||||
|
private var SectionId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
|
||||||
|
private val IdAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "id")
|
||||||
|
private val KeyAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "key")
|
||||||
|
private val NameAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "name")
|
||||||
|
private val FilenameAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "filename")
|
||||||
|
private val PackageHashAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "packageHash")
|
||||||
|
private val LineNumberAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "lineNumber")
|
||||||
|
private val OffsetAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "offset")
|
||||||
|
private val LengthAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_IDENTITY, NAMESPACE, "length")
|
||||||
|
private val BoxAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "box")
|
||||||
|
private val BoundsAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "bounds")
|
||||||
|
private val Bounds0AttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x0, y0)")
|
||||||
|
private val Bounds1AttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x1, y1)")
|
||||||
|
private val Bounds2AttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x2, y2)")
|
||||||
|
private val Bounds3AttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "(x3, y3)")
|
||||||
|
private val ViewIdAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "viewId")
|
||||||
|
private val MergedSemanticsAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "mergedSemantics")
|
||||||
|
private val UnmergedSemanticsAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "unmergedSemantics")
|
||||||
|
|
||||||
|
override fun getName(node: ComposeNode): String = node.inspectorNode.name
|
||||||
|
|
||||||
|
override fun getChildren(node: ComposeNode): List<Any> {
|
||||||
|
return node.children
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAttributes(node: ComposeNode): MaybeDeferred<Map<MetadataId, InspectableObject>> {
|
||||||
|
|
||||||
|
val builder = mutableMapOf<MetadataId, InspectableObject>()
|
||||||
|
val props = mutableMapOf<Int, Inspectable>()
|
||||||
|
|
||||||
|
props[IdAttributeId] = InspectableValue.Number(node.inspectorNode.id)
|
||||||
|
props[ViewIdAttributeId] = InspectableValue.Number(node.inspectorNode.viewId)
|
||||||
|
props[KeyAttributeId] = InspectableValue.Number(node.inspectorNode.key)
|
||||||
|
props[NameAttributeId] = InspectableValue.Text(node.inspectorNode.name)
|
||||||
|
props[FilenameAttributeId] = InspectableValue.Text(node.inspectorNode.fileName)
|
||||||
|
props[PackageHashAttributeId] = InspectableValue.Number(node.inspectorNode.packageHash)
|
||||||
|
props[LineNumberAttributeId] = InspectableValue.Number(node.inspectorNode.lineNumber)
|
||||||
|
props[OffsetAttributeId] = InspectableValue.Number(node.inspectorNode.offset)
|
||||||
|
props[LengthAttributeId] = InspectableValue.Number(node.inspectorNode.length)
|
||||||
|
|
||||||
|
props[BoxAttributeId] =
|
||||||
|
InspectableValue.Bounds(
|
||||||
|
Bounds(
|
||||||
|
node.inspectorNode.left,
|
||||||
|
node.inspectorNode.top,
|
||||||
|
node.inspectorNode.width,
|
||||||
|
node.inspectorNode.height))
|
||||||
|
|
||||||
|
node.inspectorNode.bounds?.let { bounds ->
|
||||||
|
val quadBounds = mutableMapOf<Int, Inspectable>()
|
||||||
|
quadBounds[Bounds0AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x0, bounds.y0))
|
||||||
|
quadBounds[Bounds1AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x1, bounds.y1))
|
||||||
|
quadBounds[Bounds2AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x2, bounds.y2))
|
||||||
|
quadBounds[Bounds3AttributeId] = InspectableValue.Coordinate(Coordinate(bounds.x3, bounds.y3))
|
||||||
|
props[BoundsAttributeId] = InspectableObject(quadBounds.toMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
val mergedSemantics = mutableMapOf<Int, Inspectable>()
|
||||||
|
node.inspectorNode.mergedSemantics.forEach {
|
||||||
|
val keyAttributeId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE, node.inspectorNode.name, it.name)
|
||||||
|
mergedSemantics[keyAttributeId] = InspectableValue.Text(it.value.toString())
|
||||||
|
}
|
||||||
|
props[MergedSemanticsAttributeId] = InspectableObject(mergedSemantics.toMap())
|
||||||
|
|
||||||
|
val unmergedSemantics = mutableMapOf<Int, Inspectable>()
|
||||||
|
node.inspectorNode.unmergedSemantics.forEach {
|
||||||
|
val keyAttributeId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE, node.inspectorNode.name, it.name)
|
||||||
|
mergedSemantics[keyAttributeId] = InspectableValue.Text(it.value.toString())
|
||||||
|
}
|
||||||
|
props[UnmergedSemanticsAttributeId] = InspectableObject(unmergedSemantics.toMap())
|
||||||
|
|
||||||
|
builder[SectionId] = InspectableObject(props.toMap())
|
||||||
|
|
||||||
|
return Immediate(builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBounds(node: ComposeNode): Bounds {
|
||||||
|
return node.bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getQualifiedName(node: ComposeNode): String = node.inspectorNode.name
|
||||||
|
|
||||||
|
override fun getSnapshot(node: ComposeNode, bitmap: Bitmap?): Bitmap? = null
|
||||||
|
|
||||||
|
override fun getActiveChild(node: ComposeNode): Any? = null
|
||||||
|
|
||||||
|
override fun getTags(node: ComposeNode): Set<String> = setOf(BaseTags.Android, "Compose")
|
||||||
|
|
||||||
|
override fun getId(node: ComposeNode): Id = node.inspectorNode.id.toInt()
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.jetpackcompose.descriptors
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import com.facebook.flipper.plugins.jetpackcompose.model.ComposeNode
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
|
||||||
|
import facebook.internal.androidx.compose.ui.inspection.inspector.InspectorNode
|
||||||
|
import facebook.internal.androidx.compose.ui.inspection.inspector.LayoutInspectorTree
|
||||||
|
|
||||||
|
object ComposeViewDescriptor : ChainedDescriptor<ComposeView>() {
|
||||||
|
override fun onGetName(node: ComposeView): String = node.javaClass.simpleName
|
||||||
|
|
||||||
|
private fun transform(view: View, nodes: List<InspectorNode>): List<ComposeNode> {
|
||||||
|
val positionOnScreen = IntArray(2)
|
||||||
|
view.getLocationOnScreen(positionOnScreen)
|
||||||
|
|
||||||
|
val xOffset = positionOnScreen[0]
|
||||||
|
val yOffset = positionOnScreen[1]
|
||||||
|
|
||||||
|
return nodes.map { node -> ComposeNode(view, node, xOffset, yOffset) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetChildren(node: ComposeView): List<Any> {
|
||||||
|
val children = mutableListOf<Any>()
|
||||||
|
val count = node.childCount - 1
|
||||||
|
for (i in 0..count) {
|
||||||
|
val child: View = node.getChildAt(i)
|
||||||
|
children.add(child)
|
||||||
|
|
||||||
|
if (child.javaClass.simpleName.contains("AndroidComposeView") &&
|
||||||
|
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)) {
|
||||||
|
val layoutInspector = LayoutInspectorTree()
|
||||||
|
layoutInspector.hideSystemNodes = false
|
||||||
|
return transform(child, layoutInspector.convert(child))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.jetpackcompose.model
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||||
|
|
||||||
|
class ComposeInnerViewNode(
|
||||||
|
val view: View,
|
||||||
|
) {
|
||||||
|
val bounds: Bounds = Bounds(0, 0, view.width, view.height)
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.jetpackcompose.model
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.*
|
||||||
|
import facebook.internal.androidx.compose.ui.inspection.inspector.InspectorNode
|
||||||
|
|
||||||
|
class ComposeNode(
|
||||||
|
private val parentComposeView: View,
|
||||||
|
val inspectorNode: InspectorNode,
|
||||||
|
xOffset: Int,
|
||||||
|
yOffset: Int
|
||||||
|
) {
|
||||||
|
val bounds: Bounds =
|
||||||
|
Bounds(
|
||||||
|
inspectorNode.left - xOffset,
|
||||||
|
inspectorNode.top - yOffset,
|
||||||
|
inspectorNode.width,
|
||||||
|
inspectorNode.height)
|
||||||
|
|
||||||
|
val children: List<Any> = collectChildren()
|
||||||
|
|
||||||
|
private fun collectChildren(): List<Any> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val viewId = inspectorNode.viewId
|
||||||
|
if (viewId != 0L) {
|
||||||
|
val view = parentComposeView.findViewByDrawingId(viewId)
|
||||||
|
if (view != null) {
|
||||||
|
return listOf(ComposeInnerViewNode(view))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inspectorNode.children.map { child ->
|
||||||
|
ComposeNode(parentComposeView, child, inspectorNode.left, inspectorNode.top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
private fun View.findViewByDrawingId(drawingId: Long): View? {
|
||||||
|
if (this.uniqueDrawingId == drawingId) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
if (this is ViewGroup) {
|
||||||
|
for (i in 0 until this.childCount) {
|
||||||
|
val child = this.getChildAt(i)
|
||||||
|
val foundView = child.findViewByDrawingId(drawingId)
|
||||||
|
if (foundView != null) {
|
||||||
|
return foundView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
This is a check-in of https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-main/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection
|
||||||
|
|
||||||
|
The classes are currently not exported but we rely on them for debug information.
|
||||||
|
|
||||||
|
Tree: 59746be8ea17d5753471bd285b3fbc9cf8ea7c31
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.inspector
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter for casting a parameter represented by its primitive value to its inline class type.
|
||||||
|
*
|
||||||
|
* For example: an androidx.compose.ui.graphics.Color instance is often represented by a long
|
||||||
|
*/
|
||||||
|
internal class InlineClassConverter {
|
||||||
|
// Map from inline type name to inline class and conversion lambda
|
||||||
|
private val typeMap = mutableMapOf<String, (Any) -> Any>()
|
||||||
|
// Return value used in functions
|
||||||
|
private val notInlineType: (Any) -> Any = { it }
|
||||||
|
|
||||||
|
/** Clear any cached data. */
|
||||||
|
fun clear() {
|
||||||
|
typeMap.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cast the specified [value] to a value of type [inlineClassName] if possible.
|
||||||
|
*
|
||||||
|
* @param inlineClassName the fully qualified name of the inline class.
|
||||||
|
* @param value the value to convert to an instance of [inlineClassName].
|
||||||
|
*/
|
||||||
|
fun castParameterValue(inlineClassName: String?, value: Any?): Any? =
|
||||||
|
if (value != null && inlineClassName != null) typeMapperFor(inlineClassName)(value) else value
|
||||||
|
|
||||||
|
private fun typeMapperFor(typeName: String): (Any) -> (Any) =
|
||||||
|
typeMap.getOrPut(typeName) { loadTypeMapper(typeName.replace('.', '/')) }
|
||||||
|
|
||||||
|
private fun loadTypeMapper(className: String): (Any) -> Any {
|
||||||
|
val javaClass = loadClassOrNull(className) ?: return notInlineType
|
||||||
|
val create = javaClass.declaredConstructors.singleOrNull() ?: return notInlineType
|
||||||
|
create.isAccessible = true
|
||||||
|
return { value -> create.newInstance(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadClassOrNull(className: String): Class<*>? =
|
||||||
|
try {
|
||||||
|
javaClass.classLoader!!.loadClass(className)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.inspector
|
||||||
|
|
||||||
|
import androidx.compose.ui.layout.LayoutInfo
|
||||||
|
import androidx.compose.ui.unit.IntRect
|
||||||
|
|
||||||
|
internal const val UNDEFINED_ID = 0L
|
||||||
|
|
||||||
|
internal val emptyBox = IntRect(0, 0, 0, 0)
|
||||||
|
internal val outsideBox = IntRect(Int.MAX_VALUE, Int.MIN_VALUE, Int.MAX_VALUE, Int.MIN_VALUE)
|
||||||
|
|
||||||
|
/** Node representing a Composable for the Layout Inspector. */
|
||||||
|
class InspectorNode
|
||||||
|
internal constructor(
|
||||||
|
/** The associated render node id or 0. */
|
||||||
|
val id: Long,
|
||||||
|
|
||||||
|
/** The associated key for tracking recomposition counts. */
|
||||||
|
val key: Int,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the associated anchor for tracking recomposition counts.
|
||||||
|
*
|
||||||
|
* An Anchor is a mechanism in the compose runtime that can identify a Group in the SlotTable
|
||||||
|
* that is invariant to SlotTable updates. See [androidx.compose.runtime.Anchor] for more
|
||||||
|
* information.
|
||||||
|
*/
|
||||||
|
val anchorId: Int,
|
||||||
|
|
||||||
|
/** The name of the Composable. */
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
/** The fileName where the Composable was called. */
|
||||||
|
val fileName: String,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A hash of the package name to help disambiguate duplicate [fileName] values.
|
||||||
|
*
|
||||||
|
* This hash is calculated by,
|
||||||
|
*
|
||||||
|
* `packageName.fold(0) { hash, current -> hash * 31 + current.toInt() }?.absoluteValue`
|
||||||
|
*
|
||||||
|
* where the package name is the dotted name of the package. This can be used to disambiguate
|
||||||
|
* which file is referenced by [fileName]. This number is -1 if there was no package hash
|
||||||
|
* information generated such as when the file does not contain a package declaration.
|
||||||
|
*/
|
||||||
|
val packageHash: Int,
|
||||||
|
|
||||||
|
/** The line number where the Composable was called. */
|
||||||
|
val lineNumber: Int,
|
||||||
|
|
||||||
|
/** The UTF-16 offset in the file where the Composable was called */
|
||||||
|
val offset: Int,
|
||||||
|
|
||||||
|
/** The number of UTF-16 code point comprise the Composable call */
|
||||||
|
val length: Int,
|
||||||
|
|
||||||
|
/** The bounding box of the Composable. */
|
||||||
|
internal val box: IntRect,
|
||||||
|
|
||||||
|
/** The 4 corners of the polygon after transformations of the original rectangle. */
|
||||||
|
val bounds: QuadBounds? = null,
|
||||||
|
|
||||||
|
/** True if the code for the Composable was inlined */
|
||||||
|
val inlined: Boolean = false,
|
||||||
|
|
||||||
|
/** The parameters of this Composable. */
|
||||||
|
val parameters: List<RawParameter>,
|
||||||
|
|
||||||
|
/** The id of a android View embedded under this node. */
|
||||||
|
val viewId: Long,
|
||||||
|
|
||||||
|
/** The merged semantics information of this Composable. */
|
||||||
|
val mergedSemantics: List<RawParameter>,
|
||||||
|
|
||||||
|
/** The un-merged semantics information of this Composable. */
|
||||||
|
val unmergedSemantics: List<RawParameter>,
|
||||||
|
|
||||||
|
/** The children nodes of this Composable. */
|
||||||
|
val children: List<InspectorNode>
|
||||||
|
) {
|
||||||
|
/** Left side of the Composable in pixels. */
|
||||||
|
val left: Int
|
||||||
|
get() = box.left
|
||||||
|
|
||||||
|
/** Top of the Composable in pixels. */
|
||||||
|
val top: Int
|
||||||
|
get() = box.top
|
||||||
|
|
||||||
|
/** Width of the Composable in pixels. */
|
||||||
|
val width: Int
|
||||||
|
get() = box.width
|
||||||
|
|
||||||
|
/** Width of the Composable in pixels. */
|
||||||
|
val height: Int
|
||||||
|
get() = box.height
|
||||||
|
|
||||||
|
fun parametersByKind(kind: ParameterKind): List<RawParameter> =
|
||||||
|
when (kind) {
|
||||||
|
ParameterKind.Normal -> parameters
|
||||||
|
ParameterKind.MergedSemantics -> mergedSemantics
|
||||||
|
ParameterKind.UnmergedSemantics -> unmergedSemantics
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class QuadBounds(
|
||||||
|
val x0: Int,
|
||||||
|
val y0: Int,
|
||||||
|
val x1: Int,
|
||||||
|
val y1: Int,
|
||||||
|
val x2: Int,
|
||||||
|
val y2: Int,
|
||||||
|
val x3: Int,
|
||||||
|
val y3: Int,
|
||||||
|
) {
|
||||||
|
val xMin: Int
|
||||||
|
get() = sequenceOf(x0, x1, x2, x3).minOrNull()!!
|
||||||
|
|
||||||
|
val xMax: Int
|
||||||
|
get() = sequenceOf(x0, x1, x2, x3).maxOrNull()!!
|
||||||
|
|
||||||
|
val yMin: Int
|
||||||
|
get() = sequenceOf(y0, y1, y2, y3).minOrNull()!!
|
||||||
|
|
||||||
|
val yMax: Int
|
||||||
|
get() = sequenceOf(y0, y1, y2, y3).maxOrNull()!!
|
||||||
|
|
||||||
|
val outerBox: IntRect
|
||||||
|
get() = IntRect(xMin, yMin, xMax, yMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parameter definition with a raw value reference. */
|
||||||
|
class RawParameter(val name: String, val value: Any?)
|
||||||
|
|
||||||
|
/** Mutable version of [InspectorNode]. */
|
||||||
|
internal class MutableInspectorNode {
|
||||||
|
var id = UNDEFINED_ID
|
||||||
|
var key = 0
|
||||||
|
var anchorId = 0
|
||||||
|
val layoutNodes = mutableListOf<LayoutInfo>()
|
||||||
|
val mergedSemantics = mutableListOf<RawParameter>()
|
||||||
|
val unmergedSemantics = mutableListOf<RawParameter>()
|
||||||
|
var name = ""
|
||||||
|
var fileName = ""
|
||||||
|
var packageHash = -1
|
||||||
|
var lineNumber = 0
|
||||||
|
var offset = 0
|
||||||
|
var length = 0
|
||||||
|
var box: IntRect = emptyBox
|
||||||
|
var bounds: QuadBounds? = null
|
||||||
|
var inlined = false
|
||||||
|
val parameters = mutableListOf<RawParameter>()
|
||||||
|
var viewId = UNDEFINED_ID
|
||||||
|
val children = mutableListOf<InspectorNode>()
|
||||||
|
var outerBox: IntRect = outsideBox
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
markUnwanted()
|
||||||
|
id = UNDEFINED_ID
|
||||||
|
key = 0
|
||||||
|
anchorId = 0
|
||||||
|
viewId = UNDEFINED_ID
|
||||||
|
layoutNodes.clear()
|
||||||
|
mergedSemantics.clear()
|
||||||
|
unmergedSemantics.clear()
|
||||||
|
box = emptyBox
|
||||||
|
bounds = null
|
||||||
|
inlined = false
|
||||||
|
outerBox = outsideBox
|
||||||
|
children.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markUnwanted() {
|
||||||
|
name = ""
|
||||||
|
fileName = ""
|
||||||
|
packageHash = -1
|
||||||
|
lineNumber = 0
|
||||||
|
offset = 0
|
||||||
|
length = 0
|
||||||
|
parameters.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shallowCopy(node: InspectorNode): MutableInspectorNode = apply {
|
||||||
|
id = node.id
|
||||||
|
key = node.key
|
||||||
|
anchorId = node.anchorId
|
||||||
|
mergedSemantics.addAll(node.mergedSemantics)
|
||||||
|
unmergedSemantics.addAll(node.unmergedSemantics)
|
||||||
|
name = node.name
|
||||||
|
fileName = node.fileName
|
||||||
|
packageHash = node.packageHash
|
||||||
|
lineNumber = node.lineNumber
|
||||||
|
offset = node.offset
|
||||||
|
length = node.length
|
||||||
|
box = node.box
|
||||||
|
bounds = node.bounds
|
||||||
|
inlined = node.inlined
|
||||||
|
parameters.addAll(node.parameters)
|
||||||
|
viewId = node.viewId
|
||||||
|
children.addAll(node.children)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(withSemantics: Boolean = true): InspectorNode =
|
||||||
|
InspectorNode(
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
anchorId,
|
||||||
|
name,
|
||||||
|
fileName,
|
||||||
|
packageHash,
|
||||||
|
lineNumber,
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
box,
|
||||||
|
bounds,
|
||||||
|
inlined,
|
||||||
|
parameters.toList(),
|
||||||
|
viewId,
|
||||||
|
if (withSemantics) mergedSemantics.toList() else emptyList(),
|
||||||
|
if (withSemantics) unmergedSemantics.toList() else emptyList(),
|
||||||
|
children.toList())
|
||||||
|
}
|
||||||
@@ -0,0 +1,826 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.inspector
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.compose.runtime.tooling.CompositionData
|
||||||
|
import androidx.compose.runtime.tooling.CompositionGroup
|
||||||
|
import androidx.compose.ui.InternalComposeUiApi
|
||||||
|
import androidx.compose.ui.R
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.layout.GraphicLayerInfo
|
||||||
|
import androidx.compose.ui.layout.LayoutInfo
|
||||||
|
import androidx.compose.ui.node.InteroperableComposeUiNode
|
||||||
|
import androidx.compose.ui.node.Ref
|
||||||
|
import androidx.compose.ui.node.RootForTest
|
||||||
|
import androidx.compose.ui.platform.ViewRootForInspector
|
||||||
|
import androidx.compose.ui.semantics.getAllSemanticsNodes
|
||||||
|
import androidx.compose.ui.tooling.data.ContextCache
|
||||||
|
import androidx.compose.ui.tooling.data.ParameterInformation
|
||||||
|
import androidx.compose.ui.tooling.data.SourceContext
|
||||||
|
import androidx.compose.ui.tooling.data.SourceLocation
|
||||||
|
import androidx.compose.ui.tooling.data.UiToolingDataApi
|
||||||
|
import androidx.compose.ui.tooling.data.findParameters
|
||||||
|
import androidx.compose.ui.tooling.data.mapTree
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.IntRect
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.toSize
|
||||||
|
import facebook.internal.androidx.compose.ui.inspection.util.AnchorMap
|
||||||
|
import facebook.internal.androidx.compose.ui.inspection.util.NO_ANCHOR_ID
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.IdentityHashMap
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [InspectorNode.id] will be populated with:
|
||||||
|
* - the layerId from a LayoutNode if this exists
|
||||||
|
* - an id generated from an Anchor instance from the SlotTree if this exists
|
||||||
|
* - a generated id if none of the above ids are available
|
||||||
|
*
|
||||||
|
* The interval -10000..-2 is reserved for the generated ids.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting const val RESERVED_FOR_GENERATED_IDS = -10000L
|
||||||
|
const val PLACEHOLDER_ID = Long.MAX_VALUE
|
||||||
|
|
||||||
|
private val emptySize = IntSize(0, 0)
|
||||||
|
|
||||||
|
private val unwantedCalls =
|
||||||
|
setOf(
|
||||||
|
"CompositionLocalProvider",
|
||||||
|
"Content",
|
||||||
|
"Inspectable",
|
||||||
|
"ProvideAndroidCompositionLocals",
|
||||||
|
"ProvideCommonCompositionLocals",
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Generator of a tree for the Layout Inspector. */
|
||||||
|
@OptIn(UiToolingDataApi::class)
|
||||||
|
class LayoutInspectorTree {
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate") var hideSystemNodes = true
|
||||||
|
var includeNodesOutsizeOfWindow = true
|
||||||
|
var includeAllParameters = true
|
||||||
|
private var foundNode: InspectorNode? = null
|
||||||
|
private var windowSize = emptySize
|
||||||
|
private val inlineClassConverter = InlineClassConverter()
|
||||||
|
private val parameterFactory = ParameterFactory(inlineClassConverter)
|
||||||
|
private val cache = ArrayDeque<MutableInspectorNode>()
|
||||||
|
private var generatedId = -1L
|
||||||
|
private val subCompositions = SubCompositionRoots()
|
||||||
|
/** Map from [LayoutInfo] to the nearest [InspectorNode] that contains it */
|
||||||
|
private val claimedNodes = IdentityHashMap<LayoutInfo, InspectorNode>()
|
||||||
|
/** Map from parent tree to child trees that are about to be stitched together */
|
||||||
|
private val treeMap = IdentityHashMap<MutableInspectorNode, MutableList<MutableInspectorNode>>()
|
||||||
|
/** Map from owner node to child trees that are about to be stitched to this owner */
|
||||||
|
private val ownerMap = IdentityHashMap<InspectorNode, MutableList<MutableInspectorNode>>()
|
||||||
|
/** Map from semantics id to a list of merged semantics information */
|
||||||
|
private val semanticsMap = mutableMapOf<Int, List<RawParameter>>()
|
||||||
|
/* Map of seemantics id to a list of unmerged semantics information */
|
||||||
|
private val unmergedSemanticsMap = mutableMapOf<Int, List<RawParameter>>()
|
||||||
|
/** Set of tree nodes that were stitched into another tree */
|
||||||
|
private val stitched = Collections.newSetFromMap(IdentityHashMap<MutableInspectorNode, Boolean>())
|
||||||
|
private val contextCache = ContextCache()
|
||||||
|
private val anchorMap = AnchorMap()
|
||||||
|
|
||||||
|
/** Converts the [CompositionData] set held by [view] into a list of root nodes. */
|
||||||
|
fun convert(view: View): List<InspectorNode> {
|
||||||
|
windowSize = IntSize(view.width, view.height)
|
||||||
|
parameterFactory.density = Density(view.context)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val tables =
|
||||||
|
view.getTag(R.id.inspection_slot_table_set) as? Set<CompositionData> ?: return emptyList()
|
||||||
|
clear()
|
||||||
|
collectSemantics(view)
|
||||||
|
val result = convert(tables, view)
|
||||||
|
clear()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findParameters(view: View, anchorId: Int): InspectorNode? {
|
||||||
|
windowSize = IntSize(view.width, view.height)
|
||||||
|
parameterFactory.density = Density(view.context)
|
||||||
|
val identity = anchorMap[anchorId] ?: return null
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val tables = view.getTag(R.id.inspection_slot_table_set) as? Set<CompositionData> ?: return null
|
||||||
|
val node = newNode().apply { this.anchorId = anchorId }
|
||||||
|
val group = tables.firstNotNullOfOrNull { it.find(identity) } ?: return null
|
||||||
|
group.findParameters(contextCache).forEach {
|
||||||
|
val castedValue = castValue(it)
|
||||||
|
node.parameters.add(RawParameter(it.name, castedValue))
|
||||||
|
}
|
||||||
|
return buildAndRelease(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the roots to sub compositions that may have been collected from a different SlotTree.
|
||||||
|
*
|
||||||
|
* See [SubCompositionRoots] for details.
|
||||||
|
*/
|
||||||
|
fun addSubCompositionRoots(view: View, nodes: List<InspectorNode>): List<InspectorNode> =
|
||||||
|
subCompositions.addRoot(view, nodes)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the merged semantics for this semantics owner such that they can be added to compose
|
||||||
|
* nodes during the conversion of Group nodes.
|
||||||
|
*/
|
||||||
|
private fun collectSemantics(view: View) {
|
||||||
|
val root = view as? RootForTest ?: return
|
||||||
|
val nodes = root.semanticsOwner.getAllSemanticsNodes(mergingEnabled = true)
|
||||||
|
val unmergedNodes = root.semanticsOwner.getAllSemanticsNodes(mergingEnabled = false)
|
||||||
|
nodes.forEach { node ->
|
||||||
|
semanticsMap[node.id] = node.config.map { RawParameter(it.key.name, it.value) }
|
||||||
|
}
|
||||||
|
unmergedNodes.forEach { node ->
|
||||||
|
unmergedSemanticsMap[node.id] = node.config.map { RawParameter(it.key.name, it.value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts the [RawParameter]s of the [node] into displayable parameters. */
|
||||||
|
fun convertParameters(
|
||||||
|
rootId: Long,
|
||||||
|
node: InspectorNode,
|
||||||
|
kind: ParameterKind,
|
||||||
|
maxRecursions: Int,
|
||||||
|
maxInitialIterableSize: Int
|
||||||
|
): List<NodeParameter> {
|
||||||
|
val parameters = node.parametersByKind(kind)
|
||||||
|
return parameters.mapIndexed { index, parameter ->
|
||||||
|
parameterFactory.create(
|
||||||
|
rootId,
|
||||||
|
node.id,
|
||||||
|
node.anchorId,
|
||||||
|
parameter.name,
|
||||||
|
parameter.value,
|
||||||
|
kind,
|
||||||
|
index,
|
||||||
|
maxRecursions,
|
||||||
|
maxInitialIterableSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a part of the [RawParameter] identified by [reference] into a displayable parameter.
|
||||||
|
* If the parameter is some sort of a collection then [startIndex] and [maxElements] describes the
|
||||||
|
* scope of the data returned.
|
||||||
|
*/
|
||||||
|
fun expandParameter(
|
||||||
|
rootId: Long,
|
||||||
|
node: InspectorNode,
|
||||||
|
reference: NodeParameterReference,
|
||||||
|
startIndex: Int,
|
||||||
|
maxElements: Int,
|
||||||
|
maxRecursions: Int,
|
||||||
|
maxInitialIterableSize: Int
|
||||||
|
): NodeParameter? {
|
||||||
|
val parameters = node.parametersByKind(reference.kind)
|
||||||
|
if (reference.parameterIndex !in parameters.indices) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val parameter = parameters[reference.parameterIndex]
|
||||||
|
return parameterFactory.expand(
|
||||||
|
rootId,
|
||||||
|
node.id,
|
||||||
|
node.anchorId,
|
||||||
|
parameter.name,
|
||||||
|
parameter.value,
|
||||||
|
reference,
|
||||||
|
startIndex,
|
||||||
|
maxElements,
|
||||||
|
maxRecursions,
|
||||||
|
maxInitialIterableSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reset any state accumulated between windows. */
|
||||||
|
@Suppress("unused")
|
||||||
|
fun resetAccumulativeState() {
|
||||||
|
subCompositions.resetAccumulativeState()
|
||||||
|
parameterFactory.clearReferenceCache()
|
||||||
|
// Reset the generated id. Nodes are assigned an id if there isn't a layout node id present.
|
||||||
|
generatedId = -1L
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clear() {
|
||||||
|
cache.clear()
|
||||||
|
inlineClassConverter.clear()
|
||||||
|
claimedNodes.clear()
|
||||||
|
treeMap.clear()
|
||||||
|
ownerMap.clear()
|
||||||
|
semanticsMap.clear()
|
||||||
|
unmergedSemanticsMap.clear()
|
||||||
|
stitched.clear()
|
||||||
|
subCompositions.clear()
|
||||||
|
foundNode = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convert(tables: Set<CompositionData>, view: View): List<InspectorNode> {
|
||||||
|
val trees = tables.mapNotNull { convert(view, it) }
|
||||||
|
return when (trees.size) {
|
||||||
|
0 -> listOf()
|
||||||
|
1 -> addTree(mutableListOf(), trees.single())
|
||||||
|
else -> stitchTreesByLayoutInfo(trees)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stitch separate trees together using the [LayoutInfo]s found in the [CompositionData]s.
|
||||||
|
*
|
||||||
|
* Some constructs in Compose (e.g. ModalDrawer) will result is multiple [CompositionData]s. This
|
||||||
|
* code will attempt to stitch the resulting [InspectorNode] trees together by looking at the
|
||||||
|
* parent of each [LayoutInfo].
|
||||||
|
*
|
||||||
|
* If this algorithm is successful the result of this function will be a list with a single tree.
|
||||||
|
*/
|
||||||
|
private fun stitchTreesByLayoutInfo(trees: List<MutableInspectorNode>): List<InspectorNode> {
|
||||||
|
val layoutToTreeMap = IdentityHashMap<LayoutInfo, MutableInspectorNode>()
|
||||||
|
trees.forEach { tree -> tree.layoutNodes.forEach { layoutToTreeMap[it] = tree } }
|
||||||
|
trees.forEach { tree ->
|
||||||
|
val layout = tree.layoutNodes.lastOrNull()
|
||||||
|
val parentLayout =
|
||||||
|
generateSequence(layout) { it.parentInfo }
|
||||||
|
.firstOrNull {
|
||||||
|
val otherTree = layoutToTreeMap[it]
|
||||||
|
otherTree != null && otherTree != tree
|
||||||
|
}
|
||||||
|
if (parentLayout != null) {
|
||||||
|
val ownerNode = claimedNodes[parentLayout]
|
||||||
|
val ownerTree = layoutToTreeMap[parentLayout]
|
||||||
|
if (ownerNode != null && ownerTree != null) {
|
||||||
|
ownerMap.getOrPut(ownerNode) { mutableListOf() }.add(tree)
|
||||||
|
treeMap.getOrPut(ownerTree) { mutableListOf() }.add(tree)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var parentTree = findDeepParentTree()
|
||||||
|
while (parentTree != null) {
|
||||||
|
addSubTrees(parentTree)
|
||||||
|
treeMap.remove(parentTree)
|
||||||
|
parentTree = findDeepParentTree()
|
||||||
|
}
|
||||||
|
val result = mutableListOf<InspectorNode>()
|
||||||
|
trees.asSequence().filter { !stitched.contains(it) }.forEach { addTree(result, it) }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a parent tree where the children trees (to be stitched under the parent) are not a
|
||||||
|
* parent themselves. Do this to avoid rebuilding the same tree more than once.
|
||||||
|
*/
|
||||||
|
private fun findDeepParentTree(): MutableInspectorNode? =
|
||||||
|
treeMap.entries
|
||||||
|
.asSequence()
|
||||||
|
.filter { (_, children) -> children.none { treeMap.containsKey(it) } }
|
||||||
|
.firstOrNull()
|
||||||
|
?.key
|
||||||
|
|
||||||
|
private fun addSubTrees(tree: MutableInspectorNode) {
|
||||||
|
for ((index, child) in tree.children.withIndex()) {
|
||||||
|
tree.children[index] = addSubTrees(child) ?: child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild [node] with any possible sub trees added (stitched in). Return the rebuild node, or
|
||||||
|
* null if no changes were found in this node or its children. Lazily allocate the new node to
|
||||||
|
* avoid unnecessary allocations.
|
||||||
|
*/
|
||||||
|
private fun addSubTrees(node: InspectorNode): InspectorNode? {
|
||||||
|
var newNode: MutableInspectorNode? = null
|
||||||
|
for ((index, child) in node.children.withIndex()) {
|
||||||
|
val newChild = addSubTrees(child)
|
||||||
|
if (newChild != null) {
|
||||||
|
val newCopy = newNode ?: newNode(node)
|
||||||
|
newCopy.children[index] = newChild
|
||||||
|
newNode = newCopy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val trees = ownerMap[node]
|
||||||
|
if (trees == null && newNode == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val newCopy = newNode ?: newNode(node)
|
||||||
|
if (trees != null) {
|
||||||
|
trees.forEach { addTree(newCopy.children, it) }
|
||||||
|
stitched.addAll(trees)
|
||||||
|
}
|
||||||
|
return buildAndRelease(newCopy)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add [tree] to the end of the [out] list. The root nodes of [tree] may be a fake node that hold
|
||||||
|
* a list of [LayoutInfo].
|
||||||
|
*/
|
||||||
|
private fun addTree(
|
||||||
|
out: MutableList<InspectorNode>,
|
||||||
|
tree: MutableInspectorNode
|
||||||
|
): List<InspectorNode> {
|
||||||
|
tree.children.forEach {
|
||||||
|
if (it.name.isNotEmpty()) {
|
||||||
|
out.add(it)
|
||||||
|
} else {
|
||||||
|
out.addAll(it.children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convert(view: View, table: CompositionData): MutableInspectorNode? {
|
||||||
|
val fakeParent = newNode()
|
||||||
|
val group = table.mapTree(::convert, contextCache) ?: return null
|
||||||
|
addToParent(fakeParent, listOf(group), buildFakeChildNodes = true)
|
||||||
|
return if (belongsToView(fakeParent.layoutNodes, view)) fakeParent else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convert(
|
||||||
|
group: CompositionGroup,
|
||||||
|
context: SourceContext,
|
||||||
|
children: List<MutableInspectorNode>
|
||||||
|
): MutableInspectorNode {
|
||||||
|
val parent = parse(group, context, children)
|
||||||
|
subCompositions.captureNode(parent, context)
|
||||||
|
addToParent(parent, children)
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the nodes in [input] to the children of [parentNode]. Nodes without a reference to a
|
||||||
|
* wanted Composable are skipped unless [buildFakeChildNodes]. A single skipped render id and
|
||||||
|
* layoutNode will be added to [parentNode].
|
||||||
|
*/
|
||||||
|
private fun addToParent(
|
||||||
|
parentNode: MutableInspectorNode,
|
||||||
|
input: List<MutableInspectorNode>,
|
||||||
|
buildFakeChildNodes: Boolean = false
|
||||||
|
) {
|
||||||
|
// If we're adding an unwanted node from the `input` to the parent node and it has a
|
||||||
|
// View ID, then assign it to the parent view so that we don't lose the context that we
|
||||||
|
// found a View as a descendant of the parent node. Most likely, there were one or more
|
||||||
|
// unwanted intermediate nodes between the node that actually owns the Android View
|
||||||
|
// and the desired node that the View should be associated with in the inspector. If
|
||||||
|
// there's more than one input node with a View ID, we skip this step since it's
|
||||||
|
// unclear how these views would be related.
|
||||||
|
input
|
||||||
|
.singleOrNull { it.viewId != UNDEFINED_ID }
|
||||||
|
?.takeIf { node ->
|
||||||
|
// Take if the node has been marked as unwanted
|
||||||
|
node.id == UNDEFINED_ID
|
||||||
|
}
|
||||||
|
?.let { nodeWithView -> parentNode.viewId = nodeWithView.viewId }
|
||||||
|
|
||||||
|
var id: Long? = null
|
||||||
|
input.forEach { node ->
|
||||||
|
if (node.name.isEmpty() && !(buildFakeChildNodes && node.layoutNodes.isNotEmpty())) {
|
||||||
|
parentNode.children.addAll(node.children)
|
||||||
|
if (node.id > UNDEFINED_ID) {
|
||||||
|
// If multiple siblings with a render ids are dropped:
|
||||||
|
// Ignore them all. And delegate the drawing to a parent in the inspector.
|
||||||
|
id = if (id == null) node.id else UNDEFINED_ID
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.id = if (node.id != UNDEFINED_ID) node.id else --generatedId
|
||||||
|
val withSemantics = node.packageHash !in systemPackages
|
||||||
|
val resultNode = node.build(withSemantics)
|
||||||
|
// TODO: replace getOrPut with putIfAbsent which requires API level 24
|
||||||
|
node.layoutNodes.forEach { claimedNodes.getOrPut(it) { resultNode } }
|
||||||
|
parentNode.children.add(resultNode)
|
||||||
|
if (withSemantics) {
|
||||||
|
node.mergedSemantics.clear()
|
||||||
|
node.unmergedSemantics.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.bounds != null && parentNode.box == node.box) {
|
||||||
|
parentNode.bounds = node.bounds
|
||||||
|
}
|
||||||
|
parentNode.layoutNodes.addAll(node.layoutNodes)
|
||||||
|
parentNode.mergedSemantics.addAll(node.mergedSemantics)
|
||||||
|
parentNode.unmergedSemantics.addAll(node.unmergedSemantics)
|
||||||
|
release(node)
|
||||||
|
}
|
||||||
|
val nodeId = id
|
||||||
|
parentNode.id = if (parentNode.id <= UNDEFINED_ID && nodeId != null) nodeId else parentNode.id
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(InternalComposeUiApi::class)
|
||||||
|
private fun parse(
|
||||||
|
group: CompositionGroup,
|
||||||
|
context: SourceContext,
|
||||||
|
children: List<MutableInspectorNode>
|
||||||
|
): MutableInspectorNode {
|
||||||
|
val node = newNode()
|
||||||
|
node.name = context.name ?: ""
|
||||||
|
node.key = group.key as? Int ?: 0
|
||||||
|
node.inlined = context.isInline
|
||||||
|
|
||||||
|
// If this node is associated with an android View, set the node's viewId to point to
|
||||||
|
// the hosted view. We use the parent's uniqueDrawingId since the interopView returned here
|
||||||
|
// will be the view itself, but we want to use the `AndroidViewHolder` that hosts the view
|
||||||
|
// instead of the view directly.
|
||||||
|
(group.node as? InteroperableComposeUiNode?)?.getInteropView()?.let { interopView ->
|
||||||
|
(interopView.parent as? View)?.uniqueDrawingId?.let { viewId -> node.viewId = viewId }
|
||||||
|
}
|
||||||
|
|
||||||
|
val layoutInfo = group.node as? LayoutInfo
|
||||||
|
if (layoutInfo != null) {
|
||||||
|
return parseLayoutInfo(layoutInfo, context, node)
|
||||||
|
}
|
||||||
|
if (unwantedOutsideWindow(node, children)) {
|
||||||
|
return markUnwanted(group, context, node)
|
||||||
|
}
|
||||||
|
node.box = context.bounds.emptyCheck()
|
||||||
|
if (unwantedName(node.name) || (node.box == emptyBox && !subCompositions.capturing)) {
|
||||||
|
return markUnwanted(group, context, node)
|
||||||
|
}
|
||||||
|
parseCallLocation(node, context.location)
|
||||||
|
if (isHiddenSystemNode(node)) {
|
||||||
|
return markUnwanted(group, context, node)
|
||||||
|
}
|
||||||
|
node.anchorId = anchorMap[group.identity]
|
||||||
|
node.id = syntheticId(node.anchorId)
|
||||||
|
if (includeAllParameters) {
|
||||||
|
addParameters(context, node)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IntRect.emptyCheck(): IntRect = if (left >= right && top >= bottom) emptyBox else this
|
||||||
|
|
||||||
|
private fun IntRect.inWindow(): Boolean =
|
||||||
|
!(left > windowSize.width || right < 0 || top > windowSize.height || bottom < 0)
|
||||||
|
|
||||||
|
private fun IntRect.union(other: IntRect): IntRect {
|
||||||
|
if (this == outsideBox) return other else if (other == outsideBox) return this
|
||||||
|
|
||||||
|
return IntRect(
|
||||||
|
left = min(left, other.left),
|
||||||
|
top = min(top, other.top),
|
||||||
|
bottom = max(bottom, other.bottom),
|
||||||
|
right = max(right, other.right))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseLayoutInfo(
|
||||||
|
layoutInfo: LayoutInfo,
|
||||||
|
context: SourceContext,
|
||||||
|
node: MutableInspectorNode
|
||||||
|
): MutableInspectorNode {
|
||||||
|
val box = context.bounds
|
||||||
|
val size = box.size.toSize()
|
||||||
|
val coordinates = layoutInfo.coordinates
|
||||||
|
val topLeft = toIntOffset(coordinates.localToWindow(Offset.Zero))
|
||||||
|
val topRight = toIntOffset(coordinates.localToWindow(Offset(size.width, 0f)))
|
||||||
|
val bottomRight = toIntOffset(coordinates.localToWindow(Offset(size.width, size.height)))
|
||||||
|
val bottomLeft = toIntOffset(coordinates.localToWindow(Offset(0f, size.height)))
|
||||||
|
var bounds: QuadBounds? = null
|
||||||
|
|
||||||
|
if (topLeft.x != box.left ||
|
||||||
|
topLeft.y != box.top ||
|
||||||
|
topRight.x != box.right ||
|
||||||
|
topRight.y != box.top ||
|
||||||
|
bottomRight.x != box.right ||
|
||||||
|
bottomRight.y != box.bottom ||
|
||||||
|
bottomLeft.x != box.left ||
|
||||||
|
bottomLeft.y != box.bottom) {
|
||||||
|
bounds =
|
||||||
|
QuadBounds(
|
||||||
|
topLeft.x,
|
||||||
|
topLeft.y,
|
||||||
|
topRight.x,
|
||||||
|
topRight.y,
|
||||||
|
bottomRight.x,
|
||||||
|
bottomRight.y,
|
||||||
|
bottomLeft.x,
|
||||||
|
bottomLeft.y,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!includeNodesOutsizeOfWindow) {
|
||||||
|
// Ignore this node if the bounds are completely outside the window
|
||||||
|
node.outerBox = bounds?.outerBox ?: box
|
||||||
|
if (!node.outerBox.inWindow()) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.box = box.emptyCheck()
|
||||||
|
node.bounds = bounds
|
||||||
|
node.layoutNodes.add(layoutInfo)
|
||||||
|
val modifierInfo = layoutInfo.getModifierInfo()
|
||||||
|
|
||||||
|
val unmergedSemantics = unmergedSemanticsMap[layoutInfo.semanticsId]
|
||||||
|
if (unmergedSemantics != null) {
|
||||||
|
node.unmergedSemantics.addAll(unmergedSemantics)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mergedSemantics = semanticsMap[layoutInfo.semanticsId]
|
||||||
|
if (mergedSemantics != null) {
|
||||||
|
node.mergedSemantics.addAll(mergedSemantics)
|
||||||
|
}
|
||||||
|
|
||||||
|
node.id =
|
||||||
|
modifierInfo
|
||||||
|
.asSequence()
|
||||||
|
.map { it.extra }
|
||||||
|
.filterIsInstance<GraphicLayerInfo>()
|
||||||
|
.map { it.layerId }
|
||||||
|
.firstOrNull() ?: UNDEFINED_ID
|
||||||
|
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun syntheticId(anchorId: Int): Long {
|
||||||
|
if (anchorId == NO_ANCHOR_ID) {
|
||||||
|
return UNDEFINED_ID
|
||||||
|
}
|
||||||
|
// The anchorId is an Int
|
||||||
|
return anchorId.toLong() - Int.MAX_VALUE.toLong() + RESERVED_FOR_GENERATED_IDS
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun belongsToView(layoutNodes: List<LayoutInfo>, view: View): Boolean =
|
||||||
|
layoutNodes
|
||||||
|
.asSequence()
|
||||||
|
.flatMap { node ->
|
||||||
|
node
|
||||||
|
.getModifierInfo()
|
||||||
|
.asSequence()
|
||||||
|
.map { it.extra }
|
||||||
|
.filterIsInstance<GraphicLayerInfo>()
|
||||||
|
.map { it.ownerViewId }
|
||||||
|
}
|
||||||
|
.contains(view.uniqueDrawingId)
|
||||||
|
|
||||||
|
private fun addParameters(context: SourceContext, node: MutableInspectorNode) {
|
||||||
|
context.parameters.forEach {
|
||||||
|
val castedValue = castValue(it)
|
||||||
|
node.parameters.add(RawParameter(it.name, castedValue))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun castValue(parameter: ParameterInformation): Any? {
|
||||||
|
val value = parameter.value ?: return null
|
||||||
|
if (parameter.inlineClass == null || !isPrimitive(value.javaClass)) return value
|
||||||
|
return inlineClassConverter.castParameterValue(parameter.inlineClass, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPrimitive(cls: Class<*>): Boolean = cls.kotlin.javaPrimitiveType != null
|
||||||
|
|
||||||
|
private fun toIntOffset(offset: Offset): IntOffset =
|
||||||
|
IntOffset(offset.x.roundToInt(), offset.y.roundToInt())
|
||||||
|
|
||||||
|
private fun markUnwanted(
|
||||||
|
group: CompositionGroup,
|
||||||
|
context: SourceContext,
|
||||||
|
node: MutableInspectorNode
|
||||||
|
): MutableInspectorNode =
|
||||||
|
when (node.name) {
|
||||||
|
"rememberCompositionContext" -> subCompositions.rememberCompositionContext(node, context)
|
||||||
|
"remember" -> subCompositions.remember(node, group)
|
||||||
|
else -> node.apply { markUnwanted() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseCallLocation(node: MutableInspectorNode, location: SourceLocation?) {
|
||||||
|
val fileName = location?.sourceFile ?: return
|
||||||
|
node.fileName = fileName
|
||||||
|
node.packageHash = location.packageHash
|
||||||
|
node.lineNumber = location.lineNumber
|
||||||
|
node.offset = location.offset
|
||||||
|
node.length = location.length
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isHiddenSystemNode(node: MutableInspectorNode): Boolean =
|
||||||
|
node.packageHash in systemPackages && hideSystemNodes
|
||||||
|
|
||||||
|
private fun unwantedName(name: String): Boolean =
|
||||||
|
name.isEmpty() || name.startsWith("remember") || name in unwantedCalls
|
||||||
|
|
||||||
|
private fun unwantedOutsideWindow(
|
||||||
|
node: MutableInspectorNode,
|
||||||
|
children: List<MutableInspectorNode>
|
||||||
|
): Boolean {
|
||||||
|
if (includeNodesOutsizeOfWindow) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
node.outerBox =
|
||||||
|
if (children.isEmpty()) outsideBox
|
||||||
|
else children.map { g -> g.outerBox }.reduce { acc, box -> box.union(acc) }
|
||||||
|
return !node.outerBox.inWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newNode(): MutableInspectorNode =
|
||||||
|
if (cache.isNotEmpty()) cache.pop() else MutableInspectorNode()
|
||||||
|
|
||||||
|
private fun newNode(copyFrom: InspectorNode): MutableInspectorNode =
|
||||||
|
newNode().shallowCopy(copyFrom)
|
||||||
|
|
||||||
|
private fun release(node: MutableInspectorNode) {
|
||||||
|
node.reset()
|
||||||
|
cache.add(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildAndRelease(node: MutableInspectorNode): InspectorNode {
|
||||||
|
val result = node.build()
|
||||||
|
release(node)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of sub-composition roots.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* - Popup, Dialog: When one of these is open an extra Android Window is created with its own
|
||||||
|
* AndroidComposeView. The contents of the Composable is a sub-composition that will be computed
|
||||||
|
* by calling convert.
|
||||||
|
*
|
||||||
|
* The Popup/Dialog composable itself, and a few helping composables (the root) will not be
|
||||||
|
* included in the SlotTree with the contents, instead these composables will be found in the
|
||||||
|
* SlotTree for the main app and they all have empty sizes. The aim is to collect these
|
||||||
|
* sub-composition roots such that they can be added to the [InspectorNode]s of the contents.
|
||||||
|
* - AndroidView: When this is used in a compose app we will see a similar pattern in the SlotTree
|
||||||
|
* except there isn't a sub-composition to stitch in. But we need to collect the view id
|
||||||
|
* separately from the "AndroidView" node itself.
|
||||||
|
*/
|
||||||
|
private inner class SubCompositionRoots {
|
||||||
|
/** Set to true when the nodes found should be added to a sub-composition root */
|
||||||
|
var capturing = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
/** The `uniqueDrawingId` of the `AndroidComposeView` that owns the root being captured */
|
||||||
|
private var ownerView = UNDEFINED_ID
|
||||||
|
|
||||||
|
/** The node that represent the root of the sub-composition */
|
||||||
|
private var rootNode: MutableInspectorNode? = null
|
||||||
|
|
||||||
|
/** The depth of the parse tree the [rootNode] was found at */
|
||||||
|
private var rootNodeDepth = 0
|
||||||
|
|
||||||
|
/** Last captured view that is believed to be an embbed View under an AndroidView node */
|
||||||
|
private var androidView = UNDEFINED_ID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sub-composition roots found.
|
||||||
|
*
|
||||||
|
* Map from View owner to a pair of [InspectorNode] indicating the actual root, and the node
|
||||||
|
* where the content should be stitched in.
|
||||||
|
*/
|
||||||
|
private val found = mutableMapOf<Long, InspectorNode>()
|
||||||
|
|
||||||
|
/** Call this before converting a SlotTree for an AndroidComposeView */
|
||||||
|
fun clear() {
|
||||||
|
capturing = false
|
||||||
|
ownerView = UNDEFINED_ID
|
||||||
|
rootNode?.markUnwanted()
|
||||||
|
rootNode?.id = UNDEFINED_ID
|
||||||
|
rootNode = null
|
||||||
|
rootNodeDepth = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Call this when starting converting a new set of windows */
|
||||||
|
fun resetAccumulativeState() {
|
||||||
|
found.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a "rememberCompositionContext" is found in the slot tree, it indicates that a
|
||||||
|
* sub-composition was started. We should capture all parent nodes with an empty size as the
|
||||||
|
* "root" of the sub-composition.
|
||||||
|
*/
|
||||||
|
fun rememberCompositionContext(
|
||||||
|
node: MutableInspectorNode,
|
||||||
|
context: SourceContext
|
||||||
|
): MutableInspectorNode {
|
||||||
|
if (capturing) {
|
||||||
|
save()
|
||||||
|
}
|
||||||
|
capturing = true
|
||||||
|
rootNode = node
|
||||||
|
rootNodeDepth = context.depth
|
||||||
|
node.id = PLACEHOLDER_ID
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When "remember" is found in the slot tree and we are currently capturing, the data of the
|
||||||
|
* [group] may contain the owner of the sub-composition.
|
||||||
|
*/
|
||||||
|
fun remember(node: MutableInspectorNode, group: CompositionGroup): MutableInspectorNode {
|
||||||
|
node.markUnwanted()
|
||||||
|
if (!capturing) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
val root = findSingleRootInGroupData(group) ?: return node
|
||||||
|
|
||||||
|
val view = root.subCompositionView
|
||||||
|
if (view != null) {
|
||||||
|
val composeOwner = if (view.childCount == 1) view.getChildAt(0) else return node
|
||||||
|
ownerView = composeOwner.uniqueDrawingId
|
||||||
|
} else {
|
||||||
|
androidView = root.viewRoot?.uniqueDrawingId ?: UNDEFINED_ID
|
||||||
|
// Store the viewRoot such that we can move the View under the compose node
|
||||||
|
// in Studio. We do not need to capture the Groups found for this case, so
|
||||||
|
// we call "reset" here to stop capturing.
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findSingleRootInGroupData(group: CompositionGroup): ViewRootForInspector? {
|
||||||
|
group.data.filterIsInstance<ViewRootForInspector>().singleOrNull()?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
val refs = group.data.filterIsInstance<Ref<*>>().map { it.value }
|
||||||
|
return refs.filterIsInstance<ViewRootForInspector>().singleOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Capture the top node of the sub-composition root until a non empty node is found. */
|
||||||
|
fun captureNode(node: MutableInspectorNode, context: SourceContext) {
|
||||||
|
if (!capturing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (node.box != emptyBox) {
|
||||||
|
save()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val depth = context.depth
|
||||||
|
if (depth < rootNodeDepth) {
|
||||||
|
rootNode = node
|
||||||
|
rootNodeDepth = depth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun latestViewId(): Long {
|
||||||
|
val id = androidView
|
||||||
|
androidView = UNDEFINED_ID
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If a sub-composition root has been captured, save it now. */
|
||||||
|
private fun save() {
|
||||||
|
val node = rootNode
|
||||||
|
if (node != null && ownerView != UNDEFINED_ID) {
|
||||||
|
found[ownerView] = node.build()
|
||||||
|
}
|
||||||
|
node?.markUnwanted()
|
||||||
|
node?.id = UNDEFINED_ID
|
||||||
|
node?.children?.clear()
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the root of the sub-composition to the found tree.
|
||||||
|
*
|
||||||
|
* If a root is not found for this [owner] or if the stitching fails just return [nodes].
|
||||||
|
*/
|
||||||
|
fun addRoot(owner: View, nodes: List<InspectorNode>): List<InspectorNode> {
|
||||||
|
val root = found[owner.uniqueDrawingId] ?: return nodes
|
||||||
|
val box = IntRect(0, 0, owner.width, owner.height)
|
||||||
|
val info = StitchInfo(nodes, box)
|
||||||
|
val result = listOf(stitch(root, info))
|
||||||
|
return if (info.added) result else nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stitch(node: InspectorNode, info: StitchInfo): InspectorNode {
|
||||||
|
val children = node.children.map { stitch(it, info) }
|
||||||
|
val index = children.indexOfFirst { it.id == PLACEHOLDER_ID }
|
||||||
|
val newNode = newNode()
|
||||||
|
newNode.shallowCopy(node)
|
||||||
|
newNode.children.clear()
|
||||||
|
if (index < 0) {
|
||||||
|
newNode.children.addAll(children)
|
||||||
|
} else {
|
||||||
|
newNode.children.addAll(children.subList(0, index))
|
||||||
|
newNode.children.addAll(info.nodes)
|
||||||
|
newNode.children.addAll(children.subList(index + 1, children.size))
|
||||||
|
info.added = true
|
||||||
|
}
|
||||||
|
newNode.box = info.bounds
|
||||||
|
return buildAndRelease(newNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StitchInfo(
|
||||||
|
/** The nodes found that should be stitched into a sub-composition root. */
|
||||||
|
val nodes: List<InspectorNode>,
|
||||||
|
|
||||||
|
/** The bounds of the View containing the sub-composition */
|
||||||
|
val bounds: IntRect
|
||||||
|
) {
|
||||||
|
/** Set this to true when the [nodes] have been added to a sub-composition root */
|
||||||
|
var added: Boolean = false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.inspector
|
||||||
|
|
||||||
|
/** Holds data representing a Composable parameter for the Layout Inspector. */
|
||||||
|
class NodeParameter
|
||||||
|
internal constructor(
|
||||||
|
/** The name of the parameter. */
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
/** The type of the parameter. */
|
||||||
|
val type: ParameterType,
|
||||||
|
|
||||||
|
/** The value of the parameter. */
|
||||||
|
val value: Any?
|
||||||
|
) {
|
||||||
|
/** Sub elements of the parameter. */
|
||||||
|
val elements = mutableListOf<NodeParameter>()
|
||||||
|
|
||||||
|
/** Reference to value parameter. */
|
||||||
|
var reference: NodeParameterReference? = null
|
||||||
|
|
||||||
|
/** The index into the composite parent parameter value. */
|
||||||
|
var index = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The type of a parameter. */
|
||||||
|
enum class ParameterType {
|
||||||
|
String,
|
||||||
|
Boolean,
|
||||||
|
Double,
|
||||||
|
Float,
|
||||||
|
Int32,
|
||||||
|
Int64,
|
||||||
|
Color,
|
||||||
|
Resource,
|
||||||
|
DimensionDp,
|
||||||
|
DimensionSp,
|
||||||
|
DimensionEm,
|
||||||
|
Lambda,
|
||||||
|
FunctionReference,
|
||||||
|
Iterable,
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.inspector
|
||||||
|
|
||||||
|
import facebook.internal.androidx.compose.ui.inspection.util.asIntArray
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reference to a parameter to a [NodeParameter]
|
||||||
|
*
|
||||||
|
* @param nodeId is the id of the node the parameter belongs to
|
||||||
|
* @param anchorId is the anchor hash of the node the parameter belongs to
|
||||||
|
* @param kind is this a reference to a normal, merged, or unmerged semantic parameter.
|
||||||
|
* @param parameterIndex index into [InspectorNode.parameters], [InspectorNode.mergedSemantics], or
|
||||||
|
* [InspectorNode.unMergedSemantics]
|
||||||
|
* @param indices are indices into the composite parameter
|
||||||
|
*/
|
||||||
|
class NodeParameterReference(
|
||||||
|
val nodeId: Long,
|
||||||
|
val anchorId: Int,
|
||||||
|
val kind: ParameterKind,
|
||||||
|
val parameterIndex: Int,
|
||||||
|
val indices: IntArray
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
nodeId: Long,
|
||||||
|
anchorId: Int,
|
||||||
|
kind: ParameterKind,
|
||||||
|
parameterIndex: Int,
|
||||||
|
indices: List<Int>
|
||||||
|
) : this(nodeId, anchorId, kind, parameterIndex, indices.asIntArray())
|
||||||
|
|
||||||
|
// For testing:
|
||||||
|
override fun toString(): String {
|
||||||
|
val suffix = if (indices.isNotEmpty()) ", ${indices.joinToString()}" else ""
|
||||||
|
return "[$nodeId, $anchorId, $kind, $parameterIndex$suffix]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Identifies which kind of parameter the [NodeParameterReference] is a reference to. */
|
||||||
|
enum class ParameterKind {
|
||||||
|
Normal,
|
||||||
|
MergedSemantics,
|
||||||
|
UnmergedSemantics
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WARNING: DO NOT EDIT THIS FILE MANUALLY. It's automatically generated by running:
|
||||||
|
// frameworks/support/compose/ui/ui-inspection/generate-packages/generate_compose_packages.py -r
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.inspector
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
fun packageNameHash(packageName: String) =
|
||||||
|
packageName.fold(0) { hash, char -> hash * 31 + char.code }.absoluteValue
|
||||||
|
|
||||||
|
val systemPackages =
|
||||||
|
setOf(
|
||||||
|
-1,
|
||||||
|
packageNameHash("androidx.compose.animation"),
|
||||||
|
packageNameHash("androidx.compose.animation.core"),
|
||||||
|
packageNameHash("androidx.compose.animation.graphics.vector"),
|
||||||
|
packageNameHash("androidx.compose.desktop"),
|
||||||
|
packageNameHash("androidx.compose.foundation"),
|
||||||
|
packageNameHash("androidx.compose.foundation.gestures"),
|
||||||
|
packageNameHash("androidx.compose.foundation.gestures.snapping"),
|
||||||
|
packageNameHash("androidx.compose.foundation.interaction"),
|
||||||
|
packageNameHash("androidx.compose.foundation.layout"),
|
||||||
|
packageNameHash("androidx.compose.foundation.lazy"),
|
||||||
|
packageNameHash("androidx.compose.foundation.lazy.grid"),
|
||||||
|
packageNameHash("androidx.compose.foundation.lazy.layout"),
|
||||||
|
packageNameHash("androidx.compose.foundation.lazy.staggeredgrid"),
|
||||||
|
packageNameHash("androidx.compose.foundation.newtext.text"),
|
||||||
|
packageNameHash("androidx.compose.foundation.newtext.text.copypasta"),
|
||||||
|
packageNameHash("androidx.compose.foundation.newtext.text.copypasta.selection"),
|
||||||
|
packageNameHash("androidx.compose.foundation.pager"),
|
||||||
|
packageNameHash("androidx.compose.foundation.relocation"),
|
||||||
|
packageNameHash("androidx.compose.foundation.text"),
|
||||||
|
packageNameHash("androidx.compose.foundation.text.selection"),
|
||||||
|
packageNameHash("androidx.compose.foundation.window"),
|
||||||
|
packageNameHash("androidx.compose.material"),
|
||||||
|
packageNameHash("androidx.compose.material.internal"),
|
||||||
|
packageNameHash("androidx.compose.material.pullrefresh"),
|
||||||
|
packageNameHash("androidx.compose.material.ripple"),
|
||||||
|
packageNameHash("androidx.compose.material3"),
|
||||||
|
packageNameHash("androidx.compose.material3.internal"),
|
||||||
|
packageNameHash("androidx.compose.material3.windowsizeclass"),
|
||||||
|
packageNameHash("androidx.compose.runtime"),
|
||||||
|
packageNameHash("androidx.compose.runtime.livedata"),
|
||||||
|
packageNameHash("androidx.compose.runtime.mock"),
|
||||||
|
packageNameHash("androidx.compose.runtime.reflect"),
|
||||||
|
packageNameHash("androidx.compose.runtime.rxjava2"),
|
||||||
|
packageNameHash("androidx.compose.runtime.rxjava3"),
|
||||||
|
packageNameHash("androidx.compose.runtime.saveable"),
|
||||||
|
packageNameHash("androidx.compose.ui"),
|
||||||
|
packageNameHash("androidx.compose.ui.awt"),
|
||||||
|
packageNameHash("androidx.compose.ui.graphics.benchmark"),
|
||||||
|
packageNameHash("androidx.compose.ui.graphics.vector"),
|
||||||
|
packageNameHash("androidx.compose.ui.layout"),
|
||||||
|
packageNameHash("androidx.compose.ui.platform"),
|
||||||
|
packageNameHash("androidx.compose.ui.text"),
|
||||||
|
packageNameHash("androidx.compose.ui.util"),
|
||||||
|
packageNameHash("androidx.compose.ui.viewinterop"),
|
||||||
|
packageNameHash("androidx.compose.ui.window"),
|
||||||
|
)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,215 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.inspector
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
import kotlin.jvm.internal.FunctionBase
|
||||||
|
import kotlin.jvm.internal.FunctionReference
|
||||||
|
import kotlin.jvm.internal.Lambda
|
||||||
|
import kotlin.jvm.internal.MutablePropertyReference0
|
||||||
|
import kotlin.jvm.internal.MutablePropertyReference1
|
||||||
|
import kotlin.jvm.internal.MutablePropertyReference2
|
||||||
|
import kotlin.jvm.internal.PropertyReference0
|
||||||
|
import kotlin.jvm.internal.PropertyReference1
|
||||||
|
import kotlin.jvm.internal.PropertyReference2
|
||||||
|
import kotlin.jvm.internal.Reflection
|
||||||
|
import kotlin.jvm.internal.ReflectionFactory
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KClassifier
|
||||||
|
import kotlin.reflect.KDeclarationContainer
|
||||||
|
import kotlin.reflect.KFunction
|
||||||
|
import kotlin.reflect.KMutableProperty0
|
||||||
|
import kotlin.reflect.KMutableProperty1
|
||||||
|
import kotlin.reflect.KMutableProperty2
|
||||||
|
import kotlin.reflect.KProperty0
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.KProperty2
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.KTypeParameter
|
||||||
|
import kotlin.reflect.KTypeProjection
|
||||||
|
import kotlin.reflect.KVariance
|
||||||
|
import kotlin.reflect.jvm.internal.ReflectionFactoryImpl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope that allows to use jarjar-ed kotlin-reflect artifact that is shipped with inspector itself.
|
||||||
|
*
|
||||||
|
* Issue with kotlin-reflect. Many of reflective calls such as "foo::class" rely on static functions
|
||||||
|
* defined in kotlin-stdlib's Reflection.java that delegate to ReflectionFactory. In order to
|
||||||
|
* initialize that factory kotlin-stdlib statically detects presence or absence of kotlin-reflect in
|
||||||
|
* classloader and chooses a factory accordingly. If there is no kotlin-reflect, very limited
|
||||||
|
* version of ReflectionFactory is used.
|
||||||
|
*
|
||||||
|
* It is an issue for inspectors because they could be loaded after that factory is initialised, and
|
||||||
|
* even if they are loaded before, they live in a separate child classloader, thus kotlin-reflect in
|
||||||
|
* inspector wouldn't exist for kotlin-stdlib in app.
|
||||||
|
*
|
||||||
|
* First step to avoid the issue is using ReflectionFactoryImpl that is bundled with inspector. Code
|
||||||
|
* for that would be fairly simple, for example instead of directly calling
|
||||||
|
* `kClass.declaredMemberProperties`, correct instance of kClass should be obtained from factory:
|
||||||
|
* `factory.getOrCreateKotlinClass(kClass.java).declaredMemberProperties`.
|
||||||
|
*
|
||||||
|
* That would work if code that works with correct KClass full implementation would never try to
|
||||||
|
* access a default factory installed in Reflection.java. Unfortunately it is not true, it
|
||||||
|
* eventually calls `CallableReference.getOwner()` in stdlib that uses default factory.
|
||||||
|
*
|
||||||
|
* As a result we have to replace the factory in Reflection.java. To avoid issues with user's code
|
||||||
|
* factory that we setup is smart, by default it simply delegates to a factory that was previously
|
||||||
|
* installed. Only within `reflectionScope.withReflectiveAccess{ }` factory from kotlin-reflect is
|
||||||
|
* used.
|
||||||
|
*/
|
||||||
|
@SuppressLint("BanUncheckedReflection")
|
||||||
|
class ReflectionScope {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
allowHiddenApi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val scopedReflectionFactory = installScopedReflectionFactory()
|
||||||
|
|
||||||
|
/** Runs `block` with access to kotlin-reflect. */
|
||||||
|
fun <T> withReflectiveAccess(block: () -> T): T {
|
||||||
|
return scopedReflectionFactory.withMainFactory(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installScopedReflectionFactory(): ScopedReflectionFactory {
|
||||||
|
val factoryField = Reflection::class.java.getDeclaredField("factory")
|
||||||
|
factoryField.isAccessible = true
|
||||||
|
val original: ReflectionFactory = factoryField.get(null) as ReflectionFactory
|
||||||
|
val modifiersField: Field = Field::class.java.getDeclaredField("accessFlags")
|
||||||
|
modifiersField.isAccessible = true
|
||||||
|
// make field non-final 😅 b/179685774 https://youtrack.jetbrains.com/issue/KT-44795
|
||||||
|
modifiersField.setInt(factoryField, factoryField.modifiers and Modifier.FINAL.inv())
|
||||||
|
val scopedReflectionFactory = ScopedReflectionFactory(original)
|
||||||
|
factoryField.set(null, scopedReflectionFactory)
|
||||||
|
return scopedReflectionFactory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("BanUncheckedReflection")
|
||||||
|
private fun allowHiddenApi() {
|
||||||
|
try {
|
||||||
|
val vmDebug = Class.forName("dalvik.system.VMDebug")
|
||||||
|
val allowHiddenApiReflectionFrom =
|
||||||
|
vmDebug.getDeclaredMethod("allowHiddenApiReflectionFrom", Class::class.java)
|
||||||
|
allowHiddenApiReflectionFrom.invoke(null, ReflectionScope::class.java)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
// ignore failure, let's try to proceed without it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ScopedReflectionFactory(
|
||||||
|
private val original: ReflectionFactory,
|
||||||
|
) : ReflectionFactory() {
|
||||||
|
private val mainFactory = ReflectionFactoryImpl()
|
||||||
|
private val threadLocalFactory = ThreadLocal<ReflectionFactory>()
|
||||||
|
|
||||||
|
fun <T> withMainFactory(block: () -> T): T {
|
||||||
|
threadLocalFactory.set(mainFactory)
|
||||||
|
try {
|
||||||
|
return block()
|
||||||
|
} finally {
|
||||||
|
threadLocalFactory.set(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val factory: ReflectionFactory
|
||||||
|
get() = threadLocalFactory.get() ?: original
|
||||||
|
|
||||||
|
override fun createKotlinClass(javaClass: Class<*>?): KClass<*> {
|
||||||
|
return factory.createKotlinClass(javaClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createKotlinClass(javaClass: Class<*>?, internalName: String?): KClass<*> {
|
||||||
|
return factory.createKotlinClass(javaClass, internalName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOrCreateKotlinPackage(
|
||||||
|
javaClass: Class<*>?,
|
||||||
|
moduleName: String?
|
||||||
|
): KDeclarationContainer {
|
||||||
|
return factory.getOrCreateKotlinPackage(javaClass, moduleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOrCreateKotlinClass(javaClass: Class<*>?): KClass<*> {
|
||||||
|
return factory.getOrCreateKotlinClass(javaClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getOrCreateKotlinClass(javaClass: Class<*>?, internalName: String?): KClass<*> {
|
||||||
|
return factory.getOrCreateKotlinClass(javaClass, internalName)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun renderLambdaToString(lambda: Lambda<*>?): String {
|
||||||
|
return factory.renderLambdaToString(lambda)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun renderLambdaToString(lambda: FunctionBase<*>?): String {
|
||||||
|
return factory.renderLambdaToString(lambda)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun function(f: FunctionReference?): KFunction<*> {
|
||||||
|
return factory.function(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun property0(p: PropertyReference0?): KProperty0<*> {
|
||||||
|
return factory.property0(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mutableProperty0(p: MutablePropertyReference0?): KMutableProperty0<*> {
|
||||||
|
return factory.mutableProperty0(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun property1(p: PropertyReference1?): KProperty1<*, *> {
|
||||||
|
return factory.property1(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mutableProperty1(p: MutablePropertyReference1?): KMutableProperty1<*, *> {
|
||||||
|
return factory.mutableProperty1(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun property2(p: PropertyReference2?): KProperty2<*, *, *> {
|
||||||
|
return factory.property2(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mutableProperty2(p: MutablePropertyReference2?): KMutableProperty2<*, *, *> {
|
||||||
|
return factory.mutableProperty2(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun typeOf(
|
||||||
|
klass: KClassifier?,
|
||||||
|
arguments: MutableList<KTypeProjection>?,
|
||||||
|
isMarkedNullable: Boolean
|
||||||
|
): KType {
|
||||||
|
return factory.typeOf(klass, arguments, isMarkedNullable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun typeParameter(
|
||||||
|
container: Any?,
|
||||||
|
name: String?,
|
||||||
|
variance: KVariance?,
|
||||||
|
isReified: Boolean
|
||||||
|
): KTypeParameter {
|
||||||
|
return factory.typeParameter(container, name, variance, isReified)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUpperBounds(typeParameter: KTypeParameter?, bounds: MutableList<KType>?) {
|
||||||
|
factory.setUpperBounds(typeParameter, bounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.util
|
||||||
|
|
||||||
|
import java.util.IdentityHashMap
|
||||||
|
|
||||||
|
const val NO_ANCHOR_ID = 0
|
||||||
|
|
||||||
|
/** A map of anchors with a unique id generator. */
|
||||||
|
class AnchorMap {
|
||||||
|
private val anchorLookup = mutableMapOf<Int, Any>()
|
||||||
|
private val idLookup = IdentityHashMap<Any, Int>()
|
||||||
|
|
||||||
|
/** Return a unique id for the specified [anchor] instance. */
|
||||||
|
operator fun get(anchor: Any?): Int =
|
||||||
|
anchor?.let { idLookup.getOrPut(it) { generateUniqueId(it) } } ?: NO_ANCHOR_ID
|
||||||
|
|
||||||
|
/** Return the anchor associated with a given unique anchor [id]. */
|
||||||
|
operator fun get(id: Int): Any? = anchorLookup[id]
|
||||||
|
|
||||||
|
private fun generateUniqueId(anchor: Any): Int {
|
||||||
|
var id = anchor.hashCode()
|
||||||
|
while (id == NO_ANCHOR_ID || anchorLookup.containsKey(id)) {
|
||||||
|
id++
|
||||||
|
}
|
||||||
|
anchorLookup[id] = anchor
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.util
|
||||||
|
|
||||||
|
private val EMPTY_INT_ARRAY = intArrayOf()
|
||||||
|
|
||||||
|
fun List<Int>.asIntArray() = if (isNotEmpty()) toIntArray() else EMPTY_INT_ARRAY
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package facebook.internal.androidx.compose.ui.inspection.util
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.Future
|
||||||
|
|
||||||
|
object ThreadUtils {
|
||||||
|
fun assertOnMainThread() {
|
||||||
|
if (!Looper.getMainLooper().isCurrentThread) {
|
||||||
|
error("This work is required on the main thread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertOffMainThread() {
|
||||||
|
if (Looper.getMainLooper().isCurrentThread) {
|
||||||
|
error("This work is required off the main thread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run some logic on the main thread, returning a future that will contain any data computed by
|
||||||
|
* and returned from the block.
|
||||||
|
*
|
||||||
|
* If this method is called from the main thread, it will run immediately.
|
||||||
|
*/
|
||||||
|
fun <T> runOnMainThread(block: () -> T): Future<T> {
|
||||||
|
return if (!Looper.getMainLooper().isCurrentThread) {
|
||||||
|
val future = CompletableFuture<T>()
|
||||||
|
Handler.createAsync(Looper.getMainLooper()).post { future.complete(block()) }
|
||||||
|
future
|
||||||
|
} else {
|
||||||
|
CompletableFuture.completedFuture(block())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.plugins.leakcanary'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.plugins.leakcanary2'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
@@ -19,8 +19,13 @@ android {
|
|||||||
targetSdkVersion rootProject.targetSdkVersion
|
targetSdkVersion rootProject.targetSdkVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility rootProject.javaTargetVersion
|
||||||
|
sourceCompatibility rootProject.javaTargetVersion
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
|
||||||
implementation project(':android')
|
implementation project(':android')
|
||||||
compileOnly deps.leakcanary2
|
compileOnly deps.leakcanary2
|
||||||
compileOnly deps.jsr305
|
compileOnly deps.jsr305
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.leakcanary2
|
||||||
|
|
||||||
|
import com.facebook.flipper.android.AndroidFlipperClient
|
||||||
|
import leakcanary.EventListener
|
||||||
|
import shark.HeapAnalysis
|
||||||
|
import shark.HeapAnalysisSuccess
|
||||||
|
|
||||||
|
class FlipperLeakEventListener : EventListener {
|
||||||
|
private val leaks: MutableList<Leak> = mutableListOf()
|
||||||
|
|
||||||
|
override fun onEvent(event: EventListener.Event) {
|
||||||
|
if (event is EventListener.Event.HeapAnalysisDone.HeapAnalysisSucceeded) {
|
||||||
|
val heapAnalysis = event.heapAnalysis
|
||||||
|
leaks.addAll(heapAnalysis.toLeakList())
|
||||||
|
|
||||||
|
AndroidFlipperClient.getInstanceIfInitialized()?.let { client ->
|
||||||
|
(client.getPlugin(LeakCanary2FlipperPlugin.ID) as? LeakCanary2FlipperPlugin)?.reportLeaks(
|
||||||
|
leaks)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun HeapAnalysis.toLeakList(): List<Leak> {
|
||||||
|
return if (this is HeapAnalysisSuccess) {
|
||||||
|
allLeaks
|
||||||
|
.mapNotNull {
|
||||||
|
if (it.leakTraces.isNotEmpty()) {
|
||||||
|
it.leakTraces[0].toLeak(it.shortDescription)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import leakcanary.OnHeapAnalyzedListener
|
|||||||
import shark.HeapAnalysis
|
import shark.HeapAnalysis
|
||||||
import shark.HeapAnalysisSuccess
|
import shark.HeapAnalysisSuccess
|
||||||
|
|
||||||
|
@Deprecated("Use FlipperLeakEventListener add to LeakCanary.config.eventListeners instead")
|
||||||
class FlipperLeakListener : OnHeapAnalyzedListener {
|
class FlipperLeakListener : OnHeapAnalyzedListener {
|
||||||
private val leaks: MutableList<Leak> = mutableListOf()
|
private val leaks: MutableList<Leak> = mutableListOf()
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.plugins.litho'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
@@ -16,9 +18,15 @@ android {
|
|||||||
targetSdkVersion rootProject.targetSdkVersion
|
targetSdkVersion rootProject.targetSdkVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility rootProject.javaTargetVersion
|
||||||
|
sourceCompatibility rootProject.javaTargetVersion
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly deps.lithoAnnotations
|
compileOnly deps.lithoAnnotations
|
||||||
implementation project(':android')
|
implementation project(':android')
|
||||||
|
implementation deps.kotlinCoroutinesAndroid
|
||||||
implementation deps.lithoCore
|
implementation deps.lithoCore
|
||||||
api deps.lithoEditorCore
|
api deps.lithoEditorCore
|
||||||
api(deps.lithoEditorFlipper) {
|
api(deps.lithoEditorFlipper) {
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho
|
||||||
|
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.core.ConnectionListener
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.*
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEvent
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.FrameworkEventMetadata
|
||||||
|
import com.facebook.litho.ComponentTree
|
||||||
|
import com.facebook.litho.DebugComponent
|
||||||
|
import com.facebook.litho.LithoView
|
||||||
|
import com.facebook.litho.MatrixDrawable
|
||||||
|
import com.facebook.litho.debug.LithoDebugEvent
|
||||||
|
import com.facebook.litho.widget.TextDrawable
|
||||||
|
import com.facebook.rendercore.debug.DebugEvent
|
||||||
|
import com.facebook.rendercore.debug.DebugEventBus
|
||||||
|
import com.facebook.rendercore.debug.DebugEventSubscriber
|
||||||
|
import com.facebook.rendercore.debug.DebugMarkerEvent
|
||||||
|
import com.facebook.rendercore.debug.DebugProcessEvent
|
||||||
|
import com.facebook.rendercore.debug.Duration
|
||||||
|
|
||||||
|
const val LithoTag = "Litho"
|
||||||
|
const val LithoMountableTag = "LithoMountable"
|
||||||
|
|
||||||
|
object UIDebuggerLithoSupport {
|
||||||
|
|
||||||
|
fun enable(context: UIDContext) {
|
||||||
|
addDescriptors(context.descriptorRegister)
|
||||||
|
|
||||||
|
val eventMeta =
|
||||||
|
listOf(
|
||||||
|
// Litho
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
LithoDebugEvent.StateUpdateEnqueued,
|
||||||
|
"Set state was called, this will trigger resolve and then possibly layout and mount"),
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
LithoDebugEvent.RenderRequest,
|
||||||
|
"A request to render the component tree again. It can be requested due to 1) set root 2) state update 3) size change or measurement"),
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
LithoDebugEvent.ComponentTreeResolve,
|
||||||
|
"ComponentTree resolved the hierarchy into a LayoutState, non layout nodes are removed, see attributes for source of execution"),
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
LithoDebugEvent.LayoutCommitted,
|
||||||
|
"A new layout state created (resolved and measured result) being committed; this layout state could get mounted next."),
|
||||||
|
|
||||||
|
// RenderCore
|
||||||
|
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
DebugEvent.RenderTreeMounted, "The mount phase for the entire render tree"),
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
DebugEvent.RenderUnitMounted,
|
||||||
|
"Component was added into the view hierarchy (this doesn't mean it is visible)"),
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
DebugEvent.RenderUnitUpdated,
|
||||||
|
"The properties of a component's content were were rebinded"),
|
||||||
|
FrameworkEventMetadata(
|
||||||
|
DebugEvent.RenderUnitUnmounted, "Component was removed from the view hierarchy"),
|
||||||
|
FrameworkEventMetadata(DebugEvent.RenderUnitOnVisible, "Component became visible"),
|
||||||
|
FrameworkEventMetadata(DebugEvent.RenderUnitOnInvisible, "Component became invisible"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val eventForwarder =
|
||||||
|
object : DebugEventSubscriber(*eventMeta.map { it.type }.toTypedArray()) {
|
||||||
|
override fun onEvent(event: DebugEvent) {
|
||||||
|
val timestamp =
|
||||||
|
when (event) {
|
||||||
|
is DebugMarkerEvent -> event.timestamp
|
||||||
|
is DebugProcessEvent -> event.timestamp
|
||||||
|
}
|
||||||
|
val treeId = event.renderStateId.toIntOrNull() ?: -1
|
||||||
|
|
||||||
|
val globalKey =
|
||||||
|
event.attributeOrNull<String>("key")?.let {
|
||||||
|
DebugComponent.generateGlobalKey(treeId, it).hashCode()
|
||||||
|
}
|
||||||
|
val duration = event.attributeOrNull<Duration>("duration")
|
||||||
|
|
||||||
|
val attributes = mutableMapOf<String, String>()
|
||||||
|
val source =
|
||||||
|
event.attributeOrNull<String>(
|
||||||
|
"source") // todo replace magic strings with DebugEventAttribute.Source once
|
||||||
|
// litho open source is released
|
||||||
|
if (source != null) {
|
||||||
|
attributes["source"] = source
|
||||||
|
}
|
||||||
|
context.addFrameworkEvent(
|
||||||
|
FrameworkEvent(
|
||||||
|
treeId,
|
||||||
|
globalKey ?: treeId,
|
||||||
|
event.type,
|
||||||
|
timestamp,
|
||||||
|
duration?.value,
|
||||||
|
event.threadName,
|
||||||
|
attributes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.connectionListeners.add(
|
||||||
|
object : ConnectionListener {
|
||||||
|
override fun onConnect() {
|
||||||
|
DebugEventBus.subscribe(eventForwarder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDisconnect() {
|
||||||
|
DebugEventBus.unsubscribe(eventForwarder)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
context.frameworkEventMetadata.addAll(eventMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDescriptors(register: DescriptorRegister) {
|
||||||
|
register.register(LithoView::class.java, LithoViewDescriptor)
|
||||||
|
register.register(DebugComponent::class.java, DebugComponentDescriptor(register))
|
||||||
|
register.register(TextDrawable::class.java, TextDrawableDescriptor)
|
||||||
|
register.register(MatrixDrawable::class.java, MatrixDrawableDescriptor)
|
||||||
|
register.register(ComponentTree::class.java, ComponentTreeDescriptor(register))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
|
||||||
|
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.OffsetChild
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.LithoTag
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.util.Immediate
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||||
|
import com.facebook.litho.ComponentTree
|
||||||
|
import com.facebook.litho.DebugComponent
|
||||||
|
|
||||||
|
class ComponentTreeDescriptor(val register: DescriptorRegister) : NodeDescriptor<ComponentTree> {
|
||||||
|
|
||||||
|
private val qualifiedName = ComponentTree::class.qualifiedName ?: ""
|
||||||
|
|
||||||
|
override fun getId(node: ComponentTree): Id = node.id
|
||||||
|
|
||||||
|
override fun getBounds(node: ComponentTree): Bounds {
|
||||||
|
val rootComponent = DebugComponent.getRootInstance(node)
|
||||||
|
return if (rootComponent != null) {
|
||||||
|
Bounds.fromRect(rootComponent.boundsInParentDebugComponent)
|
||||||
|
} else {
|
||||||
|
Bounds(0, 0, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getName(node: ComponentTree): String = "ComponentTree"
|
||||||
|
|
||||||
|
override fun getQualifiedName(node: ComponentTree): String = qualifiedName
|
||||||
|
|
||||||
|
override fun getChildren(node: ComponentTree): List<Any> {
|
||||||
|
val result = mutableListOf<Any>()
|
||||||
|
val debugComponent = DebugComponent.getRootInstance(node)
|
||||||
|
if (debugComponent != null) {
|
||||||
|
result.add(
|
||||||
|
// we want the component tree to take the size and any offset so we reset this one
|
||||||
|
OffsetChild.zero(
|
||||||
|
debugComponent, register.descriptorForClassUnsafe(debugComponent.javaClass)))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActiveChild(node: ComponentTree): Any? = null
|
||||||
|
|
||||||
|
override fun getAttributes(
|
||||||
|
node: ComponentTree
|
||||||
|
): MaybeDeferred<Map<MetadataId, InspectableObject>> {
|
||||||
|
return Immediate(mapOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTags(node: ComponentTree): Set<String> = setOf(LithoTag, "TreeRoot")
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.Id
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.NodeDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.OffsetChild
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.LithoMountableTag
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.LithoTag
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.ComponentDataExtractor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.LayoutPropExtractor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Bounds
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.util.Deferred
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.util.MaybeDeferred
|
||||||
|
import com.facebook.litho.Component
|
||||||
|
import com.facebook.litho.DebugComponent
|
||||||
|
import com.facebook.rendercore.FastMath
|
||||||
|
import com.facebook.yoga.YogaEdge
|
||||||
|
|
||||||
|
class DebugComponentDescriptor(val register: DescriptorRegister) : NodeDescriptor<DebugComponent> {
|
||||||
|
private val NAMESPACE = "DebugComponent"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Debug component is generated on the fly so use the underlying component instance which is
|
||||||
|
* immutable
|
||||||
|
*/
|
||||||
|
override fun getId(node: DebugComponent): Id = node.globalKey.hashCode()
|
||||||
|
|
||||||
|
override fun getName(node: DebugComponent): String = node.component.simpleName
|
||||||
|
|
||||||
|
override fun getQualifiedName(node: com.facebook.litho.DebugComponent): String =
|
||||||
|
node.component::class.qualifiedName ?: ""
|
||||||
|
|
||||||
|
override fun getChildren(node: DebugComponent): List<Any> {
|
||||||
|
val result = mutableListOf<Any>()
|
||||||
|
|
||||||
|
val mountedContent = node.mountedContent
|
||||||
|
|
||||||
|
if (mountedContent == null) {
|
||||||
|
for (child in node.childComponents) {
|
||||||
|
result.add(child)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val layoutNode = node.layoutNode
|
||||||
|
val descriptor: NodeDescriptor<Any> =
|
||||||
|
register.descriptorForClassUnsafe(mountedContent.javaClass)
|
||||||
|
// mountables are always layout nodes
|
||||||
|
if (layoutNode != null) {
|
||||||
|
/**
|
||||||
|
* We need to override the mounted contents offset since the mounted contents android bounds
|
||||||
|
* are w.r.t its native parent but we want it w.r.t to the mountable.
|
||||||
|
*
|
||||||
|
* However padding on a mountable means that the content is inset within the mountables
|
||||||
|
* bounds so we need to adjust for this
|
||||||
|
*/
|
||||||
|
result.add(
|
||||||
|
OffsetChild(
|
||||||
|
child = mountedContent,
|
||||||
|
descriptor = descriptor,
|
||||||
|
x = layoutNode.getLayoutPadding(YogaEdge.LEFT).let { FastMath.round(it) },
|
||||||
|
y = layoutNode.getLayoutPadding(YogaEdge.TOP).let { FastMath.round(it) },
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActiveChild(node: DebugComponent): Any? = null
|
||||||
|
|
||||||
|
private val LayoutId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho Layout")
|
||||||
|
|
||||||
|
private val UserPropsId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho Props")
|
||||||
|
|
||||||
|
private val StateId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Litho State")
|
||||||
|
|
||||||
|
private val MountingDataId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "Mount State")
|
||||||
|
|
||||||
|
private val isMountedAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "mounted")
|
||||||
|
|
||||||
|
private val isVisibleAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "visible")
|
||||||
|
|
||||||
|
override fun getAttributes(
|
||||||
|
node: DebugComponent
|
||||||
|
): MaybeDeferred<Map<MetadataId, InspectableObject>> {
|
||||||
|
return Deferred {
|
||||||
|
val attributeSections = mutableMapOf<MetadataId, InspectableObject>()
|
||||||
|
|
||||||
|
val mountingData = getMountingData(node)
|
||||||
|
attributeSections[MountingDataId] = InspectableObject(mountingData)
|
||||||
|
|
||||||
|
val layoutProps = LayoutPropExtractor.getProps(node)
|
||||||
|
attributeSections[LayoutId] = InspectableObject(layoutProps.toMap())
|
||||||
|
|
||||||
|
if (!node.canResolve()) {
|
||||||
|
val stateContainer = node.stateContainer
|
||||||
|
if (stateContainer != null) {
|
||||||
|
attributeSections[StateId] =
|
||||||
|
ComponentDataExtractor.getState(stateContainer, node.component.simpleName)
|
||||||
|
}
|
||||||
|
|
||||||
|
val props = ComponentDataExtractor.getProps(node.component)
|
||||||
|
|
||||||
|
attributeSections[UserPropsId] = InspectableObject(props.toMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeSections
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBounds(node: DebugComponent): Bounds =
|
||||||
|
Bounds.fromRect(node.boundsInParentDebugComponent)
|
||||||
|
|
||||||
|
override fun getTags(node: DebugComponent): Set<String> {
|
||||||
|
val tags = mutableSetOf(LithoTag)
|
||||||
|
|
||||||
|
if (node.component.mountType != Component.MountType.NONE) {
|
||||||
|
tags.add(LithoMountableTag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSnapshot(node: DebugComponent, bitmap: Bitmap?): Bitmap? = null
|
||||||
|
|
||||||
|
override fun getInlineAttributes(node: DebugComponent): Map<String, String> {
|
||||||
|
val attributes = mutableMapOf<String, String>()
|
||||||
|
val key = node.key
|
||||||
|
val testKey = node.testKey
|
||||||
|
if (key != null && key.trim { it <= ' ' }.length > 0) {
|
||||||
|
attributes["key"] = key
|
||||||
|
}
|
||||||
|
if (testKey != null && testKey.trim { it <= ' ' }.length > 0) {
|
||||||
|
attributes["testKey"] = testKey
|
||||||
|
}
|
||||||
|
return attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMountingData(node: DebugComponent): Map<Id, Inspectable> {
|
||||||
|
|
||||||
|
val lithoView = node.lithoView
|
||||||
|
val mountingData = mutableMapOf<MetadataId, Inspectable>()
|
||||||
|
|
||||||
|
if (lithoView == null) {
|
||||||
|
return mountingData
|
||||||
|
}
|
||||||
|
|
||||||
|
val mountState = lithoView.mountDelegateTarget ?: return mountingData
|
||||||
|
val componentTree = lithoView.componentTree ?: return mountingData
|
||||||
|
|
||||||
|
val component = node.component
|
||||||
|
|
||||||
|
if (component.mountType != Component.MountType.NONE) {
|
||||||
|
val renderUnit = DebugComponent.getRenderUnit(node, componentTree)
|
||||||
|
if (renderUnit != null) {
|
||||||
|
val renderUnitId = renderUnit.id
|
||||||
|
val isMounted = mountState.getContentById(renderUnitId) != null
|
||||||
|
mountingData[isMountedAttributeId] = InspectableValue.Boolean(isMounted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val visibilityOutput = DebugComponent.getVisibilityOutput(node, componentTree)
|
||||||
|
if (visibilityOutput != null) {
|
||||||
|
val isVisible = DebugComponent.isVisible(node, lithoView)
|
||||||
|
mountingData[isVisibleAttributeId] = InspectableValue.Boolean(isVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mountingData
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
|
||||||
|
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||||
|
import com.facebook.litho.LithoView
|
||||||
|
|
||||||
|
object LithoViewDescriptor : ChainedDescriptor<LithoView>() {
|
||||||
|
|
||||||
|
private const val NAMESPACE = "LithoView"
|
||||||
|
private val SectionId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
|
||||||
|
|
||||||
|
override fun onGetName(node: LithoView): String = node.javaClass.simpleName
|
||||||
|
|
||||||
|
override fun onGetChildren(node: LithoView): List<Any> {
|
||||||
|
val componentTree = node.componentTree
|
||||||
|
if (componentTree != null) {
|
||||||
|
return listOf(componentTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
return listOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val IsIncrementalMountEnabledAttributeId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "isIncrementalMountEnabled")
|
||||||
|
|
||||||
|
override fun onGetAttributes(
|
||||||
|
node: LithoView,
|
||||||
|
attributeSections: MutableMap<MetadataId, InspectableObject>
|
||||||
|
) {
|
||||||
|
attributeSections[SectionId] =
|
||||||
|
InspectableObject(
|
||||||
|
mapOf(
|
||||||
|
IsIncrementalMountEnabledAttributeId to
|
||||||
|
InspectableValue.Boolean(node.isIncrementalMountEnabled)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
|
||||||
|
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
|
||||||
|
import com.facebook.litho.MatrixDrawable
|
||||||
|
|
||||||
|
object MatrixDrawableDescriptor : ChainedDescriptor<MatrixDrawable<*>>() {
|
||||||
|
|
||||||
|
override fun onGetChildren(node: MatrixDrawable<*>): List<Any>? {
|
||||||
|
val mountedDrawable = node.mountedDrawable
|
||||||
|
return if (mountedDrawable != null) {
|
||||||
|
listOf(mountedDrawable)
|
||||||
|
} else {
|
||||||
|
listOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetName(node: MatrixDrawable<*>): String = node.javaClass.simpleName
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho.descriptors
|
||||||
|
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.ChainedDescriptor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.Inspectable
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.MetadataId
|
||||||
|
import com.facebook.litho.widget.TextDrawable
|
||||||
|
|
||||||
|
object TextDrawableDescriptor : ChainedDescriptor<TextDrawable>() {
|
||||||
|
|
||||||
|
private const val NAMESPACE = "TextDrawable"
|
||||||
|
private val SectionId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, NAMESPACE)
|
||||||
|
|
||||||
|
override fun onGetName(node: TextDrawable): String = node.javaClass.simpleName
|
||||||
|
|
||||||
|
private val TextAttributeId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "text")
|
||||||
|
|
||||||
|
override fun onGetAttributes(
|
||||||
|
node: TextDrawable,
|
||||||
|
attributeSections: MutableMap<MetadataId, InspectableObject>
|
||||||
|
) {
|
||||||
|
val props =
|
||||||
|
mapOf<Int, Inspectable>(TextAttributeId to InspectableValue.Text(node.text.toString()))
|
||||||
|
|
||||||
|
attributeSections[SectionId] = InspectableObject(props)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho.descriptors.props
|
||||||
|
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Log
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.*
|
||||||
|
import com.facebook.litho.Component
|
||||||
|
import com.facebook.litho.SpecGeneratedComponent
|
||||||
|
import com.facebook.litho.StateContainer
|
||||||
|
import com.facebook.litho.annotations.Prop
|
||||||
|
import com.facebook.litho.annotations.ResType
|
||||||
|
import com.facebook.litho.annotations.State
|
||||||
|
import com.facebook.litho.editor.EditorRegistry
|
||||||
|
import com.facebook.litho.editor.model.EditorArray
|
||||||
|
import com.facebook.litho.editor.model.EditorBool
|
||||||
|
import com.facebook.litho.editor.model.EditorColor
|
||||||
|
import com.facebook.litho.editor.model.EditorNumber
|
||||||
|
import com.facebook.litho.editor.model.EditorPick
|
||||||
|
import com.facebook.litho.editor.model.EditorShape
|
||||||
|
import com.facebook.litho.editor.model.EditorString
|
||||||
|
import com.facebook.litho.editor.model.EditorValue
|
||||||
|
import com.facebook.litho.editor.model.EditorValue.EditorVisitor
|
||||||
|
|
||||||
|
object ComponentDataExtractor {
|
||||||
|
|
||||||
|
fun getProps(component: Component): Map<MetadataId, Inspectable> {
|
||||||
|
val props = mutableMapOf<MetadataId, Inspectable>()
|
||||||
|
|
||||||
|
val isSpecComponent = component is SpecGeneratedComponent
|
||||||
|
|
||||||
|
for (declaredField in component.javaClass.declaredFields) {
|
||||||
|
declaredField.isAccessible = true
|
||||||
|
|
||||||
|
val name = declaredField.name
|
||||||
|
val declaredFieldAnnotation = declaredField.getAnnotation(Prop::class.java)
|
||||||
|
|
||||||
|
// Only expose `@Prop` annotated fields for Spec components
|
||||||
|
if (isSpecComponent && declaredFieldAnnotation == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val prop =
|
||||||
|
try {
|
||||||
|
declaredField[component]
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declaredFieldAnnotation != null) {
|
||||||
|
val resType = declaredFieldAnnotation.resType
|
||||||
|
if (resType == ResType.COLOR) {
|
||||||
|
if (prop != null) {
|
||||||
|
val identifier = getMetadataId(component.simpleName, name)
|
||||||
|
props[identifier] = InspectableValue.Color(Color.fromColor(prop as Int))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if (resType == ResType.DRAWABLE) {
|
||||||
|
val identifier = getMetadataId(component.simpleName, name)
|
||||||
|
props[identifier] = fromDrawable(prop as Drawable?)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val editorValue =
|
||||||
|
try {
|
||||||
|
EditorRegistry.read(declaredField.type, declaredField, component)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(
|
||||||
|
LogTag,
|
||||||
|
"Unable to retrieve prop ${declaredField.name} on type ${component.simpleName}")
|
||||||
|
EditorString("error fetching prop")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorValue != null) {
|
||||||
|
addProp(props, component.simpleName, name, editorValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getState(stateContainer: StateContainer, componentName: String): InspectableObject {
|
||||||
|
|
||||||
|
val stateFields = mutableMapOf<MetadataId, Inspectable>()
|
||||||
|
for (field in stateContainer.javaClass.declaredFields) {
|
||||||
|
field.isAccessible = true
|
||||||
|
val stateAnnotation = field.getAnnotation(State::class.java)
|
||||||
|
val isKStateField = field.name == "states"
|
||||||
|
if (stateAnnotation != null || isKStateField) {
|
||||||
|
val id = getMetadataId(componentName, field.name)
|
||||||
|
val editorValue: EditorValue? = EditorRegistry.read(field.type, field, stateContainer)
|
||||||
|
if (editorValue != null) {
|
||||||
|
stateFields[id] = toInspectable(field.name, editorValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InspectableObject(stateFields)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMetadataId(
|
||||||
|
namespace: String,
|
||||||
|
key: String,
|
||||||
|
mutable: Boolean = false,
|
||||||
|
possibleValues: Set<InspectableValue>? = emptySet()
|
||||||
|
): MetadataId {
|
||||||
|
val metadata = MetadataRegister.get(namespace, key)
|
||||||
|
val identifier =
|
||||||
|
metadata?.id
|
||||||
|
?: MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE, namespace, key, mutable, possibleValues)
|
||||||
|
return identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addProp(
|
||||||
|
props: MutableMap<MetadataId, Inspectable>,
|
||||||
|
namespace: String,
|
||||||
|
name: String,
|
||||||
|
value: EditorValue
|
||||||
|
) {
|
||||||
|
var possibleValues: MutableSet<InspectableValue>? = null
|
||||||
|
if (value is EditorPick) {
|
||||||
|
possibleValues = mutableSetOf()
|
||||||
|
value.values.forEach { possibleValues.add(InspectableValue.Text(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val identifier = getMetadataId(namespace, name, false, possibleValues)
|
||||||
|
props[identifier] = toInspectable(name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toInspectable(name: String, editorValue: EditorValue): Inspectable {
|
||||||
|
return editorValue.`when`(
|
||||||
|
object : EditorVisitor<Inspectable> {
|
||||||
|
override fun isShape(shape: EditorShape): Inspectable {
|
||||||
|
|
||||||
|
val fields = mutableMapOf<MetadataId, Inspectable>()
|
||||||
|
shape.value.entries.forEach { entry ->
|
||||||
|
val value = toInspectable(entry.key, entry.value)
|
||||||
|
|
||||||
|
val shapeEditorValue = entry.value
|
||||||
|
var possibleValues: MutableSet<InspectableValue>? = null
|
||||||
|
if (shapeEditorValue is EditorPick) {
|
||||||
|
possibleValues = mutableSetOf()
|
||||||
|
shapeEditorValue.values.forEach { possibleValues.add(InspectableValue.Text(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val identifier = getMetadataId(name, entry.key, false, possibleValues)
|
||||||
|
fields[identifier] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return InspectableObject(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isArray(array: EditorArray?): Inspectable {
|
||||||
|
val values = array?.value?.map { value -> toInspectable(name, value) }
|
||||||
|
return InspectableArray(values ?: listOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isPick(pick: EditorPick): Inspectable = InspectableValue.Enum(pick.selected)
|
||||||
|
|
||||||
|
override fun isNumber(number: EditorNumber): Inspectable =
|
||||||
|
InspectableValue.Number(number.value)
|
||||||
|
|
||||||
|
override fun isColor(number: EditorColor): Inspectable =
|
||||||
|
InspectableValue.Color(number.value.toInt().let { Color.fromColor(it) })
|
||||||
|
|
||||||
|
override fun isString(string: EditorString): Inspectable =
|
||||||
|
InspectableValue.Text(string.value ?: "")
|
||||||
|
|
||||||
|
override fun isBool(bool: EditorBool): Inspectable = InspectableValue.Boolean(bool.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fromDrawable(d: Drawable?): Inspectable =
|
||||||
|
when (d) {
|
||||||
|
is ColorDrawable -> InspectableValue.Color(Color.fromColor(d.color))
|
||||||
|
else -> InspectableValue.Unknown(d.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,432 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.litho.descriptors.props
|
||||||
|
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.common.enumToInspectableSet
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.*
|
||||||
|
import com.facebook.litho.DebugComponent
|
||||||
|
import com.facebook.yoga.*
|
||||||
|
|
||||||
|
object LayoutPropExtractor {
|
||||||
|
private const val NAMESPACE = "LayoutPropExtractor"
|
||||||
|
|
||||||
|
private var BackgroundId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "background")
|
||||||
|
private var ForegroundId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "foreground")
|
||||||
|
|
||||||
|
private val DirectionId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE,
|
||||||
|
NAMESPACE,
|
||||||
|
"direction",
|
||||||
|
false,
|
||||||
|
enumToInspectableSet<YogaDirection>())
|
||||||
|
private val FlexDirectionId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE,
|
||||||
|
NAMESPACE,
|
||||||
|
"flexDirection",
|
||||||
|
false,
|
||||||
|
enumToInspectableSet<YogaFlexDirection>())
|
||||||
|
private val JustifyContentId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE,
|
||||||
|
NAMESPACE,
|
||||||
|
"justifyContent",
|
||||||
|
false,
|
||||||
|
enumToInspectableSet<YogaJustify>())
|
||||||
|
private val AlignItemsId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE,
|
||||||
|
NAMESPACE,
|
||||||
|
"alignItems",
|
||||||
|
false,
|
||||||
|
enumToInspectableSet<YogaAlign>())
|
||||||
|
private val AlignSelfId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE,
|
||||||
|
NAMESPACE,
|
||||||
|
"alignSelf",
|
||||||
|
false,
|
||||||
|
enumToInspectableSet<YogaAlign>())
|
||||||
|
private val AlignContentId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE,
|
||||||
|
NAMESPACE,
|
||||||
|
"alignContent",
|
||||||
|
false,
|
||||||
|
enumToInspectableSet<YogaAlign>())
|
||||||
|
private val PositionTypeId =
|
||||||
|
MetadataRegister.register(
|
||||||
|
MetadataRegister.TYPE_ATTRIBUTE,
|
||||||
|
NAMESPACE,
|
||||||
|
"positionType",
|
||||||
|
false,
|
||||||
|
enumToInspectableSet<YogaPositionType>())
|
||||||
|
|
||||||
|
private val FlexGrowId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "flexGrow")
|
||||||
|
private val FlexShrinkId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "flexShrink")
|
||||||
|
private val FlexBasisId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "flexBasis")
|
||||||
|
private val WidthId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "width")
|
||||||
|
private val HeightId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "height")
|
||||||
|
private val MinWidthId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minWidth")
|
||||||
|
private val MinHeightId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "minHeight")
|
||||||
|
private val MaxWidthId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxWidth")
|
||||||
|
private val MaxHeightId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "maxHeight")
|
||||||
|
private val AspectRatioId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "aspectRatio")
|
||||||
|
|
||||||
|
private val MarginId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "margin")
|
||||||
|
private val PaddingId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "padding")
|
||||||
|
private val BorderId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "border")
|
||||||
|
private val PositionId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "position")
|
||||||
|
|
||||||
|
private val LeftId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "left")
|
||||||
|
private val TopId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "top")
|
||||||
|
private val RightId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "right")
|
||||||
|
private val BottomId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "bottom")
|
||||||
|
private val StartId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "start")
|
||||||
|
private val EndId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "end")
|
||||||
|
private val HorizontalId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "horizontal")
|
||||||
|
private val VerticalId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "vertical")
|
||||||
|
private val AllId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "all")
|
||||||
|
|
||||||
|
private val HasViewOutputId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "hasViewOutput")
|
||||||
|
private val AlphaId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "alpha")
|
||||||
|
private val ScaleId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "scale")
|
||||||
|
private val RotationId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "rotation")
|
||||||
|
|
||||||
|
private val EmptyId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "")
|
||||||
|
private val NoneId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "none")
|
||||||
|
private val SizeId = MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "size")
|
||||||
|
private val ViewOutputId =
|
||||||
|
MetadataRegister.register(MetadataRegister.TYPE_ATTRIBUTE, NAMESPACE, "viewOutput")
|
||||||
|
|
||||||
|
fun getInspectableBox(
|
||||||
|
left: YogaValue?,
|
||||||
|
top: YogaValue?,
|
||||||
|
right: YogaValue?,
|
||||||
|
bottom: YogaValue?,
|
||||||
|
horizontal: YogaValue?,
|
||||||
|
vertical: YogaValue?,
|
||||||
|
all: YogaValue?,
|
||||||
|
start: YogaValue?,
|
||||||
|
end: YogaValue?
|
||||||
|
): InspectableObject {
|
||||||
|
val props = mutableMapOf<MetadataId, Inspectable>()
|
||||||
|
|
||||||
|
var actualLeft = 0
|
||||||
|
var actualTop = 0
|
||||||
|
var actualRight = 0
|
||||||
|
var actualBottom = 0
|
||||||
|
|
||||||
|
all?.let { yogaValue ->
|
||||||
|
if (yogaValue.unit != YogaUnit.UNDEFINED) {
|
||||||
|
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
|
||||||
|
val intValue = yogaValue.value.toInt()
|
||||||
|
actualLeft = intValue
|
||||||
|
actualTop = intValue
|
||||||
|
actualRight = intValue
|
||||||
|
actualBottom = intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
props[AllId] = InspectableValue.Text(yogaValue.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal?.let { yogaValue ->
|
||||||
|
if (yogaValue.unit != YogaUnit.UNDEFINED) {
|
||||||
|
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
|
||||||
|
val intValue = yogaValue.value.toInt()
|
||||||
|
actualLeft = intValue
|
||||||
|
actualRight = intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
props[HorizontalId] = InspectableValue.Text(yogaValue.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertical?.let { yogaValue ->
|
||||||
|
if (yogaValue.unit != YogaUnit.UNDEFINED) {
|
||||||
|
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
|
||||||
|
val intValue = yogaValue.value.toInt()
|
||||||
|
actualTop = intValue
|
||||||
|
actualBottom = intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
props[VerticalId] = InspectableValue.Text(yogaValue.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
left?.let { yogaValue ->
|
||||||
|
if (yogaValue.unit != YogaUnit.UNDEFINED) {
|
||||||
|
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
|
||||||
|
val intValue = yogaValue.value.toInt()
|
||||||
|
actualLeft = intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
props[LeftId] = InspectableValue.Text(yogaValue.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
right?.let { yogaValue ->
|
||||||
|
if (yogaValue.unit != YogaUnit.UNDEFINED) {
|
||||||
|
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
|
||||||
|
val intValue = yogaValue.value.toInt()
|
||||||
|
actualRight = intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
props[RightId] = InspectableValue.Text(yogaValue.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
top?.let { yogaValue ->
|
||||||
|
if (yogaValue.unit != YogaUnit.UNDEFINED) {
|
||||||
|
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
|
||||||
|
val intValue = yogaValue.value.toInt()
|
||||||
|
actualTop = intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
props[TopId] = InspectableValue.Text(yogaValue.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom?.let { yogaValue ->
|
||||||
|
if (yogaValue.unit != YogaUnit.UNDEFINED) {
|
||||||
|
if (yogaValue.unit == YogaUnit.POINT || yogaValue.unit == YogaUnit.PERCENT) {
|
||||||
|
val intValue = yogaValue.value.toInt()
|
||||||
|
actualBottom = intValue
|
||||||
|
}
|
||||||
|
|
||||||
|
props[BottomId] = InspectableValue.Text(yogaValue.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
props[EmptyId] =
|
||||||
|
InspectableValue.SpaceBox(SpaceBox(actualTop, actualRight, actualBottom, actualLeft))
|
||||||
|
|
||||||
|
return InspectableObject(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInspectableBoxRaw(
|
||||||
|
left: Float?,
|
||||||
|
top: Float?,
|
||||||
|
right: Float?,
|
||||||
|
bottom: Float?,
|
||||||
|
horizontal: Float?,
|
||||||
|
vertical: Float?,
|
||||||
|
all: Float?,
|
||||||
|
start: Float?,
|
||||||
|
end: Float?
|
||||||
|
): InspectableObject {
|
||||||
|
val props = mutableMapOf<MetadataId, Inspectable>()
|
||||||
|
|
||||||
|
var actualLeft = 0
|
||||||
|
var actualTop = 0
|
||||||
|
var actualRight = 0
|
||||||
|
var actualBottom = 0
|
||||||
|
|
||||||
|
all?.let { value ->
|
||||||
|
if (!value.isNaN()) {
|
||||||
|
val intValue = value.toInt()
|
||||||
|
actualLeft = intValue
|
||||||
|
actualTop = intValue
|
||||||
|
actualRight = intValue
|
||||||
|
actualBottom = intValue
|
||||||
|
props[AllId] = InspectableValue.Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
horizontal?.let { value ->
|
||||||
|
if (!value.isNaN()) {
|
||||||
|
val intValue = value.toInt()
|
||||||
|
actualLeft = intValue
|
||||||
|
actualRight = intValue
|
||||||
|
props[HorizontalId] = InspectableValue.Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertical?.let { value ->
|
||||||
|
if (!value.isNaN()) {
|
||||||
|
val intValue = value.toInt()
|
||||||
|
actualTop = intValue
|
||||||
|
actualBottom = intValue
|
||||||
|
props[VerticalId] = InspectableValue.Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
left?.let { value ->
|
||||||
|
if (!value.isNaN()) {
|
||||||
|
val intValue = value.toInt()
|
||||||
|
actualLeft = intValue
|
||||||
|
props[LeftId] = InspectableValue.Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
right?.let { value ->
|
||||||
|
if (!value.isNaN()) {
|
||||||
|
val intValue = value.toInt()
|
||||||
|
actualRight = intValue
|
||||||
|
props[RightId] = InspectableValue.Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
top?.let { value ->
|
||||||
|
if (!value.isNaN()) {
|
||||||
|
val intValue = value.toInt()
|
||||||
|
actualTop = intValue
|
||||||
|
props[TopId] = InspectableValue.Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom?.let { value ->
|
||||||
|
if (!value.isNaN()) {
|
||||||
|
val intValue = value.toInt()
|
||||||
|
actualBottom = intValue
|
||||||
|
props[BottomId] = InspectableValue.Number(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
props[EmptyId] =
|
||||||
|
InspectableValue.SpaceBox(SpaceBox(actualTop, actualRight, actualBottom, actualLeft))
|
||||||
|
|
||||||
|
return InspectableObject(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProps(component: DebugComponent): Map<MetadataId, Inspectable> {
|
||||||
|
val props = mutableMapOf<MetadataId, Inspectable>()
|
||||||
|
|
||||||
|
val layout = component.layoutNode ?: return props
|
||||||
|
|
||||||
|
props[AlignItemsId] = InspectableValue.Enum(layout.alignItems.name)
|
||||||
|
props[AlignSelfId] = InspectableValue.Enum(layout.alignSelf.name)
|
||||||
|
props[AlignContentId] = InspectableValue.Enum(layout.alignContent.name)
|
||||||
|
|
||||||
|
props[AspectRatioId] = InspectableValue.Text(layout.aspectRatio.toString())
|
||||||
|
|
||||||
|
layout.background?.let { drawable -> props[BackgroundId] = fromDrawable(drawable) }
|
||||||
|
|
||||||
|
props[DirectionId] = InspectableValue.Enum(layout.layoutDirection.name)
|
||||||
|
|
||||||
|
props[FlexBasisId] = InspectableValue.Text(layout.flexBasis.toString())
|
||||||
|
props[FlexDirectionId] = InspectableValue.Enum(layout.flexDirection.name)
|
||||||
|
props[FlexGrowId] = InspectableValue.Text(layout.flexGrow.toString())
|
||||||
|
props[FlexShrinkId] = InspectableValue.Text(layout.flexShrink.toString())
|
||||||
|
|
||||||
|
layout.foreground?.let { drawable -> props[ForegroundId] = fromDrawable(drawable) }
|
||||||
|
|
||||||
|
props[JustifyContentId] = InspectableValue.Enum(layout.justifyContent.name)
|
||||||
|
|
||||||
|
props[PositionTypeId] = InspectableValue.Enum(layout.positionType.name)
|
||||||
|
|
||||||
|
val size: MutableMap<MetadataId, Inspectable> = mutableMapOf()
|
||||||
|
size[WidthId] = InspectableValue.Text(layout.width.toString())
|
||||||
|
if (layout.minWidth.unit != YogaUnit.UNDEFINED)
|
||||||
|
size[MinWidthId] = InspectableValue.Text(layout.minWidth.toString())
|
||||||
|
if (layout.maxWidth.unit != YogaUnit.UNDEFINED)
|
||||||
|
size[MaxWidthId] = InspectableValue.Text(layout.maxWidth.toString())
|
||||||
|
size[HeightId] = InspectableValue.Text(layout.height.toString())
|
||||||
|
if (layout.minHeight.unit != YogaUnit.UNDEFINED)
|
||||||
|
size[MinHeightId] = InspectableValue.Text(layout.minHeight.toString())
|
||||||
|
if (layout.maxHeight.unit != YogaUnit.UNDEFINED)
|
||||||
|
size[MaxHeightId] = InspectableValue.Text(layout.maxHeight.toString())
|
||||||
|
|
||||||
|
props[SizeId] = InspectableObject(size)
|
||||||
|
|
||||||
|
props[MarginId] =
|
||||||
|
getInspectableBox(
|
||||||
|
layout.getMargin(YogaEdge.LEFT),
|
||||||
|
layout.getMargin(YogaEdge.TOP),
|
||||||
|
layout.getMargin(YogaEdge.RIGHT),
|
||||||
|
layout.getMargin(YogaEdge.BOTTOM),
|
||||||
|
layout.getMargin(YogaEdge.HORIZONTAL),
|
||||||
|
layout.getMargin(YogaEdge.VERTICAL),
|
||||||
|
layout.getMargin(YogaEdge.ALL),
|
||||||
|
layout.getMargin(YogaEdge.START),
|
||||||
|
layout.getMargin(YogaEdge.END))
|
||||||
|
|
||||||
|
props[PaddingId] =
|
||||||
|
getInspectableBox(
|
||||||
|
layout.getPadding(YogaEdge.LEFT),
|
||||||
|
layout.getPadding(YogaEdge.TOP),
|
||||||
|
layout.getPadding(YogaEdge.RIGHT),
|
||||||
|
layout.getPadding(YogaEdge.BOTTOM),
|
||||||
|
layout.getPadding(YogaEdge.HORIZONTAL),
|
||||||
|
layout.getPadding(YogaEdge.VERTICAL),
|
||||||
|
layout.getPadding(YogaEdge.ALL),
|
||||||
|
layout.getPadding(YogaEdge.START),
|
||||||
|
layout.getPadding(YogaEdge.END))
|
||||||
|
|
||||||
|
props[BorderId] =
|
||||||
|
getInspectableBoxRaw(
|
||||||
|
layout.getBorderWidth(YogaEdge.LEFT),
|
||||||
|
layout.getBorderWidth(YogaEdge.TOP),
|
||||||
|
layout.getBorderWidth(YogaEdge.RIGHT),
|
||||||
|
layout.getBorderWidth(YogaEdge.BOTTOM),
|
||||||
|
layout.getBorderWidth(YogaEdge.HORIZONTAL),
|
||||||
|
layout.getBorderWidth(YogaEdge.VERTICAL),
|
||||||
|
layout.getBorderWidth(YogaEdge.ALL),
|
||||||
|
layout.getBorderWidth(YogaEdge.START),
|
||||||
|
layout.getBorderWidth(YogaEdge.END))
|
||||||
|
|
||||||
|
props[PositionId] =
|
||||||
|
getInspectableBox(
|
||||||
|
layout.getPosition(YogaEdge.LEFT),
|
||||||
|
layout.getPosition(YogaEdge.TOP),
|
||||||
|
layout.getPosition(YogaEdge.RIGHT),
|
||||||
|
layout.getPosition(YogaEdge.BOTTOM),
|
||||||
|
layout.getPosition(YogaEdge.HORIZONTAL),
|
||||||
|
layout.getPosition(YogaEdge.VERTICAL),
|
||||||
|
layout.getPosition(YogaEdge.ALL),
|
||||||
|
layout.getPosition(YogaEdge.START),
|
||||||
|
layout.getPosition(YogaEdge.END))
|
||||||
|
|
||||||
|
val viewOutput: MutableMap<MetadataId, Inspectable> = mutableMapOf()
|
||||||
|
viewOutput[HasViewOutputId] = InspectableValue.Boolean(layout.hasViewOutput())
|
||||||
|
if (layout.hasViewOutput()) {
|
||||||
|
viewOutput[AlphaId] = InspectableValue.Number(layout.alpha)
|
||||||
|
viewOutput[RotationId] = InspectableValue.Number(layout.rotation)
|
||||||
|
viewOutput[ScaleId] = InspectableValue.Number(layout.scale)
|
||||||
|
}
|
||||||
|
props[ViewOutputId] = InspectableObject(viewOutput)
|
||||||
|
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fromDrawable(d: Drawable?): Inspectable =
|
||||||
|
when (d) {
|
||||||
|
is ColorDrawable -> InspectableValue.Color(Color.fromColor(d.color))
|
||||||
|
else -> InspectableValue.Unknown(d.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.descriptors.props.ComponentDataExtractor
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableArray
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableObject
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||||
|
import com.facebook.litho.KStateContainer
|
||||||
|
import junit.framework.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class KStateContainerExtractionTest {
|
||||||
|
@Test
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun testCanExtractKState() {
|
||||||
|
|
||||||
|
// this test ensures that our reflection based extraction doesn't break if the KState class
|
||||||
|
// structure changes
|
||||||
|
val stateContainer = KStateContainer.withNewState(null, "foo")
|
||||||
|
|
||||||
|
val result = ComponentDataExtractor.getState(stateContainer, "Comp1")
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
result,
|
||||||
|
InspectableObject(mapOf(1 to InspectableArray(listOf(InspectableValue.Text("foo"))))))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.plugins.network'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.plugins.retrofit2protobuf'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
|
|
||||||
@@ -19,8 +19,13 @@ android {
|
|||||||
targetSdkVersion rootProject.targetSdkVersion
|
targetSdkVersion rootProject.targetSdkVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility rootProject.javaTargetVersion
|
||||||
|
sourceCompatibility rootProject.javaTargetVersion
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
|
||||||
implementation project(':android')
|
implementation project(':android')
|
||||||
implementation project(':network-plugin')
|
implementation project(':network-plugin')
|
||||||
implementation deps.protobuf
|
implementation deps.protobuf
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.facebook.flipper.sample">
|
package="com.facebook.flipper.sample">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-sdk android:minSdkVersion="15"
|
||||||
|
android:targetSdkVersion="31"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".FlipperSampleApplication"
|
android:name=".FlipperSampleApplication"
|
||||||
@@ -60,7 +62,7 @@
|
|||||||
<data android:scheme="flipper" android:host="list_activity" />
|
<data android:scheme="flipper" android:host="list_activity" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".IncrementActivity"
|
<activity android:name=".ButtonsActivity"
|
||||||
android:exported="true" android:hardwareAccelerated="true">
|
android:exported="true" android:hardwareAccelerated="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@@ -87,6 +89,15 @@
|
|||||||
<data android:scheme="flipper" android:host="fragment_test_activity" />
|
<data android:scheme="flipper" android:host="fragment_test_activity" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".JetpackComposeActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="flipper" android:host="jetpack_compose_activity" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
<activity android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
|
<activity android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
|
||||||
android:exported="true"/>
|
android:exported="true"/>
|
||||||
<activity android:name="com.facebook.flipper.connectivitytest.ConnectionTestActivity"
|
<activity android:name="com.facebook.flipper.connectivitytest.ConnectionTestActivity"
|
||||||
|
|||||||
@@ -5,12 +5,17 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'kotlin-android'
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
namespace 'com.facebook.flipper.sample'
|
||||||
compileSdkVersion rootProject.compileSdkVersion
|
compileSdkVersion rootProject.compileSdkVersion
|
||||||
buildToolsVersion rootProject.buildToolsVersion
|
buildToolsVersion rootProject.buildToolsVersion
|
||||||
ndkVersion rootProject.ndkVersion
|
ndkVersion rootProject.ndkVersion
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
@@ -34,9 +39,25 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
buildFeatures {
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
compose true
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
}
|
||||||
|
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.4.6"
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
pickFirst "**/libcrypto.so"
|
||||||
|
pickFirst "**/libevent-2.1.so"
|
||||||
|
pickFirst "**/libevent_core-2.1.so"
|
||||||
|
pickFirst "**/libevent_extra-2.1.so"
|
||||||
|
pickFirst "**/libflipper.so"
|
||||||
|
pickFirst "**/libssl.so"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,13 +71,27 @@ dependencies {
|
|||||||
implementation deps.lithoWidget
|
implementation deps.lithoWidget
|
||||||
implementation deps.lithoAnnotations
|
implementation deps.lithoAnnotations
|
||||||
implementation deps.lithoFresco
|
implementation deps.lithoFresco
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
|
||||||
annotationProcessor deps.lithoProcessor
|
annotationProcessor deps.lithoProcessor
|
||||||
|
|
||||||
|
// Compose
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
implementation 'androidx.activity:activity-ktx:1.7.2'
|
||||||
|
implementation 'androidx.compose.runtime:runtime:1.4.3'
|
||||||
|
implementation 'androidx.activity:activity-compose:1.7.2'
|
||||||
|
implementation 'androidx.compose.ui:ui:1.4.3'
|
||||||
|
implementation 'androidx.compose.material3:material3:1.1.1'
|
||||||
|
implementation 'androidx.compose.ui:ui-tooling:1.4.3'
|
||||||
|
implementation 'androidx.compose.ui:ui-tooling-preview:1.4.3'
|
||||||
|
|
||||||
// Third-party
|
// Third-party
|
||||||
implementation deps.soloader
|
implementation deps.soloader
|
||||||
implementation deps.okhttp3
|
implementation deps.okhttp3
|
||||||
implementation deps.fresco
|
implementation deps.fresco
|
||||||
|
implementation deps.frescoUiCommon
|
||||||
|
implementation deps.frescoVito
|
||||||
|
implementation deps.frescoVitoLitho
|
||||||
|
implementation deps.inferAnnotations
|
||||||
|
debugImplementation deps.flipperFrescoPlugin
|
||||||
|
|
||||||
// Integration test
|
// Integration test
|
||||||
androidTestImplementation deps.testCore
|
androidTestImplementation deps.testCore
|
||||||
@@ -69,8 +104,8 @@ dependencies {
|
|||||||
testImplementation deps.junit
|
testImplementation deps.junit
|
||||||
|
|
||||||
debugImplementation project(':android')
|
debugImplementation project(':android')
|
||||||
debugImplementation project(':fresco-plugin')
|
|
||||||
debugImplementation project(':network-plugin')
|
debugImplementation project(':network-plugin')
|
||||||
debugImplementation project(':litho-plugin')
|
debugImplementation project(':litho-plugin')
|
||||||
|
debugImplementation project(':jetpack-compose-plugin')
|
||||||
releaseImplementation project(':noop')
|
releaseImplementation project(':noop')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,17 @@ import com.facebook.flipper.plugins.example.ExampleFlipperPlugin;
|
|||||||
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
|
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
|
||||||
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
|
||||||
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
|
||||||
|
import com.facebook.flipper.plugins.jetpackcompose.UIDebuggerComposeSupport;
|
||||||
import com.facebook.flipper.plugins.navigation.NavigationFlipperPlugin;
|
import com.facebook.flipper.plugins.navigation.NavigationFlipperPlugin;
|
||||||
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
|
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
|
||||||
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
|
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
|
||||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
|
||||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin.SharedPreferencesDescriptor;
|
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin.SharedPreferencesDescriptor;
|
||||||
import com.facebook.flipper.plugins.uidebugger.UIDebuggerFlipperPlugin;
|
import com.facebook.flipper.plugins.uidebugger.UIDebuggerFlipperPlugin;
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.core.UIDContext;
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister;
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.litho.UIDebuggerLithoSupport;
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.observers.TreeObserverFactory;
|
||||||
import com.facebook.litho.config.ComponentsConfiguration;
|
import com.facebook.litho.config.ComponentsConfiguration;
|
||||||
import com.facebook.litho.editor.flipper.LithoFlipperDescriptors;
|
import com.facebook.litho.editor.flipper.LithoFlipperDescriptors;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -29,11 +34,11 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
public final class FlipperInitializer {
|
public final class FlipperInitializer {
|
||||||
public interface IntializationResult {
|
public interface InitializationResult {
|
||||||
OkHttpClient getOkHttpClient();
|
OkHttpClient getOkHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntializationResult initFlipperPlugins(Context context, FlipperClient client) {
|
public static InitializationResult initFlipperPlugins(Context context, FlipperClient client) {
|
||||||
final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
|
final DescriptorMapping descriptorMapping = DescriptorMapping.withDefaults();
|
||||||
|
|
||||||
final NetworkFlipperPlugin networkPlugin = new NetworkFlipperPlugin();
|
final NetworkFlipperPlugin networkPlugin = new NetworkFlipperPlugin();
|
||||||
@@ -56,7 +61,14 @@ public final class FlipperInitializer {
|
|||||||
client.addPlugin(CrashReporterPlugin.getInstance());
|
client.addPlugin(CrashReporterPlugin.getInstance());
|
||||||
client.addPlugin(new DatabasesFlipperPlugin(context));
|
client.addPlugin(new DatabasesFlipperPlugin(context));
|
||||||
client.addPlugin(NavigationFlipperPlugin.getInstance());
|
client.addPlugin(NavigationFlipperPlugin.getInstance());
|
||||||
client.addPlugin(new UIDebuggerFlipperPlugin((Application) context));
|
|
||||||
|
DescriptorRegister descriptorRegister = DescriptorRegister.Companion.withDefaults();
|
||||||
|
TreeObserverFactory treeObserverFactory = TreeObserverFactory.Companion.withDefaults();
|
||||||
|
UIDContext uidContext = UIDContext.Companion.create((Application) context);
|
||||||
|
UIDebuggerLithoSupport.INSTANCE.enable(uidContext);
|
||||||
|
UIDebuggerComposeSupport.INSTANCE.enable(uidContext);
|
||||||
|
|
||||||
|
client.addPlugin(new UIDebuggerFlipperPlugin(uidContext));
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
final OkHttpClient okHttpClient =
|
final OkHttpClient okHttpClient =
|
||||||
@@ -67,7 +79,7 @@ public final class FlipperInitializer {
|
|||||||
.writeTimeout(10, TimeUnit.MINUTES)
|
.writeTimeout(10, TimeUnit.MINUTES)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new IntializationResult() {
|
return new InitializationResult() {
|
||||||
@Override
|
@Override
|
||||||
public OkHttpClient getOkHttpClient() {
|
public OkHttpClient getOkHttpClient() {
|
||||||
return okHttpClient;
|
return okHttpClient;
|
||||||
|
|||||||
@@ -7,85 +7,69 @@
|
|||||||
|
|
||||||
package com.facebook.flipper.sample;
|
package com.facebook.flipper.sample;
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.view.animation.LinearInterpolator;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
public class AnimationsActivity extends Activity {
|
public class AnimationsActivity extends Activity {
|
||||||
|
|
||||||
Button btnBlink, btnRotate, btnMove, btnBounce, btnSequential;
|
Button btnBlink, btnRotate, btnMove, btnBounce, btnSequential, btnValueAnimator;
|
||||||
Animation animBlink, animRotate, animMove, animBounce, animSequential;
|
Animation animBlink, animRotate, animMove, animBounce, animSequential;
|
||||||
TextView txtBlink, txtRotate, txtMove, txtBounce, txtSeq;
|
TextView txtBlink, txtRotate, txtMove, txtBounce, txtSeq, txtValueAnimator;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_animations);
|
setContentView(R.layout.activity_animations);
|
||||||
|
|
||||||
btnBlink = (Button) findViewById(R.id.btnBlink);
|
btnBlink = findViewById(R.id.btnBlink);
|
||||||
btnRotate = (Button) findViewById(R.id.btnRotate);
|
btnBlink.setTranslationX(500);
|
||||||
btnMove = (Button) findViewById(R.id.btnMove);
|
btnRotate = findViewById(R.id.btnRotate);
|
||||||
btnBounce = (Button) findViewById(R.id.btnBounce);
|
btnMove = findViewById(R.id.btnMove);
|
||||||
btnSequential = (Button) findViewById(R.id.btnSequential);
|
btnBounce = findViewById(R.id.btnBounce);
|
||||||
txtBlink = (TextView) findViewById(R.id.txt_blink);
|
btnSequential = findViewById(R.id.btnSequential);
|
||||||
txtRotate = (TextView) findViewById(R.id.txt_rotate);
|
btnValueAnimator = findViewById(R.id.btnValueAnimator);
|
||||||
txtMove = (TextView) findViewById(R.id.txt_move);
|
txtBlink = findViewById(R.id.txt_blink);
|
||||||
txtBounce = (TextView) findViewById(R.id.txt_bounce);
|
txtRotate = findViewById(R.id.txt_rotate);
|
||||||
txtSeq = (TextView) findViewById(R.id.txt_seq);
|
txtMove = findViewById(R.id.txt_move);
|
||||||
|
txtBounce = findViewById(R.id.txt_bounce);
|
||||||
|
txtSeq = findViewById(R.id.txt_seq);
|
||||||
|
txtValueAnimator = findViewById(R.id.txtValueAnimator);
|
||||||
|
|
||||||
|
btnValueAnimator.setOnClickListener(
|
||||||
|
b -> {
|
||||||
|
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 300);
|
||||||
|
valueAnimator.addUpdateListener(
|
||||||
|
animator -> txtValueAnimator.setTranslationX((Float) animator.getAnimatedValue()));
|
||||||
|
valueAnimator.setInterpolator(new LinearInterpolator());
|
||||||
|
valueAnimator.setDuration(10000);
|
||||||
|
valueAnimator.start();
|
||||||
|
});
|
||||||
|
|
||||||
animBlink = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.blink);
|
|
||||||
// blink
|
|
||||||
btnBlink.setOnClickListener(
|
btnBlink.setOnClickListener(
|
||||||
new View.OnClickListener() {
|
v -> {
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
txtBlink.setVisibility(View.VISIBLE);
|
txtBlink.setVisibility(View.VISIBLE);
|
||||||
txtBlink.startAnimation(animBlink);
|
txtBlink.startAnimation(animBlink);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
animRotate = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.rotate);
|
animRotate = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.rotate);
|
||||||
|
|
||||||
// Rotate
|
btnRotate.setOnClickListener(v -> txtRotate.startAnimation(animRotate));
|
||||||
btnRotate.setOnClickListener(
|
|
||||||
new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
txtRotate.startAnimation(animRotate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
animMove = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.move);
|
animMove = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.move);
|
||||||
// Move
|
|
||||||
btnMove.setOnClickListener(
|
btnMove.setOnClickListener(v -> txtMove.startAnimation(animMove));
|
||||||
new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
txtMove.startAnimation(animMove);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
animBounce = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.bounce);
|
animBounce = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.bounce);
|
||||||
// Slide Down
|
|
||||||
btnBounce.setOnClickListener(
|
|
||||||
new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
txtBounce.startAnimation(animBounce);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
animSequential = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.sequential);
|
|
||||||
// Sequential
|
|
||||||
btnSequential.setOnClickListener(
|
|
||||||
new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
|
|
||||||
txtSeq.startAnimation(animSequential);
|
btnBounce.setOnClickListener(v -> txtBounce.startAnimation(animBounce));
|
||||||
}
|
animSequential = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.sequential);
|
||||||
});
|
|
||||||
|
btnSequential.setOnClickListener(v -> txtSeq.startAnimation(animSequential));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.sample;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.FragmentActivity;
|
||||||
|
|
||||||
|
public class ButtonsActivity extends FragmentActivity {
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
TextView text;
|
||||||
|
Button button;
|
||||||
|
Button dialogOld;
|
||||||
|
Button dialogFragment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_buttons);
|
||||||
|
|
||||||
|
text = findViewById(R.id.count);
|
||||||
|
|
||||||
|
button = findViewById(com.facebook.flipper.sample.R.id.btn_inc);
|
||||||
|
dialogOld = findViewById(R.id.dialog_old_api);
|
||||||
|
dialogFragment = findViewById(R.id.dialog_fragment);
|
||||||
|
button.setOnClickListener(view -> ButtonsActivity.this.text.setText(String.valueOf(++count)));
|
||||||
|
|
||||||
|
dialogFragment.setOnClickListener(
|
||||||
|
btn -> {
|
||||||
|
TestDialogFragment startGameDialogFragment = new TestDialogFragment();
|
||||||
|
startGameDialogFragment.show(getSupportFragmentManager(), "dialog");
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogOld.setOnClickListener(
|
||||||
|
btn -> {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Old Dialog")
|
||||||
|
.setMessage("This is an old dialog")
|
||||||
|
.setNegativeButton(android.R.string.no, null)
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import com.facebook.drawee.backends.pipeline.Fresco;
|
|||||||
import com.facebook.flipper.android.AndroidFlipperClient;
|
import com.facebook.flipper.android.AndroidFlipperClient;
|
||||||
import com.facebook.flipper.core.FlipperClient;
|
import com.facebook.flipper.core.FlipperClient;
|
||||||
import com.facebook.flipper.sample.network.NetworkClient;
|
import com.facebook.flipper.sample.network.NetworkClient;
|
||||||
|
import com.facebook.fresco.vito.init.FrescoVito;
|
||||||
import com.facebook.soloader.SoLoader;
|
import com.facebook.soloader.SoLoader;
|
||||||
|
|
||||||
public class FlipperSampleApplication extends Application {
|
public class FlipperSampleApplication extends Application {
|
||||||
@@ -22,9 +23,12 @@ public class FlipperSampleApplication extends Application {
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
SoLoader.init(this, false);
|
SoLoader.init(this, false);
|
||||||
Fresco.initialize(this);
|
Fresco.initialize(this);
|
||||||
|
FrescoVito.initialize();
|
||||||
|
|
||||||
final FlipperClient client = AndroidFlipperClient.getInstance(this);
|
final FlipperClient client = AndroidFlipperClient.getInstance(this);
|
||||||
final FlipperInitializer.IntializationResult initializationResult =
|
assert client != null;
|
||||||
|
|
||||||
|
final FlipperInitializer.InitializationResult initializationResult =
|
||||||
FlipperInitializer.initFlipperPlugins(this, client);
|
FlipperInitializer.initFlipperPlugins(this, client);
|
||||||
|
|
||||||
NetworkClient.getInstance().setOkHttpClient(initializationResult.getOkHttpClient());
|
NetworkClient.getInstance().setOkHttpClient(initializationResult.getOkHttpClient());
|
||||||
|
|||||||
@@ -15,39 +15,39 @@ import android.widget.TextView;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
public class FragmentTestFragment extends Fragment {
|
public class FragmentTestFragment extends Fragment {
|
||||||
View mView;
|
View view;
|
||||||
int mTicker;
|
int ticker;
|
||||||
|
|
||||||
public FragmentTestFragment() {
|
public FragmentTestFragment() {
|
||||||
mTicker = 0;
|
ticker = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTicker() {
|
private void updateTicker() {
|
||||||
try {
|
try {
|
||||||
ViewGroup viewGroup = (ViewGroup) mView;
|
ViewGroup viewGroup = (ViewGroup) view;
|
||||||
TextView textView = (TextView) viewGroup.getChildAt(1);
|
TextView textView = (TextView) viewGroup.getChildAt(1);
|
||||||
String text = String.valueOf(mTicker++);
|
String text = String.valueOf(ticker++);
|
||||||
|
|
||||||
textView.setText(text);
|
textView.setText(text);
|
||||||
} finally {
|
} finally {
|
||||||
// 100% guarantee that this always happens, even if
|
// 100% guarantee that this always happens, even if
|
||||||
// your update method throws an exception
|
// your update method throws an exception
|
||||||
mView.postDelayed(
|
view.postDelayed(
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
updateTicker();
|
updateTicker();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
10000);
|
1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(
|
public View onCreateView(
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
mView = inflater.inflate(R.layout.fragment_test, container, false);
|
view = inflater.inflate(R.layout.fragment_test, container, false);
|
||||||
mView.postDelayed(
|
view.postDelayed(
|
||||||
new Runnable() {
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
@@ -56,6 +56,6 @@ public class FragmentTestFragment extends Fragment {
|
|||||||
},
|
},
|
||||||
1000);
|
1000);
|
||||||
|
|
||||||
return mView;
|
return view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.flipper.sample;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
public class IncrementActivity extends Activity {
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
TextView text;
|
|
||||||
Button button;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_increment);
|
|
||||||
|
|
||||||
text = (TextView) findViewById(R.id.count);
|
|
||||||
|
|
||||||
button = (Button) findViewById(com.facebook.flipper.sample.R.id.btn_inc);
|
|
||||||
button.setOnClickListener(
|
|
||||||
new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
IncrementActivity.this.text.setText(String.valueOf(++count));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.facebook.flipper.sample
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessageCard(name: String) {
|
||||||
|
Row(modifier = Modifier.padding(all = 8.dp)) { Text(text = "Hello $name!") }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun PreviewMessageCard() {
|
||||||
|
MessageCard("Android")
|
||||||
|
}
|
||||||
|
|
||||||
|
class JetpackComposeActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContent { MessageCard("Flipper") }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ public class ListActivity extends Activity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_list);
|
setContentView(R.layout.activity_list);
|
||||||
|
|
||||||
listView = (ListView) findViewById(R.id.list);
|
listView = findViewById(R.id.list);
|
||||||
|
|
||||||
list = new ArrayList<>();
|
list = new ArrayList<>();
|
||||||
list.add("Apple");
|
list.add("Apple");
|
||||||
@@ -33,7 +33,7 @@ public class ListActivity extends Activity {
|
|||||||
list.add("Orange");
|
list.add("Orange");
|
||||||
list.add("Lychee");
|
list.add("Lychee");
|
||||||
list.add("Guava");
|
list.add("Guava");
|
||||||
list.add("Peech");
|
list.add("Peach");
|
||||||
list.add("Melon");
|
list.add("Melon");
|
||||||
list.add("Watermelon");
|
list.add("Watermelon");
|
||||||
list.add("Papaya");
|
list.add("Papaya");
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
final FlipperClient client = AndroidFlipperClient.getInstanceIfInitialized();
|
final FlipperClient client = AndroidFlipperClient.getInstanceIfInitialized();
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
final ExampleFlipperPlugin samplePlugin = client.getPluginByClass(ExampleFlipperPlugin.class);
|
final ExampleFlipperPlugin samplePlugin = client.getPluginByClass(ExampleFlipperPlugin.class);
|
||||||
|
if (samplePlugin != null) {
|
||||||
samplePlugin.setActivity(this);
|
samplePlugin.setActivity(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
package com.facebook.flipper.sample;
|
package com.facebook.flipper.sample;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
import android.net.Uri;
|
||||||
import com.facebook.drawee.interfaces.DraweeController;
|
|
||||||
import com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity;
|
import com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity;
|
||||||
import com.facebook.flipper.sample.network.NetworkClient;
|
import com.facebook.flipper.sample.network.NetworkClient;
|
||||||
|
import com.facebook.fresco.vito.litho.FrescoVitoImage2;
|
||||||
import com.facebook.litho.ClickEvent;
|
import com.facebook.litho.ClickEvent;
|
||||||
import com.facebook.litho.Column;
|
import com.facebook.litho.Column;
|
||||||
import com.facebook.litho.Component;
|
import com.facebook.litho.Component;
|
||||||
@@ -22,7 +22,6 @@ import com.facebook.litho.annotations.OnCreateLayout;
|
|||||||
import com.facebook.litho.annotations.OnEvent;
|
import com.facebook.litho.annotations.OnEvent;
|
||||||
import com.facebook.litho.annotations.OnUpdateState;
|
import com.facebook.litho.annotations.OnUpdateState;
|
||||||
import com.facebook.litho.annotations.State;
|
import com.facebook.litho.annotations.State;
|
||||||
import com.facebook.litho.fresco.FrescoImage;
|
|
||||||
import com.facebook.litho.widget.Text;
|
import com.facebook.litho.widget.Text;
|
||||||
import com.facebook.litho.widget.VerticalScroll;
|
import com.facebook.litho.widget.VerticalScroll;
|
||||||
import com.facebook.yoga.YogaEdge;
|
import com.facebook.yoga.YogaEdge;
|
||||||
@@ -32,9 +31,6 @@ public class RootComponentSpec {
|
|||||||
|
|
||||||
@OnCreateLayout
|
@OnCreateLayout
|
||||||
static Component onCreateLayout(final ComponentContext c, @State boolean displayImage) {
|
static Component onCreateLayout(final ComponentContext c, @State boolean displayImage) {
|
||||||
final DraweeController controller =
|
|
||||||
Fresco.newDraweeControllerBuilder().setUri("https://fbflipper.com/img/icon.png").build();
|
|
||||||
|
|
||||||
Column col =
|
Column col =
|
||||||
Column.create(c)
|
Column.create(c)
|
||||||
.child(
|
.child(
|
||||||
@@ -109,24 +105,33 @@ public class RootComponentSpec {
|
|||||||
.clickHandler(RootComponent.openAnimationsActivity(c)))
|
.clickHandler(RootComponent.openAnimationsActivity(c)))
|
||||||
.child(
|
.child(
|
||||||
Text.create(c)
|
Text.create(c)
|
||||||
.text("Navigate to increment activity")
|
.text("Navigate to buttons activity")
|
||||||
.key("11")
|
.key("11")
|
||||||
.marginDip(YogaEdge.ALL, 10)
|
.marginDip(YogaEdge.ALL, 10)
|
||||||
.textSizeSp(20)
|
.textSizeSp(20)
|
||||||
.clickHandler(RootComponent.openIncrementActivity(c)))
|
.clickHandler(RootComponent.openIncrementActivity(c)))
|
||||||
.child(
|
.child(
|
||||||
Text.create(c)
|
Text.create(c)
|
||||||
.text("Crash this app")
|
.text("Navigate to Jetpack Compose activity")
|
||||||
.key("12")
|
.key("12")
|
||||||
.marginDip(YogaEdge.ALL, 10)
|
.marginDip(YogaEdge.ALL, 10)
|
||||||
.textSizeSp(20)
|
.textSizeSp(20)
|
||||||
|
.clickHandler(RootComponent.openJetpackComposeActivity(c)))
|
||||||
|
.child(
|
||||||
|
Text.create(c)
|
||||||
|
.text("Crash this app")
|
||||||
|
.key("13")
|
||||||
|
.marginDip(YogaEdge.ALL, 10)
|
||||||
|
.textSizeSp(20)
|
||||||
.clickHandler(RootComponent.triggerCrash(c)))
|
.clickHandler(RootComponent.triggerCrash(c)))
|
||||||
.child(
|
.child(
|
||||||
FrescoImage.create(c)
|
displayImage
|
||||||
.controller(controller)
|
? FrescoVitoImage2.create(c)
|
||||||
|
.uri(Uri.parse("https://fbflipper.com/img/icon.png"))
|
||||||
.marginDip(YogaEdge.ALL, 10)
|
.marginDip(YogaEdge.ALL, 10)
|
||||||
.widthDip(150)
|
.widthDip(150)
|
||||||
.heightDip(150))
|
.heightDip(150)
|
||||||
|
: null)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return VerticalScroll.create(c).childComponent(col).build();
|
return VerticalScroll.create(c).childComponent(col).build();
|
||||||
@@ -200,7 +205,13 @@ public class RootComponentSpec {
|
|||||||
|
|
||||||
@OnEvent(ClickEvent.class)
|
@OnEvent(ClickEvent.class)
|
||||||
static void openIncrementActivity(final ComponentContext c) {
|
static void openIncrementActivity(final ComponentContext c) {
|
||||||
final Intent intent = new Intent(c.getAndroidContext(), IncrementActivity.class);
|
final Intent intent = new Intent(c.getAndroidContext(), ButtonsActivity.class);
|
||||||
|
c.getAndroidContext().startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnEvent(ClickEvent.class)
|
||||||
|
static void openJetpackComposeActivity(final ComponentContext c) {
|
||||||
|
final Intent intent = new Intent(c.getAndroidContext(), JetpackComposeActivity.class);
|
||||||
c.getAndroidContext().startActivity(intent);
|
c.getAndroidContext().startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.sample;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
|
public class TestDialogFragment extends DialogFragment {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
// Use the Builder class for convenient dialog construction
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
builder.setMessage("This is a dialog fragment").setPositiveButton("Yes", (dialog, id) -> {});
|
||||||
|
|
||||||
|
return builder.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -103,6 +104,30 @@
|
|||||||
android:id="@+id/txt_rotate"
|
android:id="@+id/txt_rotate"
|
||||||
/>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnValueAnimator"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Value animator"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtValueAnimator"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Value Animator"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
42
android/sample/src/main/res/layout/activity_buttons.xml
Normal file
42
android/sample/src/main/res/layout/activity_buttons.xml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ButtonsActivity"
|
||||||
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/count"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_inc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/darker_gray"
|
||||||
|
android:text="Inc"
|
||||||
|
android:textColor="@android:color/holo_blue_dark"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/dialog_old_api"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/darker_gray"
|
||||||
|
android:text="Dialog Old Api"
|
||||||
|
android:textColor="@android:color/holo_blue_dark"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/dialog_fragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@android:color/darker_gray"
|
||||||
|
android:text="Dialog Fragment"
|
||||||
|
android:textColor="@android:color/holo_blue_dark"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
tools:context=".IncrementActivity"
|
|
||||||
android:orientation="vertical"
|
|
||||||
>
|
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/count"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="0" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btn_inc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@android:color/darker_gray"
|
|
||||||
android:text="Inc"
|
|
||||||
android:textColor="@android:color/holo_blue_dark"
|
|
||||||
/>
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -13,11 +13,11 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
public final class FlipperInitializer {
|
public final class FlipperInitializer {
|
||||||
public interface IntializationResult {
|
public interface InitializationResult {
|
||||||
OkHttpClient getOkHttpClient();
|
OkHttpClient getOkHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntializationResult initFlipperPlugins(Context context, FlipperClient client) {
|
public static InitializationResult initFlipperPlugins(Context context, FlipperClient client) {
|
||||||
final OkHttpClient okHttpClient =
|
final OkHttpClient okHttpClient =
|
||||||
new OkHttpClient.Builder()
|
new OkHttpClient.Builder()
|
||||||
.connectTimeout(60, TimeUnit.SECONDS)
|
.connectTimeout(60, TimeUnit.SECONDS)
|
||||||
@@ -25,7 +25,7 @@ public final class FlipperInitializer {
|
|||||||
.writeTimeout(10, TimeUnit.MINUTES)
|
.writeTimeout(10, TimeUnit.MINUTES)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return new IntializationResult() {
|
return new InitializationResult() {
|
||||||
@Override
|
@Override
|
||||||
public OkHttpClient getOkHttpClient() {
|
public OkHttpClient getOkHttpClient() {
|
||||||
return okHttpClient;
|
return okHttpClient;
|
||||||
|
|||||||
@@ -44,8 +44,15 @@ void handleException(const std::exception& e) {
|
|||||||
__android_log_write(ANDROID_LOG_ERROR, "FLIPPER", message.c_str());
|
__android_log_write(ANDROID_LOG_ERROR, "FLIPPER", message.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<facebook::flipper::Scheduler> sonarScheduler;
|
std::unique_ptr<facebook::flipper::Scheduler>& sonarScheduler() {
|
||||||
std::unique_ptr<facebook::flipper::Scheduler> connectionScheduler;
|
static std::unique_ptr<facebook::flipper::Scheduler> scheduler;
|
||||||
|
return scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<facebook::flipper::Scheduler>& connectionScheduler() {
|
||||||
|
static std::unique_ptr<facebook::flipper::Scheduler> scheduler;
|
||||||
|
return scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
class JEventBase : public jni::HybridClass<JEventBase> {
|
class JEventBase : public jni::HybridClass<JEventBase> {
|
||||||
public:
|
public:
|
||||||
@@ -269,9 +276,9 @@ class JFlipperWebSocket : public facebook::flipper::FlipperSocket {
|
|||||||
messageHandler_ = std::move(messageHandler);
|
messageHandler_ = std::move(messageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool connect(FlipperConnectionManager* manager) override {
|
virtual void connect(FlipperConnectionManager* manager) override {
|
||||||
if (socket_ != nullptr) {
|
if (socket_ != nullptr) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string connectionURL = endpoint_.secure ? "wss://" : "ws://";
|
std::string connectionURL = endpoint_.secure ? "wss://" : "ws://";
|
||||||
@@ -290,38 +297,9 @@ class JFlipperWebSocket : public facebook::flipper::FlipperSocket {
|
|||||||
|
|
||||||
auto secure = endpoint_.secure;
|
auto secure = endpoint_.secure;
|
||||||
|
|
||||||
std::promise<bool> promise;
|
|
||||||
auto connected = promise.get_future();
|
|
||||||
|
|
||||||
connecting_ = true;
|
|
||||||
|
|
||||||
socket_ = make_global(JFlipperSocketImpl::create(connectionURL));
|
socket_ = make_global(JFlipperSocketImpl::create(connectionURL));
|
||||||
socket_->setEventHandler(JFlipperSocketEventHandlerImpl::newObjectCxxArgs(
|
socket_->setEventHandler(JFlipperSocketEventHandlerImpl::newObjectCxxArgs(
|
||||||
[this, &promise, eventHandler = eventHandler_](SocketEvent event) {
|
[eventHandler = eventHandler_](SocketEvent event) {
|
||||||
/**
|
|
||||||
Only fulfill the promise the first time the event handler is used.
|
|
||||||
If the open event is received, then set the promise value to true.
|
|
||||||
For any other event, consider a failure and set to false.
|
|
||||||
*/
|
|
||||||
if (this->connecting_) {
|
|
||||||
this->connecting_ = false;
|
|
||||||
if (event == SocketEvent::OPEN) {
|
|
||||||
promise.set_value(true);
|
|
||||||
} else if (event == SocketEvent::SSL_ERROR) {
|
|
||||||
try {
|
|
||||||
promise.set_exception(
|
|
||||||
std::make_exception_ptr(folly::AsyncSocketException(
|
|
||||||
folly::AsyncSocketException::SSL_ERROR,
|
|
||||||
"SSL handshake failed")));
|
|
||||||
} catch (...) {
|
|
||||||
// set_exception() may throw an exception
|
|
||||||
// In that case, just set the value to false.
|
|
||||||
promise.set_value(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
promise.set_value(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventHandler(event);
|
eventHandler(event);
|
||||||
},
|
},
|
||||||
[messageHandler = messageHandler_](const std::string& message) {
|
[messageHandler = messageHandler_](const std::string& message) {
|
||||||
@@ -341,14 +319,6 @@ class JFlipperWebSocket : public facebook::flipper::FlipperSocket {
|
|||||||
return JFlipperObject::create(std::move(object_));
|
return JFlipperObject::create(std::move(object_));
|
||||||
}));
|
}));
|
||||||
socket_->connect();
|
socket_->connect();
|
||||||
|
|
||||||
auto state = connected.wait_for(std::chrono::seconds(10));
|
|
||||||
if (state == std::future_status::ready) {
|
|
||||||
return connected.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void disconnect() override {
|
virtual void disconnect() override {
|
||||||
@@ -373,6 +343,13 @@ class JFlipperWebSocket : public facebook::flipper::FlipperSocket {
|
|||||||
if (socket_ == nullptr) {
|
if (socket_ == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Ensure the payload size is valid before sending.
|
||||||
|
// The maximum allowed size for a message payload is 2^53 - 1. But that is
|
||||||
|
// for the entire message, including any additional metadata.
|
||||||
|
if (message.length() > pow(2, 53) - 1) {
|
||||||
|
throw std::length_error("Payload is too big to send");
|
||||||
|
}
|
||||||
|
|
||||||
socket_->send(message);
|
socket_->send(message);
|
||||||
completion();
|
completion();
|
||||||
}
|
}
|
||||||
@@ -408,7 +385,6 @@ class JFlipperWebSocket : public facebook::flipper::FlipperSocket {
|
|||||||
facebook::flipper::SocketMessageHandler messageHandler_;
|
facebook::flipper::SocketMessageHandler messageHandler_;
|
||||||
|
|
||||||
jni::global_ref<JFlipperSocketImpl> socket_;
|
jni::global_ref<JFlipperSocketImpl> socket_;
|
||||||
bool connecting_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class JFlipperSocketProvider : public facebook::flipper::FlipperSocketProvider {
|
class JFlipperSocketProvider : public facebook::flipper::FlipperSocketProvider {
|
||||||
@@ -767,6 +743,7 @@ class JFlipperClient : public jni::HybridClass<JFlipperClient> {
|
|||||||
makeNativeMethod("getInstance", JFlipperClient::getInstance),
|
makeNativeMethod("getInstance", JFlipperClient::getInstance),
|
||||||
makeNativeMethod("start", JFlipperClient::start),
|
makeNativeMethod("start", JFlipperClient::start),
|
||||||
makeNativeMethod("stop", JFlipperClient::stop),
|
makeNativeMethod("stop", JFlipperClient::stop),
|
||||||
|
makeNativeMethod("isConnected", JFlipperClient::isConnected),
|
||||||
makeNativeMethod("addPluginNative", JFlipperClient::addPlugin),
|
makeNativeMethod("addPluginNative", JFlipperClient::addPlugin),
|
||||||
makeNativeMethod("removePluginNative", JFlipperClient::removePlugin),
|
makeNativeMethod("removePluginNative", JFlipperClient::removePlugin),
|
||||||
makeNativeMethod(
|
makeNativeMethod(
|
||||||
@@ -812,6 +789,19 @@ class JFlipperClient : public jni::HybridClass<JFlipperClient> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isConnected() {
|
||||||
|
try {
|
||||||
|
return FlipperClient::instance()->isConnected();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
handleException(e);
|
||||||
|
} catch (const std::exception* e) {
|
||||||
|
if (e) {
|
||||||
|
handleException(*e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void addPlugin(jni::alias_ref<JFlipperPlugin> plugin) {
|
void addPlugin(jni::alias_ref<JFlipperPlugin> plugin) {
|
||||||
try {
|
try {
|
||||||
auto wrapper =
|
auto wrapper =
|
||||||
@@ -949,9 +939,9 @@ class JFlipperClient : public jni::HybridClass<JFlipperClient> {
|
|||||||
const std::string app,
|
const std::string app,
|
||||||
const std::string appId,
|
const std::string appId,
|
||||||
const std::string privateAppDirectory) {
|
const std::string privateAppDirectory) {
|
||||||
sonarScheduler =
|
sonarScheduler() =
|
||||||
std::make_unique<FollyScheduler>(callbackWorker->eventBase());
|
std::make_unique<FollyScheduler>(callbackWorker->eventBase());
|
||||||
connectionScheduler =
|
connectionScheduler() =
|
||||||
std::make_unique<FollyScheduler>(connectionWorker->eventBase());
|
std::make_unique<FollyScheduler>(connectionWorker->eventBase());
|
||||||
|
|
||||||
FlipperClient::init(
|
FlipperClient::init(
|
||||||
@@ -962,8 +952,8 @@ class JFlipperClient : public jni::HybridClass<JFlipperClient> {
|
|||||||
std::move(app),
|
std::move(app),
|
||||||
std::move(appId),
|
std::move(appId),
|
||||||
std::move(privateAppDirectory)},
|
std::move(privateAppDirectory)},
|
||||||
sonarScheduler.get(),
|
sonarScheduler().get(),
|
||||||
connectionScheduler.get(),
|
connectionScheduler().get(),
|
||||||
insecurePort,
|
insecurePort,
|
||||||
securePort,
|
securePort,
|
||||||
altInsecurePort,
|
altInsecurePort,
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ public final class AndroidFlipperClient {
|
|||||||
private static final String[] REQUIRED_PERMISSIONS =
|
private static final String[] REQUIRED_PERMISSIONS =
|
||||||
new String[] {"android.permission.INTERNET", "android.permission.ACCESS_WIFI_STATE"};
|
new String[] {"android.permission.INTERNET", "android.permission.ACCESS_WIFI_STATE"};
|
||||||
|
|
||||||
public static synchronized FlipperClient getInstance(Context context) {
|
public static synchronized FlipperClient getInstance(
|
||||||
|
Context context, String id, String deviceName, String processName, String packageName) {
|
||||||
if (!sIsInitialized) {
|
if (!sIsInitialized) {
|
||||||
if (!(BuildConfig.IS_INTERNAL_BUILD || BuildConfig.LOAD_FLIPPER_EXPLICIT)) {
|
if (!(BuildConfig.IS_INTERNAL_BUILD || BuildConfig.LOAD_FLIPPER_EXPLICIT)) {
|
||||||
Log.e("Flipper", "Attempted to initialize in non-internal build");
|
Log.e("Flipper", "Attempted to initialize in non-internal build");
|
||||||
@@ -58,16 +59,23 @@ public final class AndroidFlipperClient {
|
|||||||
FlipperProps.getAltSecurePort(),
|
FlipperProps.getAltSecurePort(),
|
||||||
getServerHost(app),
|
getServerHost(app),
|
||||||
"Android",
|
"Android",
|
||||||
getFriendlyDeviceName(),
|
deviceName,
|
||||||
getId(),
|
id,
|
||||||
getRunningAppName(app),
|
processName,
|
||||||
getPackageName(app),
|
packageName,
|
||||||
privateAppDirectory);
|
privateAppDirectory);
|
||||||
sIsInitialized = true;
|
sIsInitialized = true;
|
||||||
}
|
}
|
||||||
return FlipperClientImpl.getInstance();
|
return FlipperClientImpl.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static synchronized FlipperClient getInstance(Context context) {
|
||||||
|
final Context app =
|
||||||
|
context.getApplicationContext() == null ? context : context.getApplicationContext();
|
||||||
|
return getInstance(
|
||||||
|
context, getId(), getFriendlyDeviceName(), getRunningAppName(app), getPackageName(app));
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static synchronized FlipperClient getInstanceIfInitialized() {
|
public static synchronized FlipperClient getInstanceIfInitialized() {
|
||||||
if (!sIsInitialized) {
|
if (!sIsInitialized) {
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ class FlipperClientImpl implements FlipperClient {
|
|||||||
@Override
|
@Override
|
||||||
public native void stop();
|
public native void stop();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public native boolean isConnected();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public native void subscribeForUpdates(FlipperStateUpdateListener stateListener);
|
public native void subscribeForUpdates(FlipperStateUpdateListener stateListener);
|
||||||
|
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.net.SocketFactory;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLParameters;
|
import javax.net.ssl.SSLParameters;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import org.java_websocket.client.WebSocketClient;
|
import org.java_websocket.client.WebSocketClient;
|
||||||
@@ -67,6 +67,22 @@ class FlipperSocketImpl extends WebSocketClient implements FlipperSocket {
|
|||||||
this.mEventHandler = eventHandler;
|
this.mEventHandler = eventHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void clearEventHandler() {
|
||||||
|
this.mEventHandler =
|
||||||
|
new FlipperSocketEventHandler() {
|
||||||
|
@Override
|
||||||
|
public void onConnectionEvent(SocketEvent event) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(String message) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FlipperObject onAuthenticationChallengeReceived() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flipperConnect() {
|
public void flipperConnect() {
|
||||||
if ((this.isOpen())) {
|
if ((this.isOpen())) {
|
||||||
@@ -79,6 +95,7 @@ class FlipperSocketImpl extends WebSocketClient implements FlipperSocket {
|
|||||||
* certificate exchange.
|
* certificate exchange.
|
||||||
*/
|
*/
|
||||||
FlipperObject authenticationObject = this.mEventHandler.onAuthenticationChallengeReceived();
|
FlipperObject authenticationObject = this.mEventHandler.onAuthenticationChallengeReceived();
|
||||||
|
SocketFactory socketFactory;
|
||||||
if (authenticationObject.contains("certificates_client_path")
|
if (authenticationObject.contains("certificates_client_path")
|
||||||
&& authenticationObject.contains("certificates_client_pass")) {
|
&& authenticationObject.contains("certificates_client_pass")) {
|
||||||
|
|
||||||
@@ -100,21 +117,23 @@ class FlipperSocketImpl extends WebSocketClient implements FlipperSocket {
|
|||||||
sslContext.init(
|
sslContext.init(
|
||||||
kmf.getKeyManagers(), new TrustManager[] {new FlipperTrustManager(cert_ca_path)}, null);
|
kmf.getKeyManagers(), new TrustManager[] {new FlipperTrustManager(cert_ca_path)}, null);
|
||||||
|
|
||||||
SSLSocketFactory factory = sslContext.getSocketFactory();
|
socketFactory = sslContext.getSocketFactory();
|
||||||
|
} else {
|
||||||
|
socketFactory = SocketFactory.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
this.setSocketFactory(
|
this.setSocketFactory(
|
||||||
new DelegatingSocketFactory(factory) {
|
new DelegatingSocketFactory(socketFactory) {
|
||||||
@Override
|
@Override
|
||||||
protected Socket configureSocket(Socket socket) {
|
protected Socket configureSocket(Socket socket) {
|
||||||
TrafficStats.setThreadStatsTag(SOCKET_TAG);
|
TrafficStats.setThreadStatsTag(SOCKET_TAG);
|
||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
this.connect();
|
this.connect();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("Flipper", "Failed to initialize the socket before connect. " + e.getMessage());
|
Log.e("flipper", "Failed to initialize the socket before connect. Error: " + e.getMessage());
|
||||||
this.mEventHandler.onConnectionEvent(FlipperSocketEventHandler.SocketEvent.ERROR);
|
this.mEventHandler.onConnectionEvent(FlipperSocketEventHandler.SocketEvent.ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,6 +156,9 @@ class FlipperSocketImpl extends WebSocketClient implements FlipperSocket {
|
|||||||
@Override
|
@Override
|
||||||
public void onClose(int code, String reason, boolean remote) {
|
public void onClose(int code, String reason, boolean remote) {
|
||||||
this.mEventHandler.onConnectionEvent(FlipperSocketEventHandler.SocketEvent.CLOSE);
|
this.mEventHandler.onConnectionEvent(FlipperSocketEventHandler.SocketEvent.CLOSE);
|
||||||
|
// Clear the existing event handler as to ensure no other events are processed after the close
|
||||||
|
// is handled.
|
||||||
|
this.clearEventHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,6 +170,7 @@ class FlipperSocketImpl extends WebSocketClient implements FlipperSocket {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onError(Exception ex) {
|
public void onError(Exception ex) {
|
||||||
|
|
||||||
// Check the exception for OpenSSL error and change the event type.
|
// Check the exception for OpenSSL error and change the event type.
|
||||||
// Required for Flipper as the current implementation treats these errors differently.
|
// Required for Flipper as the current implementation treats these errors differently.
|
||||||
if (ex instanceof javax.net.ssl.SSLHandshakeException) {
|
if (ex instanceof javax.net.ssl.SSLHandshakeException) {
|
||||||
@@ -155,24 +178,14 @@ class FlipperSocketImpl extends WebSocketClient implements FlipperSocket {
|
|||||||
} else {
|
} else {
|
||||||
this.mEventHandler.onConnectionEvent(FlipperSocketEventHandler.SocketEvent.ERROR);
|
this.mEventHandler.onConnectionEvent(FlipperSocketEventHandler.SocketEvent.ERROR);
|
||||||
}
|
}
|
||||||
|
// Clear the existing event handler as to ensure no other events are processed after the close
|
||||||
|
// is handled.
|
||||||
|
this.clearEventHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flipperDisconnect() {
|
public void flipperDisconnect() {
|
||||||
this.mEventHandler.onConnectionEvent(FlipperSocketEventHandler.SocketEvent.CLOSE);
|
this.clearEventHandler();
|
||||||
this.mEventHandler =
|
|
||||||
new FlipperSocketEventHandler() {
|
|
||||||
@Override
|
|
||||||
public void onConnectionEvent(SocketEvent event) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessageReceived(String message) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FlipperObject onAuthenticationChallengeReceived() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
super.close();
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,14 +16,19 @@ public final class FlipperUtils {
|
|||||||
|
|
||||||
private FlipperUtils() {}
|
private FlipperUtils() {}
|
||||||
|
|
||||||
public static boolean shouldEnableFlipper(final Context context) {
|
public static boolean shouldEnableFlipper(
|
||||||
|
final Context context, final boolean allowDebuggingServices) {
|
||||||
return (BuildConfig.IS_INTERNAL_BUILD || BuildConfig.LOAD_FLIPPER_EXPLICIT)
|
return (BuildConfig.IS_INTERNAL_BUILD || BuildConfig.LOAD_FLIPPER_EXPLICIT)
|
||||||
&& !isEndToEndTest()
|
&& !isEndToEndTest()
|
||||||
&& isMainProcess(context)
|
&& (allowDebuggingServices || isMainProcess(context))
|
||||||
// Flipper has issue with ASAN build. They cannot be concurrently enabled.
|
// Flipper has issue with ASAN build. They cannot be concurrently enabled.
|
||||||
&& !BuildConfig.IS_ASAN_BUILD;
|
&& !BuildConfig.IS_ASAN_BUILD;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean shouldEnableFlipper(final Context context) {
|
||||||
|
return shouldEnableFlipper(context, false);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isEndToEndTest() {
|
private static boolean isEndToEndTest() {
|
||||||
final String value = System.getenv("BUDDY_SONAR_DISABLED");
|
final String value = System.getenv("BUDDY_SONAR_DISABLED");
|
||||||
if (value == null || value.length() == 0) {
|
if (value == null || value.length() == 0) {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ public interface FlipperClient {
|
|||||||
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
|
boolean isConnected();
|
||||||
|
|
||||||
void subscribeForUpdates(FlipperStateUpdateListener stateListener);
|
void subscribeForUpdates(FlipperStateUpdateListener stateListener);
|
||||||
|
|
||||||
void unsubscribe();
|
void unsubscribe();
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
|
|||||||
SupportSQLiteDatabase database =
|
SupportSQLiteDatabase database =
|
||||||
sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file);
|
sqliteDatabaseConnectionProvider.openDatabase(databaseDescriptor.file);
|
||||||
try {
|
try {
|
||||||
Cursor structureCursor = database.query("PRAGMA table_info(" + table + ")", null);
|
Cursor structureCursor = database.query("PRAGMA table_info(" + table + ")");
|
||||||
Cursor foreignKeysCursor = database.query("PRAGMA foreign_key_list(" + table + ")", null);
|
Cursor foreignKeysCursor = database.query("PRAGMA foreign_key_list(" + table + ")");
|
||||||
Cursor indexesCursor = database.query("PRAGMA index_list(" + table + ")", null);
|
Cursor indexesCursor = database.query("PRAGMA index_list(" + table + ")");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Structure & foreign keys
|
// Structure & foreign keys
|
||||||
@@ -210,7 +210,7 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
|
|||||||
while (indexesCursor.moveToNext()) {
|
while (indexesCursor.moveToNext()) {
|
||||||
List<String> indexedColumnNames = new ArrayList<>();
|
List<String> indexedColumnNames = new ArrayList<>();
|
||||||
String indexName = indexesCursor.getString(indexesCursor.getColumnIndex("name"));
|
String indexName = indexesCursor.getString(indexesCursor.getColumnIndex("name"));
|
||||||
Cursor indexInfoCursor = database.query("PRAGMA index_info(" + indexName + ")", null);
|
Cursor indexInfoCursor = database.query("PRAGMA index_info(" + indexName + ")");
|
||||||
try {
|
try {
|
||||||
while (indexInfoCursor.moveToNext()) {
|
while (indexInfoCursor.moveToNext()) {
|
||||||
indexedColumnNames.add(
|
indexedColumnNames.add(
|
||||||
@@ -307,7 +307,7 @@ public class SqliteDatabaseDriver extends DatabaseDriver<SqliteDatabaseDescripto
|
|||||||
|
|
||||||
private static DatabaseExecuteSqlResponse executeSelect(
|
private static DatabaseExecuteSqlResponse executeSelect(
|
||||||
SupportSQLiteDatabase database, String query) {
|
SupportSQLiteDatabase database, String query) {
|
||||||
Cursor cursor = database.query(query, null);
|
Cursor cursor = database.query(query);
|
||||||
try {
|
try {
|
||||||
String[] columnNames = cursor.getColumnNames();
|
String[] columnNames = cursor.getColumnNames();
|
||||||
List<List<Object>> rows = cursorToList(cursor);
|
List<List<Object>> rows = cursorToList(cursor);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import android.view.View;
|
|||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
||||||
|
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,6 +120,17 @@ public class AccessibilityRoleUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role.equals(AccessibilityRole.NONE)) {
|
if (role.equals(AccessibilityRole.NONE)) {
|
||||||
|
if (AccessibilityUtil.supportsAction(
|
||||||
|
nodeInfo, AccessibilityActionCompat.ACTION_PAGE_UP.getId())
|
||||||
|
|| AccessibilityUtil.supportsAction(
|
||||||
|
nodeInfo, AccessibilityActionCompat.ACTION_PAGE_DOWN.getId())
|
||||||
|
|| AccessibilityUtil.supportsAction(
|
||||||
|
nodeInfo, AccessibilityActionCompat.ACTION_PAGE_LEFT.getId())
|
||||||
|
|| AccessibilityUtil.supportsAction(
|
||||||
|
nodeInfo, AccessibilityActionCompat.ACTION_PAGE_RIGHT.getId())) {
|
||||||
|
return AccessibilityRole.PAGER;
|
||||||
|
}
|
||||||
|
|
||||||
AccessibilityNodeInfoCompat.CollectionInfoCompat collection = nodeInfo.getCollectionInfo();
|
AccessibilityNodeInfoCompat.CollectionInfoCompat collection = nodeInfo.getCollectionInfo();
|
||||||
if (collection != null) {
|
if (collection != null) {
|
||||||
// RecyclerView will be classified as a list or grid.
|
// RecyclerView will be classified as a list or grid.
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ public final class AccessibilityUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean supportsAction(AccessibilityNodeInfoCompat node, int action) {
|
protected static boolean supportsAction(AccessibilityNodeInfoCompat node, int action) {
|
||||||
if (node != null) {
|
if (node != null) {
|
||||||
final int supportedActions = node.getActions();
|
final int supportedActions = node.getActions();
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ package com.facebook.flipper.plugins.react;
|
|||||||
import com.facebook.flipper.core.FlipperConnection;
|
import com.facebook.flipper.core.FlipperConnection;
|
||||||
import com.facebook.flipper.core.FlipperPlugin;
|
import com.facebook.flipper.core.FlipperPlugin;
|
||||||
|
|
||||||
// This plugin is not needed, but kept here for backward compatilibty
|
// This plugin is not needed, but kept here for backward compatibility
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public class ReactFlipperPlugin implements FlipperPlugin {
|
public class ReactFlipperPlugin implements FlipperPlugin {
|
||||||
|
|
||||||
|
|||||||
@@ -7,24 +7,23 @@
|
|||||||
|
|
||||||
package com.facebook.flipper.plugins.uidebugger
|
package com.facebook.flipper.plugins.uidebugger
|
||||||
|
|
||||||
import android.app.Application
|
import android.util.Log
|
||||||
import com.facebook.flipper.core.FlipperConnection
|
import com.facebook.flipper.core.FlipperConnection
|
||||||
import com.facebook.flipper.core.FlipperPlugin
|
import com.facebook.flipper.core.FlipperPlugin
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.ApplicationRef
|
import com.facebook.flipper.plugins.uidebugger.core.*
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.ConnectionRef
|
import com.facebook.flipper.plugins.uidebugger.descriptors.ApplicationRefDescriptor
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.Context
|
import com.facebook.flipper.plugins.uidebugger.descriptors.MetadataRegister
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.NativeScanScheduler
|
|
||||||
import com.facebook.flipper.plugins.uidebugger.model.InitEvent
|
import com.facebook.flipper.plugins.uidebugger.model.InitEvent
|
||||||
import com.facebook.flipper.plugins.uidebugger.scheduler.Scheduler
|
import com.facebook.flipper.plugins.uidebugger.model.MetadataUpdateEvent
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
val LogTag = "FlipperUIDebugger"
|
const val LogTag = "ui-debugger"
|
||||||
|
|
||||||
class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin {
|
class UIDebuggerFlipperPlugin(val context: UIDContext) : FlipperPlugin {
|
||||||
|
|
||||||
private val context: Context = Context(ApplicationRef(application), ConnectionRef(null))
|
init {
|
||||||
|
Log.i(LogTag, "Initializing ui-debugger")
|
||||||
private val nativeScanScheduler = Scheduler(NativeScanScheduler(context))
|
}
|
||||||
|
|
||||||
override fun getId(): String {
|
override fun getId(): String {
|
||||||
return "ui-debugger"
|
return "ui-debugger"
|
||||||
@@ -33,22 +32,37 @@ class UIDebuggerFlipperPlugin(val application: Application) : FlipperPlugin {
|
|||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun onConnect(connection: FlipperConnection) {
|
override fun onConnect(connection: FlipperConnection) {
|
||||||
this.context.connectionRef.connection = connection
|
this.context.connectionRef.connection = connection
|
||||||
|
this.context.bitmapPool.makeReady()
|
||||||
val rootDescriptor =
|
|
||||||
context.descriptorRegister.descriptorForClassUnsafe(context.applicationRef.javaClass)
|
|
||||||
|
|
||||||
connection.send(
|
connection.send(
|
||||||
InitEvent.name,
|
InitEvent.name,
|
||||||
Json.encodeToString(
|
Json.encodeToString(
|
||||||
InitEvent.serializer(), InitEvent(rootDescriptor.getId(context.applicationRef))))
|
InitEvent.serializer(),
|
||||||
|
InitEvent(
|
||||||
|
ApplicationRefDescriptor.getId(context.applicationRef),
|
||||||
|
context.frameworkEventMetadata)))
|
||||||
|
|
||||||
nativeScanScheduler.start()
|
connection.send(
|
||||||
|
MetadataUpdateEvent.name,
|
||||||
|
Json.encodeToString(
|
||||||
|
MetadataUpdateEvent.serializer(),
|
||||||
|
MetadataUpdateEvent(MetadataRegister.extractPendingMetadata())))
|
||||||
|
|
||||||
|
context.treeObserverManager.start()
|
||||||
|
|
||||||
|
context.connectionListeners.forEach { it.onConnect() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun onDisconnect() {
|
override fun onDisconnect() {
|
||||||
this.context.connectionRef.connection = null
|
this.context.connectionRef.connection = null
|
||||||
this.nativeScanScheduler.stop()
|
|
||||||
|
MetadataRegister.reset()
|
||||||
|
|
||||||
|
context.treeObserverManager.stop()
|
||||||
|
context.bitmapPool.recycleAll()
|
||||||
|
context.connectionListeners.forEach { it.onDisconnect() }
|
||||||
|
context.clearFrameworkEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun runInBackground(): Boolean {
|
override fun runInBackground(): Boolean {
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import com.facebook.flipper.core.FlipperObject
|
|||||||
import com.facebook.flipper.core.FlipperReceiver
|
import com.facebook.flipper.core.FlipperReceiver
|
||||||
import com.facebook.flipper.core.FlipperResponder
|
import com.facebook.flipper.core.FlipperResponder
|
||||||
import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver
|
import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver
|
||||||
import com.facebook.flipper.plugins.uidebugger.core.Context
|
import com.facebook.flipper.plugins.uidebugger.core.UIDContext
|
||||||
|
|
||||||
/** An interface for extensions to the UIDebugger plugin */
|
/** An interface for extensions to the UIDebugger plugin */
|
||||||
abstract class Command(val context: Context) {
|
abstract class Command(val context: UIDContext) {
|
||||||
/** The command identifier to respond to */
|
/** The command identifier to respond to */
|
||||||
abstract fun identifier(): String
|
abstract fun identifier(): String
|
||||||
/** Execute the command */
|
/** Execute the command */
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ package com.facebook.flipper.plugins.uidebugger.commands
|
|||||||
|
|
||||||
import com.facebook.flipper.core.FlipperConnection
|
import com.facebook.flipper.core.FlipperConnection
|
||||||
|
|
||||||
sealed class CommandRegister {
|
object CommandRegister {
|
||||||
companion object {
|
|
||||||
fun <T> register(connection: FlipperConnection, cmd: T) where T : Command {
|
fun <T> register(connection: FlipperConnection, cmd: T) where T : Command {
|
||||||
connection.receive(cmd.identifier(), cmd.receiver())
|
connection.receive(cmd.identifier(), cmd.receiver())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.common
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/** BitmapPool is intended to be used on the main thread. In other words, it is not thread-safe. */
|
||||||
|
class BitmapPool(private val config: Bitmap.Config = Bitmap.Config.RGB_565) {
|
||||||
|
|
||||||
|
interface ReusableBitmap {
|
||||||
|
val bitmap: Bitmap?
|
||||||
|
|
||||||
|
fun readyForReuse()
|
||||||
|
}
|
||||||
|
|
||||||
|
val mainScope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
|
private val container: MutableMap<String, MutableList<Bitmap>> = mutableMapOf()
|
||||||
|
private var isRecycled = false
|
||||||
|
|
||||||
|
private fun generateKey(width: Int, height: Int): String = "$width,$height"
|
||||||
|
|
||||||
|
fun recycleAll() {
|
||||||
|
isRecycled = true
|
||||||
|
container.forEach { (_, bitmaps) ->
|
||||||
|
bitmaps.forEach { bitmap -> bitmap.recycle() }
|
||||||
|
bitmaps.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
container.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun makeReady() {
|
||||||
|
isRecycled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBitmap(width: Int, height: Int): ReusableBitmap {
|
||||||
|
val key = generateKey(width, height)
|
||||||
|
val bitmaps = container[key]
|
||||||
|
|
||||||
|
return if (bitmaps == null || bitmaps.isEmpty()) {
|
||||||
|
LeasedBitmap(Bitmap.createBitmap(width, height, config))
|
||||||
|
} else {
|
||||||
|
LeasedBitmap(bitmaps.removeLast())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class LeasedBitmap(override val bitmap: Bitmap) : ReusableBitmap {
|
||||||
|
override fun readyForReuse() {
|
||||||
|
val key = generateKey(bitmap.width, bitmap.height)
|
||||||
|
|
||||||
|
mainScope.launch {
|
||||||
|
if (isRecycled) {
|
||||||
|
bitmap.recycle()
|
||||||
|
} else {
|
||||||
|
var bitmaps = container[key]
|
||||||
|
if (bitmaps == null) {
|
||||||
|
bitmaps = mutableListOf()
|
||||||
|
container[key] = bitmaps
|
||||||
|
}
|
||||||
|
bitmaps.add(bitmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun createBitmapWithDefaultConfig(width: Int, height: Int): Bitmap {
|
||||||
|
return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,34 +9,63 @@ package com.facebook.flipper.plugins.uidebugger.common
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.facebook.flipper.plugins.uidebugger.LogTag
|
import com.facebook.flipper.plugins.uidebugger.LogTag
|
||||||
|
import com.facebook.flipper.plugins.uidebugger.model.InspectableValue
|
||||||
|
|
||||||
// Maintains 2 way mapping between some enum value and a readable string representation
|
// Maintains 2 way mapping between some enum value and a readable string representation
|
||||||
open class EnumMapping<T>(val mapping: Map<String, T>) {
|
open class EnumMapping<T>(private val mapping: Map<String, T>) {
|
||||||
|
|
||||||
fun getStringRepresentation(enumValue: T): String {
|
fun getStringRepresentation(enumValue: T): String {
|
||||||
val entry = mapping.entries.find { (_, value) -> value == enumValue }
|
val entry = mapping.entries.find { (_, value) -> value == enumValue }
|
||||||
if (entry != null) {
|
return if (entry != null) {
|
||||||
return entry.key
|
entry.key
|
||||||
} else {
|
} else {
|
||||||
Log.w(
|
Log.v(
|
||||||
LogTag,
|
LogTag,
|
||||||
"Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}")
|
"Could not convert enum value ${enumValue.toString()} to string, known values ${mapping.entries}")
|
||||||
return NoMapping
|
NoMapping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getEnumValue(key: String): T {
|
fun getEnumValue(key: String): T {
|
||||||
val value =
|
return mapping[key]
|
||||||
mapping[key]
|
|
||||||
?: throw UIDebuggerException(
|
?: throw UIDebuggerException(
|
||||||
"Could not convert string ${key} to enum value, possible values ${mapping.entries} ")
|
"Could not convert string $key to enum value, possible values ${mapping.entries} ")
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toInspectable(value: T, mutable: Boolean): InspectableValue.Enum {
|
fun getInspectableValues(): Set<InspectableValue> {
|
||||||
return InspectableValue.Enum(EnumData(mapping.keys, getStringRepresentation(value)), mutable)
|
val set: MutableSet<InspectableValue> = mutableSetOf()
|
||||||
|
mapping.entries.forEach { set.add(InspectableValue.Text(it.key)) }
|
||||||
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toInspectable(value: T): InspectableValue.Enum {
|
||||||
|
return InspectableValue.Enum(getStringRepresentation(value))
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val NoMapping = "__UNKNOWN_ENUM_VALUE__"
|
const val NoMapping = "__UNKNOWN_ENUM_VALUE__"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Enum<T>> enumerator(): Iterator<T> = enumValues<T>().iterator()
|
||||||
|
|
||||||
|
inline fun <reified T : Enum<T>> enumToSet(): Set<String> {
|
||||||
|
val set = mutableSetOf<String>()
|
||||||
|
val values = enumerator<T>()
|
||||||
|
values.forEach { set.add(it.name) }
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Enum<T>> enumToInspectableSet(): Set<InspectableValue> {
|
||||||
|
val set = mutableSetOf<InspectableValue>()
|
||||||
|
val values = enumerator<T>()
|
||||||
|
values.forEach { set.add(InspectableValue.Text(it.name)) }
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Enum<T>> enumMapping(): EnumMapping<T> {
|
||||||
|
val map = mutableMapOf<String, T>()
|
||||||
|
val values = enumerator<T>()
|
||||||
|
values.forEach { map[it.name] = it }
|
||||||
|
return EnumMapping<T>(map)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.facebook.flipper.plugins.uidebugger.core
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
object ActivityTracker : Application.ActivityLifecycleCallbacks {
|
||||||
|
interface ActivityStackChangedListener {
|
||||||
|
fun onActivityAdded(activity: Activity, stack: List<Activity>)
|
||||||
|
|
||||||
|
fun onActivityStackChanged(stack: List<Activity>)
|
||||||
|
|
||||||
|
fun onActivityDestroyed(activity: Activity, stack: List<Activity>)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val activities: MutableList<WeakReference<Activity>> = mutableListOf()
|
||||||
|
private val trackedActivities: MutableSet<Int> = mutableSetOf()
|
||||||
|
private var activityStackChangedListener: ActivityStackChangedListener? = null
|
||||||
|
|
||||||
|
fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) {
|
||||||
|
activityStackChangedListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start(application: Application) {
|
||||||
|
initialiseActivities()
|
||||||
|
application.registerActivityLifecycleCallbacks(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun trackActivity(activity: Activity) {
|
||||||
|
if (trackedActivities.contains(System.identityHashCode(activity))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
trackedActivities.add(System.identityHashCode(activity))
|
||||||
|
activities.add(WeakReference<Activity>(activity))
|
||||||
|
|
||||||
|
FragmentTracker.trackFragmentsOfActivity(activity)
|
||||||
|
|
||||||
|
activityStackChangedListener?.onActivityAdded(activity, this.activitiesStack)
|
||||||
|
activityStackChangedListener?.onActivityStackChanged(this.activitiesStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun untrackActivity(activity: Activity) {
|
||||||
|
trackedActivities.remove(System.identityHashCode(activity))
|
||||||
|
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
|
||||||
|
|
||||||
|
while (activityIterator.hasNext()) {
|
||||||
|
if (activityIterator.next().get() === activity) {
|
||||||
|
activityIterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FragmentTracker.untrackFragmentsOfActivity(activity)
|
||||||
|
|
||||||
|
activityStackChangedListener?.onActivityDestroyed(activity, this.activitiesStack)
|
||||||
|
activityStackChangedListener?.onActivityStackChanged(this.activitiesStack)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
|
||||||
|
trackActivity(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {
|
||||||
|
trackActivity(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResumed(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivityPaused(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivityStopped(activity: Activity) {}
|
||||||
|
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}
|
||||||
|
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {
|
||||||
|
untrackActivity(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
val activitiesStack: List<Activity>
|
||||||
|
get() {
|
||||||
|
val stack: MutableList<Activity> = ArrayList(activities.size)
|
||||||
|
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
|
||||||
|
while (activityIterator.hasNext()) {
|
||||||
|
val activity: Activity? = activityIterator.next().get()
|
||||||
|
if (activity == null) {
|
||||||
|
activityIterator.remove()
|
||||||
|
} else {
|
||||||
|
stack.add(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stack
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity tracker is used to track activities. However, it cannot track via life-cycle events
|
||||||
|
* all those activities that were created prior to initialisation via the `start(application:
|
||||||
|
* Application)` method.
|
||||||
|
*
|
||||||
|
* As such, the method below makes a 'best effort' to find these untracked activities and add them
|
||||||
|
* to the tracked list.
|
||||||
|
*/
|
||||||
|
@SuppressLint("PrivateApi", "DiscouragedPrivateApi")
|
||||||
|
fun initialiseActivities() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val activityThreadClass: Class<*> = Class.forName("android.app.ActivityThread")
|
||||||
|
val currentActivityThreadMethod: Method =
|
||||||
|
activityThreadClass.getMethod("currentActivityThread")
|
||||||
|
val currentActivityThread: Any? = currentActivityThreadMethod.invoke(null)
|
||||||
|
|
||||||
|
currentActivityThread?.let { activityThread ->
|
||||||
|
val mActivitiesField: Field = activityThreadClass.getDeclaredField("mActivities")
|
||||||
|
mActivitiesField.isAccessible = true
|
||||||
|
val mActivities = mActivitiesField.get(activityThread) as android.util.ArrayMap<*, *>
|
||||||
|
for (record in mActivities.values) {
|
||||||
|
val recordClass: Class<*> = record.javaClass
|
||||||
|
val activityField: Field = recordClass.getDeclaredField("activity")
|
||||||
|
activityField.isAccessible = true
|
||||||
|
|
||||||
|
val activity = activityField.get(record)
|
||||||
|
if (activity != null && activity is Activity) {
|
||||||
|
trackActivity(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.flipper.plugins.uidebugger.core
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewTreeObserver
|
|
||||||
import com.facebook.flipper.plugins.uidebugger.descriptors.DescriptorRegister
|
|
||||||
|
|
||||||
class ApplicationInspector(val context: Context) {
|
|
||||||
val descriptorRegister = DescriptorRegister.withDefaults()
|
|
||||||
val traversal = LayoutTraversal(descriptorRegister, context.applicationRef)
|
|
||||||
|
|
||||||
fun attachListeners(view: View) {
|
|
||||||
// An OnGlobalLayoutListener watches the entire hierarchy for layout changes
|
|
||||||
// (so registering one of these on any View in a hierarchy will cause it to be triggered
|
|
||||||
// when any View in that hierarchy is laid out or changes visibility).
|
|
||||||
view
|
|
||||||
.getViewTreeObserver()
|
|
||||||
.addOnGlobalLayoutListener(
|
|
||||||
object : ViewTreeObserver.OnGlobalLayoutListener {
|
|
||||||
override fun onGlobalLayout() {}
|
|
||||||
})
|
|
||||||
view
|
|
||||||
.getViewTreeObserver()
|
|
||||||
.addOnPreDrawListener(
|
|
||||||
object : ViewTreeObserver.OnPreDrawListener {
|
|
||||||
override fun onPreDraw(): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun observe() {
|
|
||||||
val rootResolver = RootViewResolver()
|
|
||||||
rootResolver.attachListener(
|
|
||||||
object : RootViewResolver.Listener {
|
|
||||||
override fun onRootViewAdded(view: View) {
|
|
||||||
attachListeners(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRootViewRemoved(view: View) {}
|
|
||||||
override fun onRootViewsChanged(views: java.util.List<View>) {}
|
|
||||||
})
|
|
||||||
|
|
||||||
val activeRoots = rootResolver.listActiveRootViews()
|
|
||||||
activeRoots?.let { roots -> for (root: RootViewResolver.RootView in roots) {} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.facebook.flipper.plugins.uidebugger.core
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Application
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
|
|
||||||
class ApplicationObserver(val application: Application) : Application.ActivityLifecycleCallbacks {
|
|
||||||
interface ActivityStackChangedListener {
|
|
||||||
fun onActivityStackChanged(stack: List<Activity>)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ActivityDestroyedListener {
|
|
||||||
fun onActivityDestroyed(activity: Activity)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val rootsResolver: RootViewResolver
|
|
||||||
private val activities: MutableList<WeakReference<Activity>>
|
|
||||||
private var activityStackChangedlistener: ActivityStackChangedListener? = null
|
|
||||||
private var activityDestroyedListener: ActivityDestroyedListener? = null
|
|
||||||
|
|
||||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
|
||||||
activities.add(WeakReference<Activity>(activity))
|
|
||||||
activityStackChangedlistener?.let { listener ->
|
|
||||||
listener.onActivityStackChanged(this.activitiesStack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun onActivityStarted(activity: Activity) {}
|
|
||||||
override fun onActivityResumed(activity: Activity) {}
|
|
||||||
override fun onActivityPaused(activity: Activity) {}
|
|
||||||
override fun onActivityStopped(activity: Activity) {}
|
|
||||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
|
|
||||||
override fun onActivityDestroyed(activity: Activity) {
|
|
||||||
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
|
|
||||||
|
|
||||||
while (activityIterator.hasNext()) {
|
|
||||||
if (activityIterator.next().get() === activity) {
|
|
||||||
activityIterator.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
activityDestroyedListener?.let { listener -> listener.onActivityDestroyed(activity) }
|
|
||||||
|
|
||||||
activityStackChangedlistener?.let { listener ->
|
|
||||||
listener.onActivityStackChanged(this.activitiesStack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setActivityStackChangedListener(listener: ActivityStackChangedListener?) {
|
|
||||||
activityStackChangedlistener = listener
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setActivityDestroyedListener(listener: ActivityDestroyedListener?) {
|
|
||||||
activityDestroyedListener = listener
|
|
||||||
}
|
|
||||||
|
|
||||||
val activitiesStack: List<Activity>
|
|
||||||
get() {
|
|
||||||
val stack: MutableList<Activity> = ArrayList<Activity>(activities.size)
|
|
||||||
val activityIterator: MutableIterator<WeakReference<Activity>> = activities.iterator()
|
|
||||||
while (activityIterator.hasNext()) {
|
|
||||||
val activity: Activity? = activityIterator.next().get()
|
|
||||||
if (activity == null) {
|
|
||||||
activityIterator.remove()
|
|
||||||
} else {
|
|
||||||
stack.add(activity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stack
|
|
||||||
}
|
|
||||||
|
|
||||||
val rootViews: List<View>
|
|
||||||
get() {
|
|
||||||
val roots = rootsResolver.listActiveRootViews()
|
|
||||||
roots?.let { roots ->
|
|
||||||
val viewRoots: MutableList<View> = ArrayList<View>(roots.size)
|
|
||||||
for (root in roots) {
|
|
||||||
viewRoots.add(root.view)
|
|
||||||
}
|
|
||||||
return viewRoots
|
|
||||||
}
|
|
||||||
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
rootsResolver = RootViewResolver()
|
|
||||||
application.registerActivityLifecycleCallbacks(this)
|
|
||||||
activities = ArrayList<WeakReference<Activity>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user