From 533db3cfac950348c6259e7a2336c7ae3f7163ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwa=C5=9Bniewski?= Date: Tue, 2 Aug 2022 20:10:29 +0200 Subject: [PATCH 1/2] Add fabric on iOS (#400) * Update podspec file for fabric * Define codegen spec & move import to separate file * Add codegenConfig in package.json * Update podspec to detect .mm files * Change spec types to floats * Implement fabric component with props updating * Re-order functions, add accessibilityIncrements prop * Clean up imports, remove comment * Sort out props updating, remove unused functions * Add event handling * Add tapToSeek implementation * Fix tapToSeek * Handle images using bridge * Add missing typedef * Verify the build for new arch with GH Actions * Save new-arch Pods under new-arch cache key * Use Podfile.lock with old arch and regenerate for new one * Correct path for new arch Podfile.lock creation * Disable flipper in Podfile * Install pods with new arch flag * Fix tapToSeek on iOS * Allow value property to be controlled * Generate project.pbxproj for new arch * Separate npm & pods step in CI * Change step names, fix cache keys * Remove pods cache * Run npm install if cache was not found * Run build using xcodebuild * Fix: Pods-related error after using Pods from cache (#407) * Bring back Pods to cache * Use new-arch string in key for new arch cache * Try to reinstall pods instead of creating new * Separate deps installation between two caches * Rename Pods reinstall step * Remove explicit folly version, default to old arch * Add clean scripts in example for codegen cleanup * Change CI step names * Remove isFabricEnabled This check is no longer needed. Co-authored-by: BartoszKlonowski --- .github/workflows/ReactNativeSlider-CI.yml | 100 +++++-- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 121 +-------- example/ios/example.xcodeproj/project.pbxproj | 118 +++------ example/package.json | 4 +- .../ios/RNCSlider.xcodeproj/project.pbxproj | 6 + package/ios/RNCSliderComponentView.h | 35 +++ package/ios/RNCSliderComponentView.mm | 250 ++++++++++++++++++ package/package.json | 9 + package/react-native-slider.podspec | 21 +- package/src/RNCSliderNativeComponent.js | 46 ++-- package/src/Slider.js | 2 +- package/src/index.js | 4 + 13 files changed, 478 insertions(+), 240 deletions(-) create mode 100644 package/ios/RNCSliderComponentView.h create mode 100644 package/ios/RNCSliderComponentView.mm create mode 100644 package/src/index.js diff --git a/.github/workflows/ReactNativeSlider-CI.yml b/.github/workflows/ReactNativeSlider-CI.yml index 97b589b1..2c433920 100644 --- a/.github/workflows/ReactNativeSlider-CI.yml +++ b/.github/workflows/ReactNativeSlider-CI.yml @@ -60,7 +60,7 @@ jobs: verify: - name: Verify the Example app sources + name: Verify example app sources runs-on: ubuntu-latest steps: @@ -75,7 +75,7 @@ jobs: build-android-app: - name: Verify the package and example app builds for Android platform + name: Build example app Android runs-on: ubuntu-latest needs: [verify] @@ -95,7 +95,7 @@ jobs: build-iOS-app: - name: Verify the package and example app builds for iOS platform + name: Build example app iOS runs-on: macos-latest needs: [verify] steps: @@ -105,33 +105,101 @@ jobs: id: cache-npm uses: actions/cache@v3 env: - cache-name: cached-ios-deps + cache-name: cached-ios-npm-deps with: - path: | - example/node_modules - example/ios/Pods - key: ${{ hashFiles('./example/package-lock.json') }}-${{ hashFiles('./package/package-lock.json') }}-${{ hashFiles('./example/ios/Podfile.lock') }} + path: example/node_modules + key: ${{ hashFiles('./example/package-lock.json') }}-${{ hashFiles('./package/package-lock.json') }} - - name: Install required dependencies on cache miss + - name: Install required dependencies on cache miss (npm) if: steps.cache-npm.outputs.cache-hit != 'true' run: | npm install - cd example/ios && pod install --verbose + + - name: Cache Pods + id: cache-pods + uses: actions/cache@v3 + env: + cache-name: cached-ios-pods-deps + with: + path: example/ios/Pods + key: ${{ hashFiles('./example/ios/Podfile.lock') }} + + - name: Install required dependencies on cache miss (Pods) + if: steps.cache-pods.outputs.cache-hit != 'true' + run: | + cd example/ios && pod install + + - name: Reinstall Pods only if using cached ones + if: steps.cache-pods.outputs.cache-hit == 'true' + run: cd example/ios && pod install - name: Use the current package sources in build run: cd example && npm run refresh-package - - name: Opening Simulator app - uses: futureware-tech/simulator-action@v1 + - name: Build iOS + run: | + device_name='iPhone 13' + device=$(xcrun simctl list devices "${device_name}" available | grep "${device_name} (") + re='\(([-0-9A-Fa-f]+)\)' + [[ $device =~ $re ]] || exit 1 + xcodebuild -workspace example.xcworkspace -scheme example -destination "platform=iOS Simulator,id=${BASH_REMATCH[1]}" CODE_SIGNING_ALLOWED=NO COMPILER_INDEX_STORE_ENABLE=NO build + working-directory: example/ios + + + build-iOS-new-arch-app: + name: Build example app iOS (Fabric) + runs-on: macos-latest + needs: [build-iOS-app] + steps: + - uses: actions/checkout@v3 + + - name: Cache node modules + id: cache-npm + uses: actions/cache@v3 + env: + cache-name: cached-ios-npm-deps + with: + path: example/node_modules + key: new-arch-${{ hashFiles('./example/package-lock.json') }}-${{ hashFiles('./package/package-lock.json') }} + + - name: Install required dependencies on cache miss (npm) + if: steps.cache-npm.outputs.cache-hit != 'true' + run: | + npm install + + - name: Cache Pods + id: cache-pods + uses: actions/cache@v3 + env: + cache-name: cached-ios-pods-deps with: - model: 'iPhone 12 mini' + path: example/ios/Pods + key: new-arch-${{ hashFiles('./example/ios/Podfile.lock') }} + + - name: Install required dependencies on cache miss (Pods) + if: steps.cache-pods.outputs.cache-hit != 'true' + run: | + cd example/ios && RCT_NEW_ARCH_ENABLED=1 pod install + + - name: Reinstall Pods only if using cached ones + if: steps.cache-pods.outputs.cache-hit == 'true' + run: cd example/ios && RCT_NEW_ARCH_ENABLED=1 pod install - - name: Builds the iOS app - run: cd example && npx react-native run-ios + - name: Use the current package sources in build + run: cd example && npm run refresh-package + + - name: Build iOS - Fabric + run: | + device_name='iPhone 13' + device=$(xcrun simctl list devices "${device_name}" available | grep "${device_name} (") + re='\(([-0-9A-Fa-f]+)\)' + [[ $device =~ $re ]] || exit 1 + xcodebuild -workspace example.xcworkspace -scheme example -destination "platform=iOS Simulator,id=${BASH_REMATCH[1]}" CODE_SIGNING_ALLOWED=NO COMPILER_INDEX_STORE_ENABLE=NO build + working-directory: example/ios build-Windows-app: - name: Verify the package and example app builds for Windows platform + name: Build example app Windows runs-on: windows-latest needs: [verify] steps: diff --git a/example/ios/Podfile b/example/ios/Podfile index eb7fb575..d099ec34 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -28,7 +28,7 @@ target 'example' do # # Note that if you have use_frameworks! enabled, Flipper will not work and # you should disable the next line. - use_flipper!() + # use_flipper!() post_install do |installer| react_native_post_install(installer) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index aabfd4d2..3fddea77 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,6 +1,5 @@ PODS: - boost (1.76.0) - - CocoaAsyncSocket (7.6.5) - DoubleConversion (1.1.6) - FBLazyVector (0.69.1) - FBReactNativeSpec (0.69.1): @@ -10,71 +9,8 @@ PODS: - React-Core (= 0.69.1) - React-jsi (= 0.69.1) - ReactCommon/turbomodule/core (= 0.69.1) - - Flipper (0.125.0): - - Flipper-Folly (~> 2.6) - - Flipper-RSocket (~> 1.4) - - Flipper-Boost-iOSX (1.76.0.1.11) - - Flipper-DoubleConversion (3.2.0.1) - - Flipper-Fmt (7.1.7) - - Flipper-Folly (2.6.10): - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt (= 7.1.7) - - Flipper-Glog - - libevent (~> 2.1.12) - - OpenSSL-Universal (= 1.1.1100) - - Flipper-Glog (0.5.0.5) - - Flipper-PeerTalk (0.0.4) - - Flipper-RSocket (1.4.3): - - Flipper-Folly (~> 2.6) - - FlipperKit (0.125.0): - - FlipperKit/Core (= 0.125.0) - - FlipperKit/Core (0.125.0): - - Flipper (~> 0.125.0) - - FlipperKit/CppBridge - - FlipperKit/FBCxxFollyDynamicConvert - - FlipperKit/FBDefines - - FlipperKit/FKPortForwarding - - SocketRocket (~> 0.6.0) - - FlipperKit/CppBridge (0.125.0): - - Flipper (~> 0.125.0) - - FlipperKit/FBCxxFollyDynamicConvert (0.125.0): - - Flipper-Folly (~> 2.6) - - FlipperKit/FBDefines (0.125.0) - - FlipperKit/FKPortForwarding (0.125.0): - - CocoaAsyncSocket (~> 7.6) - - Flipper-PeerTalk (~> 0.0.4) - - FlipperKit/FlipperKitHighlightOverlay (0.125.0) - - FlipperKit/FlipperKitLayoutHelpers (0.125.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutTextSearchable - - FlipperKit/FlipperKitLayoutIOSDescriptors (0.125.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutPlugin (0.125.0): - - FlipperKit/Core - - FlipperKit/FlipperKitHighlightOverlay - - FlipperKit/FlipperKitLayoutHelpers - - FlipperKit/FlipperKitLayoutIOSDescriptors - - FlipperKit/FlipperKitLayoutTextSearchable - - YogaKit (~> 1.18) - - FlipperKit/FlipperKitLayoutTextSearchable (0.125.0) - - FlipperKit/FlipperKitNetworkPlugin (0.125.0): - - FlipperKit/Core - - FlipperKit/FlipperKitReactPlugin (0.125.0): - - FlipperKit/Core - - FlipperKit/FlipperKitUserDefaultsPlugin (0.125.0): - - FlipperKit/Core - - FlipperKit/SKIOSNetworkPlugin (0.125.0): - - FlipperKit/Core - - FlipperKit/FlipperKitNetworkPlugin - fmt (6.2.1) - glog (0.3.5) - - libevent (2.1.12) - - OpenSSL-Universal (1.1.1100) - RCT-Folly (2021.06.28.00-v2): - boost - DoubleConversion @@ -352,39 +288,14 @@ PODS: - React-jsi (= 0.69.1) - React-logger (= 0.69.1) - React-perflogger (= 0.69.1) - - SocketRocket (0.6.0) - Yoga (1.14.0) - - YogaKit (1.18.1): - - Yoga (~> 1.14) DEPENDENCIES: - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) - - Flipper (= 0.125.0) - - Flipper-Boost-iOSX (= 1.76.0.1.11) - - Flipper-DoubleConversion (= 3.2.0.1) - - Flipper-Fmt (= 7.1.7) - - Flipper-Folly (= 2.6.10) - - Flipper-Glog (= 0.5.0.5) - - Flipper-PeerTalk (= 0.0.4) - - Flipper-RSocket (= 1.4.3) - - FlipperKit (= 0.125.0) - - FlipperKit/Core (= 0.125.0) - - FlipperKit/CppBridge (= 0.125.0) - - FlipperKit/FBCxxFollyDynamicConvert (= 0.125.0) - - FlipperKit/FBDefines (= 0.125.0) - - FlipperKit/FKPortForwarding (= 0.125.0) - - FlipperKit/FlipperKitHighlightOverlay (= 0.125.0) - - FlipperKit/FlipperKitLayoutPlugin (= 0.125.0) - - FlipperKit/FlipperKitLayoutTextSearchable (= 0.125.0) - - FlipperKit/FlipperKitNetworkPlugin (= 0.125.0) - - FlipperKit/FlipperKitReactPlugin (= 0.125.0) - - FlipperKit/FlipperKitUserDefaultsPlugin (= 0.125.0) - - FlipperKit/SKIOSNetworkPlugin (= 0.125.0) - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - - OpenSSL-Universal (= 1.1.1100) - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) @@ -418,21 +329,7 @@ DEPENDENCIES: SPEC REPOS: trunk: - - CocoaAsyncSocket - - Flipper - - Flipper-Boost-iOSX - - Flipper-DoubleConversion - - Flipper-Fmt - - Flipper-Folly - - Flipper-Glog - - Flipper-PeerTalk - - Flipper-RSocket - - FlipperKit - fmt - - libevent - - OpenSSL-Universal - - SocketRocket - - YogaKit EXTERNAL SOURCES: boost: @@ -504,23 +401,11 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: a7c83b31436843459a1961bfd74b96033dc77234 - CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 FBLazyVector: 068141206af867f72854753423d0117c4bf53419 FBReactNativeSpec: 546a637adc797fa436dd51d1c63c580f820de31c - Flipper: 26fc4b7382499f1281eb8cb921e5c3ad6de91fe0 - Flipper-Boost-iOSX: fd1e2b8cbef7e662a122412d7ac5f5bea715403c - Flipper-DoubleConversion: 2dc99b02f658daf147069aad9dbd29d8feb06d30 - Flipper-Fmt: 60cbdd92fc254826e61d669a5d87ef7015396a9b - Flipper-Folly: 584845625005ff068a6ebf41f857f468decd26b3 - Flipper-Glog: 70c50ce58ddaf67dc35180db05f191692570f446 - Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 - Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541 - FlipperKit: cbdee19bdd4e7f05472a66ce290f1b729ba3cb86 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a - libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 - OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a RCTRequired: ae07282b2ec9c90d7eb98251603bc3f82403d239 RCTTypeSafety: a04dc1339af2e1da759ccd093bf11c310dce1ef6 @@ -535,7 +420,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 758e70947c232828a66b5ddc42d02b4d010fa26e React-jsinspector: 55605caf04e02f9b0e05842b786f1c12dde08f4b React-logger: ca970551cb7eea2fd814d0d5f6fc1a471eb53b76 - react-native-slider: 8a61f3dfa62c859e67f545ed707e9438ada8be6c + react-native-slider: 541c8579b23211cc985ac44023dd0f0d46c9f497 React-perflogger: c9161ff0f1c769993cd11d2751e4331ff4ceb7cd React-RCTActionSheet: 2d885b0bea76a5254ef852939273edd8de116180 React-RCTAnimation: 353fa4fc3c19060068832dd32e555182ec07be45 @@ -548,10 +433,8 @@ SPEC CHECKSUMS: React-RCTVibration: e8b7dd6635cc95689b5db643b5a3848f1e05b30b React-runtimeexecutor: 27f468c5576eaf05ffb7a907528e44c75a3fcbae ReactCommon: e30ec17dfb1d4c4f3419eac254350d6abca6d5a2 - SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 Yoga: 7ab6e3ee4ce47d7b789d1cb520163833e515f452 - YogaKit: f782866e155069a2cca2517aafea43200b01fd5a -PODFILE CHECKSUM: ea17b1731971e714550ad4c057f8c9e4dd544466 +PODFILE CHECKSUM: 2bee2bd5d60bce023442613522baa9154a69716b COCOAPODS: 1.11.3 diff --git a/example/ios/example.xcodeproj/project.pbxproj b/example/ios/example.xcodeproj/project.pbxproj index a78db216..348d7c97 100644 --- a/example/ios/example.xcodeproj/project.pbxproj +++ b/example/ios/example.xcodeproj/project.pbxproj @@ -8,12 +8,12 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* exampleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* exampleTests.m */; }; - 0C80B921A6F3F58F76C31292 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-example.a */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; - 7699B88040F8A987B510C191 /* libPods-example-exampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-example-exampleTests.a */; }; + 6BBEC3876DB24E7EC5F666C4 /* libPods-example.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6983A68A044EB9DD95F63306 /* libPods-example.a */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + 81D3FA39D5D958007A96A69C /* libPods-example-exampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5115448F37E1CCE810660B01 /* libPods-example-exampleTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -30,19 +30,19 @@ 00E356EE1AD99517003FC87E /* exampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = exampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* exampleTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = exampleTests.m; sourceTree = ""; }; + 0E91EBB04FF9AC87FF15A49A /* Pods-example-exampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.release.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.release.xcconfig"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = example/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = example/AppDelegate.mm; sourceTree = ""; }; 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = example/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = example/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = example/main.m; sourceTree = ""; }; - 19F6CBCC0A4E27FBF8BF4A61 /* libPods-example-exampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-exampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B4392A12AC88292D35C810B /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = ""; }; - 5709B34CF0A7D63546082F79 /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = ""; }; - 5B7EB9410499542E8C5724F5 /* Pods-example-exampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.debug.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.debug.xcconfig"; sourceTree = ""; }; - 5DCACB8F33CDC322A6C60F78 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 3207487C8E606095CD27F4E5 /* Pods-example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.debug.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.debug.xcconfig"; sourceTree = ""; }; + 4B5D1A917FE91365CC456D98 /* Pods-example-exampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.debug.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.debug.xcconfig"; sourceTree = ""; }; + 5115448F37E1CCE810660B01 /* libPods-example-exampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example-exampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6983A68A044EB9DD95F63306 /* libPods-example.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-example.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = example/LaunchScreen.storyboard; sourceTree = ""; }; - 89C6BE57DB24E9ADA2F236DE /* Pods-example-exampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example-exampleTests.release.xcconfig"; path = "Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests.release.xcconfig"; sourceTree = ""; }; + 8480D051363212A5D8D2391D /* Pods-example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-example.release.xcconfig"; path = "Target Support Files/Pods-example/Pods-example.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -51,7 +51,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7699B88040F8A987B510C191 /* libPods-example-exampleTests.a in Frameworks */, + 81D3FA39D5D958007A96A69C /* libPods-example-exampleTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -59,7 +59,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 0C80B921A6F3F58F76C31292 /* libPods-example.a in Frameworks */, + 6BBEC3876DB24E7EC5F666C4 /* libPods-example.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -100,8 +100,8 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 5DCACB8F33CDC322A6C60F78 /* libPods-example.a */, - 19F6CBCC0A4E27FBF8BF4A61 /* libPods-example-exampleTests.a */, + 6983A68A044EB9DD95F63306 /* libPods-example.a */, + 5115448F37E1CCE810660B01 /* libPods-example-exampleTests.a */, ); name = Frameworks; sourceTree = ""; @@ -140,10 +140,10 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - 3B4392A12AC88292D35C810B /* Pods-example.debug.xcconfig */, - 5709B34CF0A7D63546082F79 /* Pods-example.release.xcconfig */, - 5B7EB9410499542E8C5724F5 /* Pods-example-exampleTests.debug.xcconfig */, - 89C6BE57DB24E9ADA2F236DE /* Pods-example-exampleTests.release.xcconfig */, + 3207487C8E606095CD27F4E5 /* Pods-example.debug.xcconfig */, + 8480D051363212A5D8D2391D /* Pods-example.release.xcconfig */, + 4B5D1A917FE91365CC456D98 /* Pods-example-exampleTests.debug.xcconfig */, + 0E91EBB04FF9AC87FF15A49A /* Pods-example-exampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -155,12 +155,11 @@ isa = PBXNativeTarget; buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "exampleTests" */; buildPhases = ( - A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */, + ED2E2923E032D37CF13CC324 /* [CP] Check Pods Manifest.lock */, 00E356EA1AD99517003FC87E /* Sources */, 00E356EB1AD99517003FC87E /* Frameworks */, 00E356EC1AD99517003FC87E /* Resources */, - C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */, - F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */, + 924ABD9BC0AE551498AC2975 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -176,14 +175,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "example" */; buildPhases = ( - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, + 1718781C37F9A97B10768F83 /* [CP] Check Pods Manifest.lock */, FD10A7F022414F080027D42C /* Start Packager */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, + 99BDD7F81F6511283291DE22 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -266,46 +264,7 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-example-exampleTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { + 1718781C37F9A97B10768F83 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -327,24 +286,24 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */ = { + 924ABD9BC0AE551498AC2975 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; - E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { + 99BDD7F81F6511283291DE22 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -361,21 +320,26 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example/Pods-example-resources.sh\"\n"; showEnvVarsInLog = 0; }; - F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */ = { + ED2E2923E032D37CF13CC324 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-example-exampleTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-example-exampleTests/Pods-example-exampleTests-resources.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; FD10A7F022414F080027D42C /* Start Packager */ = { @@ -430,7 +394,7 @@ /* Begin XCBuildConfiguration section */ 00E356F61AD99517003FC87E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-example-exampleTests.debug.xcconfig */; + baseConfigurationReference = 4B5D1A917FE91365CC456D98 /* Pods-example-exampleTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -457,7 +421,7 @@ }; 00E356F71AD99517003FC87E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-example-exampleTests.release.xcconfig */; + baseConfigurationReference = 0E91EBB04FF9AC87FF15A49A /* Pods-example-exampleTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; @@ -481,7 +445,7 @@ }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-example.debug.xcconfig */; + baseConfigurationReference = 3207487C8E606095CD27F4E5 /* Pods-example.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -507,7 +471,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-example.release.xcconfig */; + baseConfigurationReference = 8480D051363212A5D8D2391D /* Pods-example.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/example/package.json b/example/package.json index cca481d4..ce491287 100644 --- a/example/package.json +++ b/example/package.json @@ -10,7 +10,9 @@ "lint": "eslint .", "windows": "react-native run-windows", "postinstall": "rimraf ./node_modules/@react-native-community/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@react-native-community/slider && rimraf ./node_modules/@react-native-community/slider/node_modules", - "refresh-package": "rimraf ./node_modules/@react-native-community/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@react-native-community/slider && rimraf ./node_modules/@react-native-community/slider/node_modules" + "refresh-package": "rimraf ./node_modules/@react-native-community/slider && npx copyfiles -u 2 \"./../package/**/*\" ./node_modules/@react-native-community/slider && rimraf ./node_modules/@react-native-community/slider/node_modules", + "clean-android": "rm -rf android/app/build", + "clean-ios": "rm -rf ios/build/generated/ios && rm -rf ios/Pods && rm ios/Podfile.lock" }, "dependencies": { "@react-native-community/slider": "file:../package", diff --git a/package/ios/RNCSlider.xcodeproj/project.pbxproj b/package/ios/RNCSlider.xcodeproj/project.pbxproj index 0e625a01..0006240d 100644 --- a/package/ios/RNCSlider.xcodeproj/project.pbxproj +++ b/package/ios/RNCSlider.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 28C79A22220DC7760061DE82 /* RNCSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C79A1F220DC7760061DE82 /* RNCSliderManager.m */; }; 28C79A23220DC7760061DE82 /* RNCSlider.m in Sources */ = {isa = PBXBuildFile; fileRef = 28C79A21220DC7760061DE82 /* RNCSlider.m */; }; + 7682E5172887E3AB00642F2D /* RNCSliderComponentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7682E5162887E3AB00642F2D /* RNCSliderComponentView.mm */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -29,6 +30,8 @@ 28C79A1F220DC7760061DE82 /* RNCSliderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCSliderManager.m; sourceTree = ""; }; 28C79A20220DC7760061DE82 /* RNCSliderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCSliderManager.h; sourceTree = ""; }; 28C79A21220DC7760061DE82 /* RNCSlider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCSlider.m; sourceTree = ""; }; + 7682E5152887E39900642F2D /* RNCSliderComponentView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNCSliderComponentView.h; sourceTree = ""; }; + 7682E5162887E3AB00642F2D /* RNCSliderComponentView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNCSliderComponentView.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -45,6 +48,8 @@ 28C79A00220DC4CC0061DE82 = { isa = PBXGroup; children = ( + 7682E5162887E3AB00642F2D /* RNCSliderComponentView.mm */, + 7682E5152887E39900642F2D /* RNCSliderComponentView.h */, 28C79A1E220DC7760061DE82 /* RNCSlider.h */, 28C79A21220DC7760061DE82 /* RNCSlider.m */, 28C79A20220DC7760061DE82 /* RNCSliderManager.h */, @@ -117,6 +122,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7682E5172887E3AB00642F2D /* RNCSliderComponentView.mm in Sources */, 28C79A23220DC7760061DE82 /* RNCSlider.m in Sources */, 28C79A22220DC7760061DE82 /* RNCSliderManager.m in Sources */, ); diff --git a/package/ios/RNCSliderComponentView.h b/package/ios/RNCSliderComponentView.h new file mode 100644 index 00000000..ea688004 --- /dev/null +++ b/package/ios/RNCSliderComponentView.h @@ -0,0 +1,35 @@ +#ifdef RCT_NEW_ARCH_ENABLED + +#import +#import +#import "RNCSlider.h" + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^RNCLoadImageCompletionBlock)(NSError * _Nullable error, UIImage * _Nullable image); + +@interface RNCSliderComponentView : RCTViewComponentView + +@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderValueChange; +@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderSlidingStart; +@property (nonatomic, copy) RCTBubblingEventBlock onRNCSliderSlidingComplete; + +@property (nonatomic, assign) float step; +@property (nonatomic, assign) float lastValue; +@property (nonatomic, assign) bool isSliding; + +@property (nonatomic, strong) UIImage *trackImage; +@property (nonatomic, strong) UIImage *minimumTrackImage; +@property (nonatomic, strong) UIImage *maximumTrackImage; +@property (nonatomic, strong) UIImage *thumbImage; +@property (nonatomic, assign) bool tapToSeek; +@property (nonatomic, strong) NSString *accessibilityUnits; +@property (nonatomic, strong) NSArray *accessibilityIncrements; + +- (float) discreteValue:(float)value; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/package/ios/RNCSliderComponentView.mm b/package/ios/RNCSliderComponentView.mm new file mode 100644 index 00000000..a19e5375 --- /dev/null +++ b/package/ios/RNCSliderComponentView.mm @@ -0,0 +1,250 @@ +#ifdef RCT_NEW_ARCH_ENABLED + +#import "RNCSliderComponentView.h" + +#import + +#import +#import +#import +#import +#import +#import "RCTImagePrimitivesConversions.h" +#import +#import "RCTFabricComponentsPlugins.h" +#import "RNCSlider.h" + + +using namespace facebook::react; + +@interface RNCSliderComponentView () + +@end + + +@implementation RNCSliderComponentView +{ + RNCSlider *slider; + UIImage *_image; + BOOL _isSliding; +} + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + slider = [[RNCSlider alloc] initWithFrame:self.bounds]; + [slider addTarget:self action:@selector(sliderValueChanged:) + forControlEvents:UIControlEventValueChanged]; + [slider addTarget:self action:@selector(sliderTouchStart:) + forControlEvents:UIControlEventTouchDown]; + [slider addTarget:self action:@selector(sliderTouchEnd:) + forControlEvents:(UIControlEventTouchUpInside | + UIControlEventTouchUpOutside | + UIControlEventTouchCancel)]; + + UITapGestureRecognizer *tapGesturer; + tapGesturer = [[UITapGestureRecognizer alloc] initWithTarget: self action:@selector(tapHandler:)]; + [tapGesturer setNumberOfTapsRequired: 1]; + [slider addGestureRecognizer:tapGesturer]; + + slider.value = (float)defaultProps->value; + self.contentView = slider; + } + return self; +} + +- (void)tapHandler:(UITapGestureRecognizer *)gesture { + if ([gesture.view class] != [RNCSlider class]) { + return; + } + RNCSlider *slider = (RNCSlider *)gesture.view; + slider.isSliding = _isSliding; + + // Ignore this tap if in the middle of a slide. + if (_isSliding) { + return; + } + + if (!slider.tapToSeek) { + return; + } + + CGPoint touchPoint = [gesture locationInView:slider]; + float rangeWidth = slider.maximumValue - slider.minimumValue; + float sliderPercent = touchPoint.x / slider.bounds.size.width; + slider.lastValue = slider.value; + float value = slider.minimumValue + (rangeWidth * sliderPercent); + + [slider setValue:[slider discreteValue:value] animated: YES]; + + std::dynamic_pointer_cast(_eventEmitter) + ->onRNCSliderSlidingStart(RNCSliderEventEmitter::OnRNCSliderSlidingStart{.value = static_cast(slider.lastValue)}); + + // Trigger onValueChange to address https://github.com/react-native-community/react-native-slider/issues/212 + std::dynamic_pointer_cast(_eventEmitter) + ->onRNCSliderValueChange(RNCSliderEventEmitter::OnRNCSliderValueChange{.value = static_cast(slider.value)}); + + std::dynamic_pointer_cast(_eventEmitter) + ->onRNCSliderSlidingComplete(RNCSliderEventEmitter::OnRNCSliderSlidingComplete{.value = static_cast(slider.value)}); +} + +- (void)sliderValueChanged:(RNCSlider *)sender +{ + [self RNCSendSliderEvent:sender withContinuous:YES isSlidingStart:NO]; +} + +- (void)sliderTouchStart:(RNCSlider *)sender +{ + [self RNCSendSliderEvent:sender withContinuous:NO isSlidingStart:YES]; + _isSliding = YES; + sender.isSliding = YES; +} + +- (void)sliderTouchEnd:(RNCSlider *)sender +{ + [self RNCSendSliderEvent:sender withContinuous:NO isSlidingStart:NO]; + sender.isSliding = NO; + _isSliding = NO; +} + +- (void)RNCSendSliderEvent:(RNCSlider *)sender withContinuous:(BOOL)continuous isSlidingStart:(BOOL)isSlidingStart +{ + float value = [sender discreteValue:sender.value]; + + if(!sender.isSliding) { + [sender setValue:value animated:NO]; + } + + if (continuous) { + if (sender.lastValue != value) { + std::dynamic_pointer_cast(_eventEmitter) + ->onRNCSliderValueChange(RNCSliderEventEmitter::OnRNCSliderValueChange{.value = static_cast(value)}); + } + } else { + if (!isSlidingStart) { + std::dynamic_pointer_cast(_eventEmitter) + ->onRNCSliderSlidingComplete(RNCSliderEventEmitter::OnRNCSliderSlidingComplete{.value = static_cast(value)}); + } + if (isSlidingStart) { + std::dynamic_pointer_cast(_eventEmitter) + ->onRNCSliderSlidingStart(RNCSliderEventEmitter::OnRNCSliderSlidingStart{.value = static_cast(value)}); + } + } + + sender.lastValue = value; +} + +- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps +{ + const auto &oldScreenProps = *std::static_pointer_cast(_props); + const auto &newScreenProps = *std::static_pointer_cast(props); + + if (oldScreenProps.value != newScreenProps.value) { + if (!slider.isSliding) { + slider.value = newScreenProps.value; + } + } + if (oldScreenProps.disabled != newScreenProps.disabled) { + slider.enabled = !newScreenProps.disabled; + } + if (oldScreenProps.step != newScreenProps.step) { + slider.step = newScreenProps.step; + } + if (oldScreenProps.inverted != newScreenProps.inverted) { + [self setInverted:newScreenProps.inverted]; + } + if (oldScreenProps.maximumValue != newScreenProps.maximumValue) { + [slider setMaximumValue:newScreenProps.maximumValue]; + } + if (oldScreenProps.tapToSeek != newScreenProps.tapToSeek) { + slider.tapToSeek = newScreenProps.tapToSeek; + } + if (oldScreenProps.minimumValue != newScreenProps.minimumValue) { + [slider setMinimumValue:newScreenProps.minimumValue]; + } + if (oldScreenProps.thumbTintColor != newScreenProps.thumbTintColor) { + slider.thumbTintColor = RCTUIColorFromSharedColor(newScreenProps.thumbTintColor); + } + if (oldScreenProps.minimumTrackTintColor != newScreenProps.minimumTrackTintColor) { + slider.minimumTrackTintColor = RCTUIColorFromSharedColor(newScreenProps.minimumTrackTintColor); + } + if (oldScreenProps.maximumTrackTintColor != newScreenProps.maximumTrackTintColor) { + slider.maximumTrackTintColor = RCTUIColorFromSharedColor(newScreenProps.maximumTrackTintColor); + } + if (oldScreenProps.accessibilityUnits != newScreenProps.accessibilityUnits) { + NSString *convertedAccessibilityUnits = [NSString stringWithCString:newScreenProps.accessibilityUnits.c_str() + encoding:[NSString defaultCStringEncoding]]; + slider.accessibilityUnits = convertedAccessibilityUnits; + } + if (oldScreenProps.accessibilityIncrements != newScreenProps.accessibilityIncrements) { + id accessibilityIncrements = [NSArray new]; + for (auto str : newScreenProps.accessibilityIncrements) { + [accessibilityIncrements addObject:[NSString stringWithUTF8String:str.c_str()]]; + } + [slider setAccessibilityIncrements:accessibilityIncrements]; + } + if (oldScreenProps.thumbImage != newScreenProps.thumbImage) { + [self loadImageFromImageSource:newScreenProps.thumbImage completionBlock:^(NSError *error, UIImage *image) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->slider setThumbImage:image]; + }); + }]; + } + if (oldScreenProps.trackImage != newScreenProps.trackImage) { + [self loadImageFromImageSource:newScreenProps.trackImage completionBlock:^(NSError *error, UIImage *image) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->slider setTrackImage:image]; + }); + }]; + } + if (oldScreenProps.minimumTrackImage != newScreenProps.minimumTrackImage) { + [self loadImageFromImageSource:newScreenProps.minimumTrackImage completionBlock:^(NSError *error, UIImage *image) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->slider setMinimumTrackImage:image]; + }); + }]; + } + if (oldScreenProps.maximumTrackImage != newScreenProps.maximumTrackImage) { + [self loadImageFromImageSource:newScreenProps.maximumTrackImage completionBlock:^(NSError *error, UIImage *image) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->slider setMaximumTrackImage:image]; + }); + }]; + } + [super updateProps:props oldProps:oldProps]; +} + + +// TODO temporarily using bridge, workaround for https://github.com/reactwg/react-native-new-architecture/discussions/31#discussioncomment-2717047, rewrite when Meta comes with a solution. +- (void)loadImageFromImageSource:(ImageSource)source completionBlock:(RNCLoadImageCompletionBlock)completionBlock +{ + NSString *uri = [[NSString alloc] initWithUTF8String:source.uri.c_str()]; + if ((BOOL)uri.length) { + [[[RCTBridge currentBridge] moduleForName:@"ImageLoader"] loadImageWithURLRequest:NSURLRequestFromImageSource(source) size:CGSizeMake(source.size.width, source.size.height) scale:source.scale clipped:NO resizeMode:RCTResizeModeCover progressBlock:nil partialLoadBlock:nil completionBlock:completionBlock]; + } +} + +- (void)setInverted:(BOOL)inverted +{ + if (inverted) { + self.transform = CGAffineTransformMakeScale(-1, 1); + } else { + self.transform = CGAffineTransformMakeScale(1, 1); + } +} + +@end + +Class RNCSliderCls(void) +{ + return RNCSliderComponentView.class; +} + +#endif diff --git a/package/package.json b/package/package.json index 2bef3994..4ff718e5 100644 --- a/package/package.json +++ b/package/package.json @@ -59,5 +59,14 @@ "bracketSpacing": false, "jsxBracketSameLine": true, "parser": "flow" + }, + "codegenConfig": { + "libraries": [ + { + "name": "RNCSlider", + "type": "components", + "jsSrcsDir": "src" + } + ] } } diff --git a/package/react-native-slider.podspec b/package/react-native-slider.podspec index 72126fbe..d7a620e8 100644 --- a/package/react-native-slider.podspec +++ b/package/react-native-slider.podspec @@ -2,6 +2,8 @@ require 'json' package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + Pod::Spec.new do |s| s.name = "react-native-slider" s.version = package['version'] @@ -13,7 +15,24 @@ Pod::Spec.new do |s| s.platform = :ios, "9.0" s.source = { :git => "https://github.com/callstack/react-native-slider.git", :tag => "v#{s.version}" } - s.source_files = "ios/**/*.{h,m}" + s.source_files = "ios/**/*.{h,m,mm}" s.dependency 'React-Core' + + # This guard prevent to install the dependencies when we run `pod install` in the old architecture. + if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + s.dependency "React-RCTFabric" + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + end end diff --git a/package/src/RNCSliderNativeComponent.js b/package/src/RNCSliderNativeComponent.js index 632a7a6a..09ad0cfd 100644 --- a/package/src/RNCSliderNativeComponent.js +++ b/package/src/RNCSliderNativeComponent.js @@ -10,50 +10,48 @@ 'use strict'; -import {requireNativeComponent} from 'react-native'; - +import type {HostComponent} from 'react-native'; import type {ColorValue} from 'react-native/Libraries/StyleSheet/StyleSheetTypes'; import type {ImageSource} from 'react-native/Libraries/Image/ImageSource'; -import type {NativeComponent} from 'react-native/Libraries/Renderer/shims/ReactNative'; -import type {SyntheticEvent} from 'react-native/Libraries/Types/CoreEventTypes'; import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import type { + Float, + BubblingEventHandler, +} from 'react-native/Libraries/Types/CodegenTypes'; -type Event = SyntheticEvent< - $ReadOnly<{| - value: number, - fromUser?: boolean, - |}>, ->; +type Event = $ReadOnly<{| + value: ?Float, + fromUser?: ?boolean, +|}>; type NativeProps = $ReadOnly<{| ...ViewProps, accessibilityUnits?: string, - accessibilityIncrements?: Array, + accessibilityIncrements?: $ReadOnlyArray, disabled?: ?boolean, enabled?: ?boolean, inverted?: ?boolean, vertical?: ?boolean, + tapToSeek?: ?boolean, maximumTrackImage?: ?ImageSource, maximumTrackTintColor?: ?ColorValue, - maximumValue?: ?number, + maximumValue?: ?Float, minimumTrackImage?: ?ImageSource, minimumTrackTintColor?: ?ColorValue, - minimumValue?: ?number, - onChange?: ?(event: Event) => void, - onRNCSliderSlidingStart?: ?(event: Event) => void, - onRNCSliderSlidingComplete?: ?(event: Event) => void, - onRNCSliderValueChange?: ?(event: Event) => void, - step?: ?number, + minimumValue?: ?Float, + onChange?: ?BubblingEventHandler, + onRNCSliderSlidingStart?: ?BubblingEventHandler, + onRNCSliderSlidingComplete?: ?BubblingEventHandler, + onRNCSliderValueChange?: ?BubblingEventHandler, + step?: ?Float, testID?: ?string, thumbImage?: ?ImageSource, thumbTintColor?: ?ColorValue, trackImage?: ?ImageSource, - value?: ?number, + value?: ?Float, |}>; -type RNCSliderType = Class>; - -const RNCSliderNativeComponent = ((requireNativeComponent( +export default (codegenNativeComponent( 'RNCSlider', -): any): RNCSliderType); -export default RNCSliderNativeComponent; +): HostComponent); diff --git a/package/src/Slider.js b/package/src/Slider.js index 5c3274dd..fbd70f80 100644 --- a/package/src/Slider.js +++ b/package/src/Slider.js @@ -17,7 +17,7 @@ import { StyleSheet, AccessibilityActionEvent, } from 'react-native'; -import RCTSliderNativeComponent from './RNCSliderNativeComponent'; +import RCTSliderNativeComponent from './index'; import type {Ref} from 'react'; import type {NativeComponent} from 'react-native/Libraries/Renderer/shims/ReactNative'; diff --git a/package/src/index.js b/package/src/index.js new file mode 100644 index 00000000..81033322 --- /dev/null +++ b/package/src/index.js @@ -0,0 +1,4 @@ +// @flow +const RNCSlider = require('./RNCSliderNativeComponent').default; + +export default RNCSlider; From 51bc969a8253bddd244587eeb9fe8819a8fc2fb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwa=C5=9Bniewski?= Date: Thu, 4 Aug 2022 09:46:03 +0200 Subject: [PATCH 2/2] Add fabric on Android (#402) * Configure build.gradle * Update libraryName on android * Create ReactSlider shared implementation * Split implementations into oldarch and newarch * Dispatch events * Cleanup eventDispatcher * Make oldarch implementation use shared code * Add defaults to js spec * Clean up newarch ReactSliderManager * Reorder props to fix disabled state * Handle TestID setter * Move ReactSliderShadowNode to shared implementation * Share getExportedCustomDirectEventTypeConstants * Remove comments, add empty line --- package/android/build.gradle | 29 +- .../slider/ReactSliderManager.java | 258 ------------------ .../slider/ReactSliderManagerImpl.java | 175 ++++++++++++ .../slider/ReactSliderManager.java | 209 ++++++++++++++ .../slider/ReactSliderManager.java | 148 ++++++++++ package/src/RNCSliderNativeComponent.js | 9 +- package/src/Slider.js | 2 +- 7 files changed, 566 insertions(+), 264 deletions(-) delete mode 100644 package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java create mode 100644 package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java create mode 100644 package/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java create mode 100644 package/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java diff --git a/package/android/build.gradle b/package/android/build.gradle index 5d78bc84..f940a4bd 100644 --- a/package/android/build.gradle +++ b/package/android/build.gradle @@ -11,6 +11,15 @@ buildscript { } } +def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} + +apply plugin: 'com.android.library' +if (isNewArchitectureEnabled()) { + apply plugin: 'com.facebook.react' +} + def getExtOrDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['ReactNativeSlider_' + name] } @@ -19,7 +28,6 @@ def getExtOrIntegerDefault(name) { return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties['ReactNativeSlider_' + name]).toInteger() } -apply plugin: 'com.android.library' android { compileSdkVersion getExtOrIntegerDefault('compileSdkVersion') @@ -28,6 +36,17 @@ android { defaultConfig { minSdkVersion getExtOrIntegerDefault('minSdkVersion') targetSdkVersion getExtOrIntegerDefault('targetSdkVersion') + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + } + + sourceSets { + main { + if (isNewArchitectureEnabled()) { + java.srcDirs += ['src/newarch'] + } else { + java.srcDirs += ['src/oldarch'] + } + } } } @@ -40,3 +59,11 @@ dependencies { //noinspection GradleDynamicVersion api 'com.facebook.react:react-native:+' } + +if (isNewArchitectureEnabled()) { + react { + jsRootDir = file("../src") + libraryName = "ReactSlider" + codegenJavaPackageName = "com.reactnativecommunity.slider" + } +} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java deleted file mode 100644 index 65f7849f..00000000 --- a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManager.java +++ /dev/null @@ -1,258 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its 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.reactnativecommunity.slider; - -import android.graphics.PorterDuffColorFilter; -import android.os.Build; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.view.View; -import android.widget.SeekBar; -import com.facebook.react.bridge.ReactContext; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.uimanager.LayoutShadowNode; -import com.facebook.react.uimanager.SimpleViewManager; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.ViewProps; -import com.facebook.react.uimanager.annotations.ReactProp; -import com.facebook.yoga.YogaMeasureFunction; -import com.facebook.yoga.YogaMeasureMode; -import com.facebook.yoga.YogaMeasureOutput; -import com.facebook.yoga.YogaNode; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import javax.annotation.Nullable; - -/** - * Manages instances of {@code ReactSlider}. - */ -public class ReactSliderManager extends SimpleViewManager { - - public static final String REACT_CLASS = "RNCSlider"; - - static class ReactSliderShadowNode extends LayoutShadowNode implements - YogaMeasureFunction { - - private int mWidth; - private int mHeight; - private boolean mMeasured; - - private ReactSliderShadowNode() { - initMeasureFunction(); - } - - private void initMeasureFunction() { - setMeasureFunction(this); - } - - @Override - public long measure( - YogaNode node, - float width, - YogaMeasureMode widthMode, - float height, - YogaMeasureMode heightMode) { - if (!mMeasured) { - SeekBar reactSlider = new ReactSlider(getThemedContext(), null); - final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - reactSlider.measure(spec, spec); - mWidth = reactSlider.getMeasuredWidth(); - mHeight = reactSlider.getMeasuredHeight(); - mMeasured = true; - } - - return YogaMeasureOutput.make(mWidth, mHeight); - } - } - - private static final SeekBar.OnSeekBarChangeListener ON_CHANGE_LISTENER = - new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { - ReactContext reactContext = (ReactContext) seekbar.getContext(); - reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( - new ReactSliderEvent( - seekbar.getId(), - ((ReactSlider)seekbar).toRealProgress(progress), fromUser)); - } - - @Override - public void onStartTrackingTouch(SeekBar seekbar) { - ReactContext reactContext = (ReactContext) seekbar.getContext(); - ((ReactSlider)seekbar).isSliding(true); - reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( - new ReactSlidingStartEvent( - seekbar.getId(), - ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); - } - - @Override - public void onStopTrackingTouch(SeekBar seekbar) { - ReactContext reactContext = (ReactContext) seekbar.getContext(); - ((ReactSlider)seekbar).isSliding(false); - reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( - new ReactSlidingCompleteEvent( - seekbar.getId(), - ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); - reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( - new ReactSliderEvent( - seekbar.getId(), - ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()), - !((ReactSlider)seekbar).isSliding())); - } - }; - - @Override - public String getName() { - return REACT_CLASS; - } - - @Override - public LayoutShadowNode createShadowNodeInstance() { - return new ReactSliderShadowNode(); - } - - @Override - public Class getShadowNodeClass() { - return ReactSliderShadowNode.class; - } - - @Override - protected ReactSlider createViewInstance(ThemedReactContext context) { - ReactSlider slider = new ReactSlider(context, null); - - if (Build.VERSION.SDK_INT >= 21) { - /** - * The "splitTrack" parameter should have "false" value, - * otherwise the SeekBar progress line doesn't appear when it is rotated. - */ - slider.setSplitTrack(false); - } - - return slider; - } - - @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true) - public void setEnabled(ReactSlider view, boolean enabled) { - view.setEnabled(enabled); - } - - @ReactProp(name = "value", defaultDouble = 0d) - public void setValue(ReactSlider view, double value) { - if (view.isSliding() == false) { - view.setValue(value); - if (view.isAccessibilityFocused() && Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { - view.setupAccessibility((int)value); - } - } - } - - @ReactProp(name = "minimumValue", defaultDouble = 0d) - public void setMinimumValue(ReactSlider view, double value) { - view.setMinValue(value); - } - - @ReactProp(name = "maximumValue", defaultDouble = 1d) - public void setMaximumValue(ReactSlider view, double value) { - view.setMaxValue(value); - } - - @ReactProp(name = "step", defaultDouble = 0d) - public void setStep(ReactSlider view, double value) { - view.setStep(value); - } - - @ReactProp(name = "thumbTintColor", customType = "Color") - public void setThumbTintColor(ReactSlider view, Integer color) { - if (view.getThumb() != null) { - if (color == null) { - view.getThumb().clearColorFilter(); - } else { - view.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - } - } - - @ReactProp(name = "minimumTrackTintColor", customType = "Color") - public void setMinimumTrackTintColor(ReactSlider view, Integer color) { - LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent(); - Drawable progress = drawable.findDrawableByLayerId(android.R.id.progress); - if (color == null) { - progress.clearColorFilter(); - } else { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - progress.setColorFilter(new PorterDuffColorFilter((int)color, PorterDuff.Mode.SRC_IN)); - } - else { - progress.setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - } - } - - @ReactProp(name = "thumbImage") - public void setThumbImage(ReactSlider view, @Nullable ReadableMap source) { - String uri = null; - if (source != null) { - uri = source.getString("uri"); - } - view.setThumbImage(uri); - } - - @ReactProp(name = "maximumTrackTintColor", customType = "Color") - public void setMaximumTrackTintColor(ReactSlider view, Integer color) { - LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent(); - Drawable background = drawable.findDrawableByLayerId(android.R.id.background); - if (color == null) { - background.clearColorFilter(); - } else { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - background.setColorFilter(new PorterDuffColorFilter((int)color, PorterDuff.Mode.SRC_IN)); - } - else { - background.setColorFilter(color, PorterDuff.Mode.SRC_IN); - } - } - } - - @ReactProp(name = "inverted", defaultBoolean = false) - public void setInverted(ReactSlider view, boolean inverted) { - if (inverted) view.setScaleX(-1f); - else view.setScaleX(1f); - } - - @ReactProp(name = "accessibilityUnits") - public void setAccessibilityUnits(ReactSlider view, String accessibilityUnits) { - view.setAccessibilityUnits(accessibilityUnits); - } - - @ReactProp(name = "accessibilityIncrements") - public void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibilityIncrements) { - List objectList = accessibilityIncrements.toArrayList(); - List stringList = new ArrayList<>(); - for(Object item: objectList) { - stringList.add((String)item); - } - view.setAccessibilityIncrements(stringList); - } - - @Override - protected void addEventEmitters(final ThemedReactContext reactContext, final ReactSlider view) { - view.setOnSeekBarChangeListener(ON_CHANGE_LISTENER); - } - - @Override - public Map getExportedCustomDirectEventTypeConstants() { - return MapBuilder.of(ReactSlidingCompleteEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRNCSliderSlidingComplete"), - ReactSlidingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRNCSliderSlidingStart")); - } -} diff --git a/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java new file mode 100644 index 00000000..421cc948 --- /dev/null +++ b/package/android/src/main/java/com/reactnativecommunity/slider/ReactSliderManagerImpl.java @@ -0,0 +1,175 @@ +package com.reactnativecommunity.slider; + +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.Build; +import android.view.View; +import android.widget.SeekBar; + +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.yoga.YogaMeasureFunction; +import com.facebook.yoga.YogaMeasureMode; +import com.facebook.yoga.YogaMeasureOutput; +import com.facebook.yoga.YogaNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +public class ReactSliderManagerImpl { + + public static final String REACT_CLASS = "RNCSlider"; + + public static class ReactSliderShadowNode extends LayoutShadowNode implements + YogaMeasureFunction { + + private int mWidth; + private int mHeight; + private boolean mMeasured; + + public ReactSliderShadowNode() { + initMeasureFunction(); + } + + private void initMeasureFunction() { + setMeasureFunction(this); + } + + @Override + public long measure( + YogaNode node, + float width, + YogaMeasureMode widthMode, + float height, + YogaMeasureMode heightMode) { + if (!mMeasured) { + SeekBar reactSlider = new ReactSlider(getThemedContext(), null); + final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + reactSlider.measure(spec, spec); + mWidth = reactSlider.getMeasuredWidth(); + mHeight = reactSlider.getMeasuredHeight(); + mMeasured = true; + } + + return YogaMeasureOutput.make(mWidth, mHeight); + } + } + + public static ReactSlider createViewInstance(ThemedReactContext context) { + ReactSlider slider = new ReactSlider(context, null); + + if (Build.VERSION.SDK_INT >= 21) { + /** + * The "splitTrack" parameter should have "false" value, + * otherwise the SeekBar progress line doesn't appear when it is rotated. + */ + slider.setSplitTrack(false); + } + + return slider; + } + + public static void setValue(ReactSlider view, double value) { + if (view.isSliding() == false) { + view.setValue(value); + if (view.isAccessibilityFocused() && Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { + view.setupAccessibility((int)value); + } + } + } + + public static void setMinimumValue(ReactSlider view, float value) { + view.setMinValue(value); + } + + public static void setMaximumValue(ReactSlider view, float value) { + view.setMaxValue(value); + } + + public static void setStep(ReactSlider view, float value) { + view.setStep(value); + } + + public static void setEnabled(ReactSlider view, boolean enabled) { + view.setEnabled(enabled); + } + + public static void setThumbTintColor(ReactSlider view, Integer color) { + if (view.getThumb() != null) { + if (color == null) { + view.getThumb().clearColorFilter(); + } else { + view.getThumb().setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } + } + + public static void setMinimumTrackTintColor(ReactSlider view, Integer color) { + LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent(); + Drawable progress = drawable.findDrawableByLayerId(android.R.id.progress); + if (color == null) { + progress.clearColorFilter(); + } else { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + progress.setColorFilter(new PorterDuffColorFilter((int)color, PorterDuff.Mode.SRC_IN)); + } + else { + progress.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } + } + + public static void setThumbImage(ReactSlider view, @Nullable ReadableMap source) { + String uri = null; + if (source != null) { + uri = source.getString("uri"); + } + view.setThumbImage(uri); + } + + public static void setMaximumTrackTintColor(ReactSlider view, Integer color) { + LayerDrawable drawable = (LayerDrawable) view.getProgressDrawable().getCurrent(); + Drawable background = drawable.findDrawableByLayerId(android.R.id.background); + if (color == null) { + background.clearColorFilter(); + } else { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + background.setColorFilter(new PorterDuffColorFilter((int)color, PorterDuff.Mode.SRC_IN)); + } + else { + background.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } + } + + public static void setInverted(ReactSlider view, boolean inverted) { + if (inverted) view.setScaleX(-1f); + else view.setScaleX(1f); + } + + public static void setAccessibilityUnits(ReactSlider view, String accessibilityUnits) { + view.setAccessibilityUnits(accessibilityUnits); + } + + public static void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibilityIncrements) { + List objectList = accessibilityIncrements.toArrayList(); + List stringList = new ArrayList<>(); + for(Object item: objectList) { + stringList.add((String)item); + } + view.setAccessibilityIncrements(stringList); + } + + public static Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.of(ReactSlidingCompleteEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRNCSliderSlidingComplete"), + ReactSlidingStartEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRNCSliderSlidingStart")); + } +} diff --git a/package/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java b/package/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java new file mode 100644 index 00000000..e87e7b46 --- /dev/null +++ b/package/android/src/newarch/java/com/reactnativecommunity/slider/ReactSliderManager.java @@ -0,0 +1,209 @@ +package com.reactnativecommunity.slider; + +import android.widget.SeekBar; +import androidx.annotation.Nullable; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerHelper; +import com.facebook.react.uimanager.ViewManagerDelegate; +import com.facebook.react.uimanager.ViewProps; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.events.EventDispatcher; +import java.util.Map; +import com.facebook.react.viewmanagers.RNCSliderManagerInterface; +import com.facebook.react.viewmanagers.RNCSliderManagerDelegate; +import com.facebook.react.module.annotations.ReactModule; + +/** + * Manages instances of {@code ReactSlider}. + */ +@ReactModule(name = ReactSliderManagerImpl.REACT_CLASS) +public class ReactSliderManager extends SimpleViewManager implements RNCSliderManagerInterface { + + private final ViewManagerDelegate mDelegate; + + public ReactSliderManager() { + mDelegate = new RNCSliderManagerDelegate<>(this); + } + + @Nullable + @Override + protected ViewManagerDelegate getDelegate() { + return mDelegate; + } + + private static final SeekBar.OnSeekBarChangeListener ON_CHANGE_LISTENER = + new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { + ReactContext reactContext = (ReactContext) seekbar.getContext(); + int reactTag = seekbar.getId(); + UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag) + .dispatchEvent(new ReactSliderEvent(reactTag, ((ReactSlider)seekbar).toRealProgress(progress), fromUser)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekbar) { + ReactContext reactContext = (ReactContext) seekbar.getContext(); + int reactTag = seekbar.getId(); + ((ReactSlider)seekbar).isSliding(true); + UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag) + .dispatchEvent(new ReactSlidingStartEvent( + reactTag, + ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); + } + + @Override + public void onStopTrackingTouch(SeekBar seekbar) { + ReactContext reactContext = (ReactContext) seekbar.getContext(); + ((ReactSlider)seekbar).isSliding(false); + int reactTag = seekbar.getId(); + + EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, reactTag); + + eventDispatcher.dispatchEvent( + new ReactSlidingCompleteEvent( + reactTag, + ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress())) + ); + eventDispatcher.dispatchEvent( + new ReactSliderEvent( + reactTag, + ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()), + !((ReactSlider)seekbar).isSliding())); + } + }; + + @Override + public String getName() { + return ReactSliderManagerImpl.REACT_CLASS; + } + + @Override + public LayoutShadowNode createShadowNodeInstance() { + return new ReactSliderManagerImpl.ReactSliderShadowNode(); + } + + @Override + public Class getShadowNodeClass() { + return ReactSliderManagerImpl.ReactSliderShadowNode.class; + } + + @Override + protected ReactSlider createViewInstance(ThemedReactContext context) { + return ReactSliderManagerImpl.createViewInstance(context); + } + + @Override + @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true) + public void setEnabled(ReactSlider view, boolean enabled) { + ReactSliderManagerImpl.setEnabled(view, enabled); + } + + @Override + @ReactProp(name = "value", defaultFloat = 0f) + public void setValue(ReactSlider view, float value) { + ReactSliderManagerImpl.setValue(view, value); + } + + @Override + @ReactProp(name = "minimumValue", defaultFloat = 0f) + public void setMinimumValue(ReactSlider view, float value) { + ReactSliderManagerImpl.setMinimumValue(view, value); + } + + @Override + @ReactProp(name = "maximumValue", defaultFloat = 0f) + public void setMaximumValue(ReactSlider view, float value) { + ReactSliderManagerImpl.setMaximumValue(view, value); + } + + @Override + @ReactProp(name = "step", defaultFloat = 0f) + public void setStep(ReactSlider view, float value) { + ReactSliderManagerImpl.setStep(view, value); + } + + @Override + @ReactProp(name = "thumbTintColor", customType = "Color") + public void setThumbTintColor(ReactSlider view, Integer color) { + ReactSliderManagerImpl.setThumbTintColor(view, color); + } + + @Override + @ReactProp(name = "minimumTrackTintColor", customType = "Color") + public void setMinimumTrackTintColor(ReactSlider view, Integer color) { + ReactSliderManagerImpl.setMinimumTrackTintColor(view, color); + } + + @Override + @ReactProp(name = "maximumTrackTintColor", customType = "Color") + public void setMaximumTrackTintColor(ReactSlider view, Integer color) { + ReactSliderManagerImpl.setMaximumTrackTintColor(view, color); + } + + @Override + @ReactProp(name = "inverted", defaultBoolean = false) + public void setInverted(ReactSlider view, boolean inverted) { + ReactSliderManagerImpl.setInverted(view, inverted); + } + + @Override + @ReactProp(name = "accessibilityUnits") + public void setAccessibilityUnits(ReactSlider view, String accessibilityUnits) { + ReactSliderManagerImpl.setAccessibilityUnits(view, accessibilityUnits); + } + + @Override + @ReactProp(name = "accessibilityIncrements") + public void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibilityIncrements) { + ReactSliderManagerImpl.setAccessibilityIncrements(view, accessibilityIncrements); + } + + @Override + @ReactProp(name = "thumbImage") + public void setThumbImage(ReactSlider view, @androidx.annotation.Nullable ReadableMap source) { + ReactSliderManagerImpl.setThumbImage(view, source); + } + + @Override + public void setTestID(ReactSlider view, @Nullable String value) { + super.setTestId(view, value); + } + + @Override + protected void addEventEmitters(final ThemedReactContext reactContext, final ReactSlider view) { + view.setOnSeekBarChangeListener(ON_CHANGE_LISTENER); + } + + @Override + public Map getExportedCustomDirectEventTypeConstants() { + return ReactSliderManagerImpl.getExportedCustomDirectEventTypeConstants(); + } + + // these props are not available on Android, however we must override their setters + @Override + @ReactProp(name = "disabled") + public void setDisabled(ReactSlider view, boolean disabled) {} + + @Override + public void setMinimumTrackImage(ReactSlider view, @Nullable ReadableMap readableMap) {} + + @Override + public void setMaximumTrackImage(ReactSlider view, @Nullable ReadableMap readableMap) {} + + @Override + public void setTrackImage(ReactSlider view, @Nullable ReadableMap value) {} + + @Override + public void setTapToSeek(ReactSlider view, boolean value) {} + + @Override + public void setVertical(ReactSlider view, boolean value) {} +} diff --git a/package/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java b/package/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java new file mode 100644 index 00000000..b76b4417 --- /dev/null +++ b/package/android/src/oldarch/java/com/reactnativecommunity/slider/ReactSliderManager.java @@ -0,0 +1,148 @@ +package com.reactnativecommunity.slider; + +import android.widget.SeekBar; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.SimpleViewManager; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewProps; +import com.facebook.react.uimanager.annotations.ReactProp; +import java.util.Map; +import javax.annotation.Nullable; + +/** + * Manages instances of {@code ReactSlider}. + */ +public class ReactSliderManager extends SimpleViewManager { + + private static final SeekBar.OnSeekBarChangeListener ON_CHANGE_LISTENER = + new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) { + ReactContext reactContext = (ReactContext) seekbar.getContext(); + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( + new ReactSliderEvent( + seekbar.getId(), + ((ReactSlider)seekbar).toRealProgress(progress), fromUser)); + } + + @Override + public void onStartTrackingTouch(SeekBar seekbar) { + ReactContext reactContext = (ReactContext) seekbar.getContext(); + ((ReactSlider)seekbar).isSliding(true); + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( + new ReactSlidingStartEvent( + seekbar.getId(), + ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); + } + + @Override + public void onStopTrackingTouch(SeekBar seekbar) { + ReactContext reactContext = (ReactContext) seekbar.getContext(); + ((ReactSlider)seekbar).isSliding(false); + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( + new ReactSlidingCompleteEvent( + seekbar.getId(), + ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()))); + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent( + new ReactSliderEvent( + seekbar.getId(), + ((ReactSlider)seekbar).toRealProgress(seekbar.getProgress()), + !((ReactSlider)seekbar).isSliding())); + } + }; + + @Override + public String getName() { + return ReactSliderManagerImpl.REACT_CLASS; + } + + @Override + public LayoutShadowNode createShadowNodeInstance() { + return new ReactSliderManagerImpl.ReactSliderShadowNode(); + } + + @Override + public Class getShadowNodeClass() { + return ReactSliderManagerImpl.ReactSliderShadowNode.class; + } + + @Override + protected ReactSlider createViewInstance(ThemedReactContext context) { + return ReactSliderManagerImpl.createViewInstance(context); + } + + @ReactProp(name = ViewProps.ENABLED, defaultBoolean = true) + public void setEnabled(ReactSlider view, boolean enabled) { + ReactSliderManagerImpl.setEnabled(view, enabled); + } + + @ReactProp(name = "value", defaultFloat = 0f) + public void setValue(ReactSlider view, float value) { + ReactSliderManagerImpl.setValue(view, value); + } + + @ReactProp(name = "minimumValue", defaultFloat = 0f) + public void setMinimumValue(ReactSlider view, float value) { + ReactSliderManagerImpl.setMinimumValue(view, value); + } + + @ReactProp(name = "maximumValue", defaultFloat = 1f) + public void setMaximumValue(ReactSlider view, float value) { + ReactSliderManagerImpl.setMaximumValue(view, value); + } + + @ReactProp(name = "step", defaultFloat = 0f) + public void setStep(ReactSlider view, float value) { + ReactSliderManagerImpl.setStep(view, value); + } + + @ReactProp(name = "thumbTintColor", customType = "Color") + public void setThumbTintColor(ReactSlider view, Integer color) { + ReactSliderManagerImpl.setThumbTintColor(view, color); + } + + @ReactProp(name = "minimumTrackTintColor", customType = "Color") + public void setMinimumTrackTintColor(ReactSlider view, Integer color) { + ReactSliderManagerImpl.setMinimumTrackTintColor(view, color); + } + + @ReactProp(name = "thumbImage") + public void setThumbImage(ReactSlider view, @Nullable ReadableMap source) { + ReactSliderManagerImpl.setThumbImage(view, source); + } + + @ReactProp(name = "maximumTrackTintColor", customType = "Color") + public void setMaximumTrackTintColor(ReactSlider view, Integer color) { + ReactSliderManagerImpl.setMaximumTrackTintColor(view, color); + } + + @ReactProp(name = "inverted", defaultBoolean = false) + public void setInverted(ReactSlider view, boolean inverted) { + ReactSliderManagerImpl.setInverted(view, inverted); + } + + @ReactProp(name = "accessibilityUnits") + public void setAccessibilityUnits(ReactSlider view, String accessibilityUnits) { + ReactSliderManagerImpl.setAccessibilityUnits(view, accessibilityUnits); + } + + @ReactProp(name = "accessibilityIncrements") + public void setAccessibilityIncrements(ReactSlider view, ReadableArray accessibilityIncrements) { + ReactSliderManagerImpl.setAccessibilityIncrements(view, accessibilityIncrements); + } + + @Override + protected void addEventEmitters(final ThemedReactContext reactContext, final ReactSlider view) { + view.setOnSeekBarChangeListener(ON_CHANGE_LISTENER); + } + + @Override + public Map getExportedCustomDirectEventTypeConstants() { + return ReactSliderManagerImpl.getExportedCustomDirectEventTypeConstants(); + } +} diff --git a/package/src/RNCSliderNativeComponent.js b/package/src/RNCSliderNativeComponent.js index 09ad0cfd..2075818b 100644 --- a/package/src/RNCSliderNativeComponent.js +++ b/package/src/RNCSliderNativeComponent.js @@ -18,6 +18,7 @@ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNati import type { Float, BubblingEventHandler, + WithDefault, } from 'react-native/Libraries/Types/CodegenTypes'; type Event = $ReadOnly<{| @@ -29,11 +30,11 @@ type NativeProps = $ReadOnly<{| ...ViewProps, accessibilityUnits?: string, accessibilityIncrements?: $ReadOnlyArray, - disabled?: ?boolean, - enabled?: ?boolean, - inverted?: ?boolean, + disabled?: WithDefault, + enabled?: WithDefault, + inverted?: WithDefault, vertical?: ?boolean, - tapToSeek?: ?boolean, + tapToSeek?: WithDefault, maximumTrackImage?: ?ImageSource, maximumTrackTintColor?: ?ColorValue, maximumValue?: ?Float, diff --git a/package/src/Slider.js b/package/src/Slider.js index fbd70f80..9d7a7211 100644 --- a/package/src/Slider.js +++ b/package/src/Slider.js @@ -292,6 +292,7 @@ const SliderComponent = ( return ( true} onResponderTerminationRequest={() => false} - accessibilityState={_accessibilityState} onRNCSliderAccessibilityAction={onAccessibilityActionEvent} /> );