From 8df590b51e80dfa5cd81bb5a4281de2f6ce139e5 Mon Sep 17 00:00:00 2001 From: Alfonso Curbelo Date: Tue, 27 Sep 2022 05:37:43 -0400 Subject: [PATCH] feat(ios): add Fabric support (#657) * Initial changes * test fabric in CI * Sending picker size to shadow node * Fix E2E config * Update Xcode in CI * Adding more comments * cleanup Co-authored-by: Alfonso Curbelo --- .circleci/config.yml | 5 +- .gitignore | 1 + CONTRIBUTING.md | 27 ++ RNDateTimePicker.podspec | 33 ++- .../RNDateTimePicker/ComponentDescriptors.h | 42 +++ .../RNDateTimePickerState.cpp | 12 + .../RNDateTimePicker/RNDateTimePickerState.h | 23 ++ .../RNDateTimePicker/ShadowNodes.cpp | 17 ++ .../components/RNDateTimePicker/ShadowNodes.h | 39 +++ example/App.js | 2 +- example/SegmentedControl.js | 7 + example/ios/Podfile | 4 +- example/ios/Podfile.lock | 3 +- .../contents.xcworkspacedata | 10 + ios/fabric/RNDateTimePickerComponentView.h | 13 + ios/fabric/RNDateTimePickerComponentView.mm | 272 ++++++++++++++++++ package.json | 13 +- src/datetimepicker.ios.js | 32 +-- src/picker.ios.js | 8 +- src/specs/DateTimePickerNativeComponent.js | 40 +++ src/types.js | 8 +- src/utils.js | 7 + yarn.lock | 8 +- 23 files changed, 573 insertions(+), 53 deletions(-) create mode 100644 cpp/react/renderer/components/RNDateTimePicker/ComponentDescriptors.h create mode 100644 cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.cpp create mode 100644 cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.h create mode 100644 cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.cpp create mode 100644 cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.h create mode 100644 example/SegmentedControl.js create mode 100644 example/ios/date-time-picker-example.xcworkspace/contents.xcworkspacedata create mode 100644 ios/fabric/RNDateTimePickerComponentView.h create mode 100644 ios/fabric/RNDateTimePickerComponentView.mm create mode 100644 src/specs/DateTimePickerNativeComponent.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 23b3274e..e4492363 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,7 +33,7 @@ jobs: e2e_release_ios: executor: name: rn/macos - xcode_version: '13.1.0' + xcode_version: '14.0.0' steps: - checkout - run: @@ -52,9 +52,6 @@ jobs: name: bundle js - rn/pod_install: pod_install_directory: 'example/ios' - - run: - command: curl https://raw.githubusercontent.com/facebook/react-native/6334ac35ac3cbc2c84b2d46d46ec118bf9bf714d/scripts/find-node.sh > node_modules/react-native/scripts/find-node.sh - name: fix issue with nvm # will be fixed in RN 67 (https://github.com/react-native-community/upgrade-support/issues/138) - run: command: yarn detox:ios:build:release name: build app for e2e tests diff --git a/.gitignore b/.gitignore index 8d5bc0b8..2145c4cf 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ DerivedData *.ipa *.xcuserstate project.xcworkspace +IDEWorkspaceChecks.plist #Detox # diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c880b170..bf8c37c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -68,3 +68,30 @@ An existing Android emulator is required to match the name defined in `detox.con yarn detox:android:build:release yarn detox:android:test:release ``` + +### Fabric + +Fabric is the new React Native rendering system ([read more about it here](https://reactnative.dev/architecture/fabric-renderer)). + +#### iOS + +``` +yarn start +cd "example/ios" && RCT_NEW_ARCH_ENABLED=1 npx pod-install && cd - +yarn start:ios +``` + +If you want to go back to the old renderer (Paper), +remove `ios/build`, run `pod-install` without the `RCT_NEW_ARCH_ENABLED=1` and build again + +``` +rm -r "example/ios/build" +cd "example/ios" && npx pod-install && cd - +yarn start:ios +``` + + +#### Android + +The date time picker does not have a native UI component for Android but a native module. +([read more about native modules here](https://reactnative.dev/docs/native-modules-intro)). \ No newline at end of file diff --git a/RNDateTimePicker.podspec b/RNDateTimePicker.podspec index ae7cb4db..d60e0695 100644 --- a/RNDateTimePicker.podspec +++ b/RNDateTimePicker.podspec @@ -1,5 +1,7 @@ require 'json' +fabric_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1' + package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) Pod::Spec.new do |s| @@ -12,8 +14,35 @@ Pod::Spec.new do |s| s.homepage = package['homepage'] s.platform = :ios, "11.0" s.source = { :git => "https://github.com/react-native-community/datetimepicker", :tag => "v#{s.version}" } - s.source_files = "ios/*.{h,m}" + s.source_files = "ios/**/*.{h,m,mm,cpp}" s.requires_arc = true - s.dependency "React-Core" + if fabric_enabled + folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + 'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/boost" "$(PODS_ROOT)/boost-for-react-native" "$(PODS_ROOT)/RCT-Folly"', + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + + s.dependency "React" + s.dependency "React-RCTFabric" + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + + s.subspec "cpp" do |ss| + ss.source_files = "cpp/**/*.{cpp,h}" + ss.header_dir = "rndatetimepicker" + ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp\"" } + end + + else + s.exclude_files = "ios/fabric" + + s.dependency "React-Core" + end end diff --git a/cpp/react/renderer/components/RNDateTimePicker/ComponentDescriptors.h b/cpp/react/renderer/components/RNDateTimePicker/ComponentDescriptors.h new file mode 100644 index 00000000..4a1a8e24 --- /dev/null +++ b/cpp/react/renderer/components/RNDateTimePicker/ComponentDescriptors.h @@ -0,0 +1,42 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * and copied to the cpp directory to override the adopt function and set the size of the shadow node based + * on the state. + * @generated by codegen project: GenerateComponentDescriptorH.js + */ + +#pragma once + +#include "ShadowNodes.h" +#include + +namespace facebook { +namespace react { + +class RNDateTimePickerComponentDescriptor final : public ConcreteComponentDescriptor { + public: + using ConcreteComponentDescriptor::ConcreteComponentDescriptor; + + void adopt(ShadowNode::Unshared const &shadowNode) const override { + react_native_assert(std::dynamic_pointer_cast(shadowNode)); + auto pickerShadowNode = std::static_pointer_cast(shadowNode); + + react_native_assert( + std::dynamic_pointer_cast(pickerShadowNode)); + auto layoutableShadowNode = + std::static_pointer_cast(pickerShadowNode); + + auto state = std::static_pointer_cast(shadowNode->getState()); + auto stateData = state->getData(); + + if(stateData.frameSize.width != 0 && stateData.frameSize.height != 0) { + layoutableShadowNode->setSize(Size{stateData.frameSize.width, stateData.frameSize.height}); + } + + ConcreteComponentDescriptor::adopt(shadowNode); + } +}; + +} // namespace react +} // namespace facebook diff --git a/cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.cpp b/cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.cpp new file mode 100644 index 00000000..d5177a9e --- /dev/null +++ b/cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.cpp @@ -0,0 +1,12 @@ +/** + * Custom state to store frameSize that the component descriptor will use to modify the + * shadow node layout. + */ + +#include "RNDateTimePickerState.h" + +namespace facebook { +namespace react { + +} // namespace react +} // namespace facebook \ No newline at end of file diff --git a/cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.h b/cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.h new file mode 100644 index 00000000..6215e17b --- /dev/null +++ b/cpp/react/renderer/components/RNDateTimePicker/RNDateTimePickerState.h @@ -0,0 +1,23 @@ +/** + * Custom state to store frameSize that the component descriptor will use to modify the + * shadow node layout. + */ + +#pragma once + +#include + +namespace facebook { +namespace react { + +class RNDateTimePickerState final { + public: + using Shared = std::shared_ptr; + RNDateTimePickerState(){}; + RNDateTimePickerState(Size frameSize_) : frameSize(frameSize_){}; + + Size frameSize{}; +}; + +} // namespace react +} // namespace facebook diff --git a/cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.cpp b/cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.cpp new file mode 100644 index 00000000..192850e3 --- /dev/null +++ b/cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.cpp @@ -0,0 +1,17 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen) + * and copied to the cpp directory to add custom state and set shadow node trait as a LeafYogaNode. + * + * @generated by codegen project: GenerateShadowNodeCpp.js + */ + +#include "ShadowNodes.h" + +namespace facebook { +namespace react { + +extern const char RNDateTimePickerComponentName[] = "RNDateTimePicker"; + +} // namespace react +} // namespace facebook diff --git a/cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.h b/cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.h new file mode 100644 index 00000000..dd2b6efc --- /dev/null +++ b/cpp/react/renderer/components/RNDateTimePicker/ShadowNodes.h @@ -0,0 +1,39 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen) + * and copied to the cpp directory to add custom state and set shadow node trait as a LeafYogaNode. + * + * @generated by codegen project: GenerateShadowNodeH.js + */ + +#pragma once + +#include "RNDateTimePickerState.h" +#include +#include +#include +#include +#include + +namespace facebook { +namespace react { + +JSI_EXPORT extern const char RNDateTimePickerComponentName[]; + +/* + * `ShadowNode` for component. + */ +class JSI_EXPORT RNDateTimePickerShadowNode final : public ConcreteViewShadowNode { + +public: + using ConcreteViewShadowNode::ConcreteViewShadowNode; + + static ShadowNodeTraits BaseTraits() { + auto traits = ConcreteViewShadowNode::BaseTraits(); + traits.set(ShadowNodeTraits::Trait::LeafYogaNode); + return traits; + } +}; + +} // namespace react +} // namespace facebook diff --git a/example/App.js b/example/App.js index 1b370850..334d2f8f 100644 --- a/example/App.js +++ b/example/App.js @@ -12,7 +12,7 @@ import { Switch, } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; -import SegmentedControl from '@react-native-segmented-control/segmented-control'; +import SegmentedControl from './SegmentedControl'; import {Colors} from 'react-native/Libraries/NewAppScreen'; import React, {useRef, useState} from 'react'; import {Picker} from 'react-native-windows'; diff --git a/example/SegmentedControl.js b/example/SegmentedControl.js new file mode 100644 index 00000000..01869141 --- /dev/null +++ b/example/SegmentedControl.js @@ -0,0 +1,7 @@ +import SegmentedControl from '@react-native-segmented-control/segmented-control'; +import JSSegmentedControl from '@react-native-segmented-control/segmented-control/js/SegmentedControl.js'; + +const isFabricEnabled = global.nativeFabricUIManager !== null; + +// Forcing the JS implementation for Fabric as the native module is not compatible with Fabric yet. +export default isFabricEnabled ? JSSegmentedControl : SegmentedControl; diff --git a/example/ios/Podfile b/example/ios/Podfile index 4f115443..cb397779 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,6 +1,8 @@ require_relative '../../node_modules/react-native-test-app/test_app' -use_flipper! +# Flipper causes the build to fail on release when fabric is enabled +# https://github.com/facebook/react-native/issues/33764 +use_flipper!() workspace 'date-time-picker-example.xcworkspace' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 856293bb..115e07b7 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -263,6 +263,7 @@ PODS: - React-logger (= 0.70.0) - React-perflogger (= 0.70.0) - React-runtimeexecutor (= 0.70.0) + - RCT-Folly/Fabric (= 2021.07.22.00) - React-jsi (0.70.0): - boost (= 1.76.0) - DoubleConversion @@ -570,7 +571,7 @@ SPEC CHECKSUMS: ReactCommon: de55f940495d7bf87b5d7bf55b5b15cdd50d7d7b ReactTestApp-DevSupport: 8a8cff38c37cd8145a12ac7d7d0503dd08f97d65 ReactTestApp-Resources: ff5f151e465e890010b417ce65ca6c5de6aeccbb - RNDateTimePicker: 4f1fc917f5af9d9ae4c5fc0c63a4474d61338693 + RNDateTimePicker: 9d66f002d6095cc89fcb66d0dc54bc6191c9ab0d RNLocalize: cbcb55d0e19c78086ea4eea20e03fe8000bbbced SocketRocket: fccef3f9c5cedea1353a9ef6ada904fde10d6608 Yoga: 82c9e8f652789f67d98bed5aef9d6653f71b04a9 diff --git a/example/ios/date-time-picker-example.xcworkspace/contents.xcworkspacedata b/example/ios/date-time-picker-example.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..7b3c0ff9 --- /dev/null +++ b/example/ios/date-time-picker-example.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/fabric/RNDateTimePickerComponentView.h b/ios/fabric/RNDateTimePickerComponentView.h new file mode 100644 index 00000000..738a97d3 --- /dev/null +++ b/ios/fabric/RNDateTimePickerComponentView.h @@ -0,0 +1,13 @@ +/** + * RNDateTimePickerComponentView is only be available when fabric is enabled. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RNDateTimePickerComponentView : RCTViewComponentView + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/fabric/RNDateTimePickerComponentView.mm b/ios/fabric/RNDateTimePickerComponentView.mm new file mode 100644 index 00000000..bf0194f9 --- /dev/null +++ b/ios/fabric/RNDateTimePickerComponentView.mm @@ -0,0 +1,272 @@ +/** + * RNDateTimePickerComponentView is only be available when fabric is enabled. + */ + +#import "RNDateTimePickerComponentView.h" +#import + +#import +#import +#import +#import + +#import "RCTFabricComponentsPlugins.h" +#import "RNDateTimePicker.h" + +using namespace facebook::react; + +// JS Standard for time is milliseconds +NSDate* convertJSTimeToDate (double jsTime) { + double time = jsTime/1000.0; + return [NSDate dateWithTimeIntervalSince1970: time]; +} + +@interface RNDateTimePickerComponentView () +@end + +@implementation RNDateTimePickerComponentView { + UIDatePicker *_picker; + // Dummy picker to apply prop changes and calculate/update the size before the actual picker gets updated + UIDatePicker *_dummyPicker; + RNDateTimePickerShadowNode::ConcreteState::Shared _state; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + _picker = [RNDateTimePicker new]; + _dummyPicker = [RNDateTimePicker new]; + + [_picker addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; + + // Default Picker mode + _picker.datePickerMode = UIDatePickerModeDate; + _dummyPicker.datePickerMode = UIDatePickerModeDate; + + self.contentView = _picker; + } + + return self; +} + +-(void)onChange:(RNDateTimePicker *)sender +{ + if (!_eventEmitter) { + return; + } + + NSTimeInterval timestamp = [sender.date timeIntervalSince1970]; + RNDateTimePickerEventEmitter::OnChange event = { + // Sending time in milliseconds + .timestamp = timestamp * 1000 + }; + + std::dynamic_pointer_cast(_eventEmitter) + ->onChange(event); +} + +/** + * Updates the shadow node state with the dummyPicker size. This will update the shadow node size. + * (see adopt method in ComponentDescriptors.h) + */ +- (void) updateMeasurements { + if (_state == nullptr) { + return; + } + + CGSize size = [_dummyPicker sizeThatFits:UILayoutFittingCompressedSize]; + auto newState = RNDateTimePickerState{RCTSizeFromCGSize(size)}; + _state->updateState(std::move(newState)); +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +- (void)prepareForRecycle +{ + [super prepareForRecycle]; + _state.reset(); +} + +-(void)updateTextColorForPicker:(UIDatePicker *)picker color:(UIColor *)color +{ + if (@available(iOS 14.0, *)) { + if (picker.datePickerStyle != UIDatePickerStyleWheels) { + // prevents #247 + return; + } + } + + if (color) { + [picker setValue:color forKey:@"textColor"]; + [picker setValue:@(NO) forKey:@"highlightsToday"]; + } else { + // Default Text color + if (@available(iOS 13.0, *)) { + color = [UIColor labelColor]; + } else { + color = [UIColor blackColor]; + } + [picker setValue:color forKey:@"textColor"]; + [picker setValue:@(YES) forKey:@"highlightsToday"]; + } +} + +/** + * override update state to update shadow node size once the state is available + */ +- (void)updateState:(const State::Shared &)state oldState:(const State::Shared &)oldState { + _state = std::static_pointer_cast(state); + + if (oldState == nullptr) { + // Calculate the initial picker measurements + [self updateMeasurements]; + } +} + +/** + * Updates picker properties based on prop changes and returns a boolean that indicates if the shadow node size needs + * to be updated. This boolean helpful when we update the dummy picker to know if we need to update the shadow node + * size before updating the actual picker. + * Props that will to update measurements: date, locale, mode, displayIOS. + */ +- (Boolean)updatePropsForPicker:(UIDatePicker *)picker props:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps { + + const auto &oldPickerProps = *std::static_pointer_cast(_props); + const auto &newPickerProps = *std::static_pointer_cast(props); + Boolean needsToUpdateMeasurements = false; + + if (oldPickerProps.date != newPickerProps.date) { + picker.date = convertJSTimeToDate(newPickerProps.date); + needsToUpdateMeasurements = true; + } + + if (oldPickerProps.minimumDate != newPickerProps.minimumDate) { + picker.minimumDate = convertJSTimeToDate(newPickerProps.minimumDate); + } + + if (oldPickerProps.maximumDate != newPickerProps.maximumDate) { + picker.maximumDate = convertJSTimeToDate(newPickerProps.maximumDate); + } + + if (oldPickerProps.locale != newPickerProps.locale) { + NSString *convertedLocale = RCTNSStringFromString(newPickerProps.locale); + NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:convertedLocale]; + + picker.locale = locale; + needsToUpdateMeasurements = true; + } + + if (oldPickerProps.mode != newPickerProps.mode) { + switch(newPickerProps.mode) { + case RNDateTimePickerMode::Time: + picker.datePickerMode = UIDatePickerModeTime; + break; + case RNDateTimePickerMode::Datetime: + picker.datePickerMode = UIDatePickerModeDateAndTime; + break; + case RNDateTimePickerMode::Countdown: + picker.datePickerMode = UIDatePickerModeCountDownTimer; + break; + default: + picker.datePickerMode = UIDatePickerModeDate; + } + needsToUpdateMeasurements = true; + } + + if (@available(iOS 14.0, *)) { + if (oldPickerProps.displayIOS != newPickerProps.displayIOS) { + switch(newPickerProps.displayIOS) { + case RNDateTimePickerDisplayIOS::Compact: + picker.preferredDatePickerStyle = UIDatePickerStyleCompact; + break; + case RNDateTimePickerDisplayIOS::Inline: + picker.preferredDatePickerStyle = UIDatePickerStyleInline; + break; + case RNDateTimePickerDisplayIOS::Spinner: + picker.preferredDatePickerStyle = UIDatePickerStyleWheels; + break; + default: + picker.preferredDatePickerStyle = UIDatePickerStyleAutomatic; + } + needsToUpdateMeasurements = true; + } + } + + if (oldPickerProps.minuteInterval != newPickerProps.minuteInterval) { + picker.minuteInterval = newPickerProps.minuteInterval; + } + + if (oldPickerProps.timeZoneOffsetInMinutes != newPickerProps.timeZoneOffsetInMinutes) { + // JS standard for time zones is minutes. + picker.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:newPickerProps.timeZoneOffsetInMinutes * 60.0]; + } + + if (oldPickerProps.accentColor != newPickerProps.accentColor) { + UIColor *color = RCTUIColorFromSharedColor(newPickerProps.accentColor); + + if (color != nil) { + [picker setTintColor:color]; + } else { + if (@available(iOS 15.0, *)) { + [picker setTintColor:[UIColor tintColor]]; + } else { + [picker setTintColor:[UIColor systemBlueColor]]; + } + } + } + + if (oldPickerProps.textColor != newPickerProps.textColor) { + [self updateTextColorForPicker:picker color:RCTUIColorFromSharedColor(newPickerProps.textColor)]; + } + + if (@available(iOS 13.0, *)) { + if (oldPickerProps.themeVariant != newPickerProps.themeVariant) { + switch (newPickerProps.themeVariant) { + case RNDateTimePickerThemeVariant::Light: + picker.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; + break; + case RNDateTimePickerThemeVariant::Dark: + picker.overrideUserInterfaceStyle = UIUserInterfaceStyleDark; + break; + default: + picker.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified; + } + } + } + + if (oldPickerProps.enabled != newPickerProps.enabled) { + picker.enabled = newPickerProps.enabled; + } + + return needsToUpdateMeasurements; +} + +- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps +{ + // Updating the dummy first to check if we need to update measurements + Boolean needsToUpdateMeasurements = [self updatePropsForPicker:_dummyPicker props:props oldProps:oldProps]; + + if (needsToUpdateMeasurements) { + [self updateMeasurements]; + } + + [self updatePropsForPicker:_picker props:props oldProps:oldProps]; + + [super updateProps:props oldProps:oldProps]; +} + +@end + +Class RNDateTimePickerCls(void) +{ + return RNDateTimePickerComponentView.class; +} + diff --git a/package.json b/package.json index a6e97bec..0a532c1b 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "react": "18.1.0", "react-native": "^0.70.0", "react-native-localize": "^2.2.0", - "react-native-test-app": "^1.6.13", + "react-native-test-app": "^1.6.16", "react-native-windows": "^0.70.0-preview.2", "react-test-renderer": "18.1.0", "semantic-release": "^19.0.3" @@ -105,10 +105,10 @@ "configurations": { "ios.sim.debug": { "binaryPath": "example/ios/build/Build/Products/Debug-iphonesimulator/ReactTestApp.app", - "build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace example/ios/date-time-picker-example.xcworkspace -destination 'platform=iOS Simulator,name=iPhone 11' -scheme date-time-picker-example -configuration Debug -derivedDataPath example/ios/build -UseModernBuildSystem=YES", + "build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace example/ios/date-time-picker-example.xcworkspace -destination 'platform=iOS Simulator,name=iPhone 13' -scheme date-time-picker-example -configuration Debug -derivedDataPath example/ios/build -UseModernBuildSystem=YES", "type": "ios.simulator", "device": { - "type": "iPhone 11" + "type": "iPhone 13" } }, "ios.sim.release": { @@ -116,7 +116,7 @@ "build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace example/ios/date-time-picker-example.xcworkspace -sdk iphonesimulator -scheme date-time-picker-example -configuration Release -derivedDataPath example/ios/build -UseModernBuildSystem=YES", "type": "ios.simulator", "device": { - "type": "iPhone 11" + "type": "iPhone 13" } }, "android.emu.debug": { @@ -144,5 +144,10 @@ } } } + }, + "codegenConfig": { + "name": "RNDateTimePicker", + "type": "components", + "jsSrcsDir": "src/specs" } } diff --git a/src/datetimepicker.ios.js b/src/datetimepicker.ios.js index 5eaa7683..a75a91b9 100644 --- a/src/datetimepicker.ios.js +++ b/src/datetimepicker.ios.js @@ -10,20 +10,18 @@ * @flow strict-local */ import RNDateTimePicker from './picker'; -import {sharedPropsValidation, toMilliseconds} from './utils'; +import {dateToMilliseconds, sharedPropsValidation} from './utils'; import {IOS_DISPLAY, ANDROID_MODE, EVENT_TYPE_SET} from './constants'; import invariant from 'invariant'; import * as React from 'react'; import {Platform} from 'react-native'; import type { + DateTimePickerEvent, NativeEventIOS, - NativeRef, IOSNativeProps, - DatePickerOptions, IOSDisplay, } from './types'; -import type {DateTimePickerEvent} from './types'; const getDisplaySafe = (display: IOSDisplay): IOSDisplay => { const majorVersionIOS = parseInt(Platform.Version, 10); @@ -60,24 +58,8 @@ export default function Picker({ }: IOSNativeProps): React.Node { sharedPropsValidation({value}); - const _picker: NativeRef = React.useRef(null); const display = getDisplaySafe(providedDisplay); - React.useEffect( - function ensureNativeIsInSyncWithJS() { - const {current} = _picker; - - if (value && onChange && current) { - const timestamp = value.getTime(); - // $FlowFixMe Cannot call `current.setNativeProps` because property `setNativeProps` is missing in `AbstractComponent` [1]. - current.setNativeProps({ - date: timestamp, - }); - } - }, - [onChange, value], - ); - const _onChange = (event: NativeEventIOS) => { const timestamp = event.nativeEvent.timestamp; // $FlowFixMe Cannot assign object literal to `unifiedEvent` because number [1] is incompatible with undefined [2] in property `nativeEvent.timestamp`. @@ -90,19 +72,15 @@ export default function Picker({ invariant(value, 'A date or time should be specified as `value`.'); - const dates: DatePickerOptions = {value, maximumDate, minimumDate}; - toMilliseconds(dates, 'value', 'minimumDate', 'maximumDate'); - return ( // $FlowFixMe - dozen of flow errors ; + +type NativeProps = $ReadOnly<{| + ...ViewProps, + onChange?: ?BubblingEventHandler, + maximumDate?: ?Double, + minimumDate?: ?Double, + date?: ?Double, + locale?: ?string, + minuteInterval?: ?Int32, + mode?: WithDefault<'date' | 'time' | 'datetime' | 'countdown', 'date'>, + timeZoneOffsetInMinutes?: ?Double, + textColor?: ?ColorValue, + accentColor?: ?ColorValue, + themeVariant?: WithDefault<'dark' | 'light' | 'unspecified', 'unspecified'>, + displayIOS?: WithDefault< + 'default' | 'spinner' | 'compact' | 'inline', + 'default', + >, + enabled?: WithDefault, +|}>; + +export default (codegenNativeComponent( + 'RNDateTimePicker', +): HostComponent); diff --git a/src/types.js b/src/types.js index 642e13d2..a531b7bf 100644 --- a/src/types.js +++ b/src/types.js @@ -26,9 +26,11 @@ type Display = $Keys; type AndroidEvtTypes = $Keys; type MinuteInterval = ?(1 | 2 | 3 | 4 | 5 | 6 | 10 | 12 | 15 | 20 | 30); -export type NativeEventIOS = SyntheticEvent<{| - timestamp: number, -|}>; +export type NativeEventIOS = SyntheticEvent< + $ReadOnly<{| + timestamp: number, + |}>, +>; export type DateTimePickerEvent = { type: AndroidEvtTypes, diff --git a/src/utils.js b/src/utils.js index cc7e87b3..4df40d25 100644 --- a/src/utils.js +++ b/src/utils.js @@ -23,6 +23,13 @@ export function toMilliseconds( }); } +export function dateToMilliseconds(date: ?Date): ?number { + if (!date) { + return; + } + return date.getTime(); +} + export function sharedPropsValidation({value}: {value: ?Date}) { invariant(value, 'A date or time must be specified as `value` prop'); invariant( diff --git a/yarn.lock b/yarn.lock index efb2b4b3..985e5486 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9366,10 +9366,10 @@ react-native-localize@^2.2.0: resolved "https://registry.yarnpkg.com/react-native-localize/-/react-native-localize-2.2.1.tgz#6fe646833691c6ee8a474df3c8b069402cb1dba8" integrity sha512-BuPaQWvxLZG1NrCDGqgAnecDrNQu3LED9/Pyl4H2LwTMHcEngXpE5PfVntW2GiLumdr6nUOkWmMnh8PynZqrsw== -react-native-test-app@^1.6.13: - version "1.6.13" - resolved "https://registry.yarnpkg.com/react-native-test-app/-/react-native-test-app-1.6.13.tgz#8e8359af9975f8c60e79e9b2f7a4575929380ba1" - integrity sha512-x4rkaOUMuP3zzl++uhmEfOyTnJytOf67XRvwQ/N6ZikfnIs2DSSBdmmIhFw4tvfMDm/ptnQ0D+XyfULSebH/VQ== +react-native-test-app@^1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/react-native-test-app/-/react-native-test-app-1.6.16.tgz#258bf3485b40666cee5a16ad1105bd08263684bb" + integrity sha512-Yl3uCIUrbyxnegamsTe9Am5qeftVG63z0sHsk420RqMog6wj+ttvSgrQ/15XXrxWeagNGtme0ub2YZRxTbFY7g== dependencies: ajv "^8.0.0" chalk "^4.1.0"