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..87fe7173 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,28 @@ 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}" 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" + else + s.exclude_files = "ios/fabric" + + s.dependency "React-Core" + end end diff --git a/example/App.js b/example/App.js index ed6017da..c5121131 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..9186d263 --- /dev/null +++ b/example/SegmentedControl.js @@ -0,0 +1,7 @@ +import {isFabricEnabled} from '../src/utils'; + +import SegmentedControl from '@react-native-segmented-control/segmented-control'; +import JSSegmentedControl from '@react-native-segmented-control/segmented-control/js/SegmentedControl.js'; + +// 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.lock b/example/ios/Podfile.lock index 856293bb..e6a644b4 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -570,7 +570,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/example/ios/date-time-picker-example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/date-time-picker-example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/example/ios/date-time-picker-example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/RNDateTimePicker.xcodeproj/project.pbxproj b/ios/RNDateTimePicker.xcodeproj/project.pbxproj index be6be629..980cdd3d 100644 --- a/ios/RNDateTimePicker.xcodeproj/project.pbxproj +++ b/ios/RNDateTimePicker.xcodeproj/project.pbxproj @@ -6,11 +6,6 @@ objectVersion = 46; objects = { -/* Begin PBXBuildFile section */ - 1415EF09223110200027D3C6 /* RNDateTimePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1415EF07223110200027D3C6 /* RNDateTimePickerManager.m */; }; - B3E7B58A1CC2AC0600A0062D /* RNDateTimePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNDateTimePicker.m */; }; -/* End PBXBuildFile section */ - /* Begin PBXCopyFilesBuildPhase section */ 58B511D91A9E6C8500147676 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; @@ -25,10 +20,11 @@ /* Begin PBXFileReference section */ 134814201AA4EA6300B7C361 /* libRNDateTimePicker.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNDateTimePicker.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 1415EF07223110200027D3C6 /* RNDateTimePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNDateTimePickerManager.m; sourceTree = ""; }; - 1415EF08223110200027D3C6 /* RNDateTimePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNDateTimePickerManager.h; sourceTree = ""; }; - B3E7B5881CC2AC0600A0062D /* RNDateTimePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNDateTimePicker.h; sourceTree = ""; }; - B3E7B5891CC2AC0600A0062D /* RNDateTimePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNDateTimePicker.m; sourceTree = ""; }; + 7C1CCA3B28D0D66200DABF4D /* fabric */ = {isa = PBXFileReference; lastKnownFileType = folder; path = fabric; sourceTree = ""; }; + 7C1CCA3D28D227F800DABF4D /* RNDateTimePicker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNDateTimePicker.m; sourceTree = ""; }; + 7C1CCA3E28D227F800DABF4D /* RNDateTimePicker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNDateTimePicker.h; sourceTree = ""; }; + 7C1CCA3F28D227F900DABF4D /* RNDateTimePickerManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNDateTimePickerManager.h; sourceTree = ""; }; + 7C1CCA4028D227F900DABF4D /* RNDateTimePickerManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNDateTimePickerManager.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,10 +49,11 @@ 58B511D21A9E6C8500147676 = { isa = PBXGroup; children = ( - 1415EF08223110200027D3C6 /* RNDateTimePickerManager.h */, - 1415EF07223110200027D3C6 /* RNDateTimePickerManager.m */, - B3E7B5881CC2AC0600A0062D /* RNDateTimePicker.h */, - B3E7B5891CC2AC0600A0062D /* RNDateTimePicker.m */, + 7C1CCA3E28D227F800DABF4D /* RNDateTimePicker.h */, + 7C1CCA3D28D227F800DABF4D /* RNDateTimePicker.m */, + 7C1CCA3F28D227F900DABF4D /* RNDateTimePickerManager.h */, + 7C1CCA4028D227F900DABF4D /* RNDateTimePickerManager.m */, + 7C1CCA3B28D0D66200DABF4D /* fabric */, 134814211AA4EA7D00B7C361 /* Products */, ); sourceTree = ""; @@ -118,8 +115,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1415EF09223110200027D3C6 /* RNDateTimePickerManager.m in Sources */, - B3E7B58A1CC2AC0600A0062D /* RNDateTimePicker.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/fabric/RNDateTimePickerComponentView.h b/ios/fabric/RNDateTimePickerComponentView.h new file mode 100644 index 00000000..9b54089e --- /dev/null +++ b/ios/fabric/RNDateTimePickerComponentView.h @@ -0,0 +1,9 @@ +#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..e3230855 --- /dev/null +++ b/ios/fabric/RNDateTimePickerComponentView.mm @@ -0,0 +1,206 @@ +#import "RNDateTimePickerComponentView.h" + +#import + +#import +#import +#import +#import + +#import "RCTFabricComponentsPlugins.h" +#import "RNDateTimePicker.h" + +using namespace facebook::react; + +@interface RNDateTimePickerComponentView () +@end + +@implementation RNDateTimePickerComponentView { + UIDatePicker *_datePickerView; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + static const auto defaultProps = std::make_shared(); + _props = defaultProps; + + _datePickerView = [[RNDateTimePicker alloc] initWithFrame:self.bounds]; + + [_datePickerView addTarget:self action:@selector(onChange:) forControlEvents:UIControlEventValueChanged]; + + // Default Picker mode + _datePickerView.datePickerMode = UIDatePickerModeDate; + + self.contentView = _datePickerView; + } + + 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); +} + +#pragma mark - RCTComponentViewProtocol + ++ (ComponentDescriptorProvider)componentDescriptorProvider +{ + return concreteComponentDescriptorProvider(); +} + +// JS Standard for time is milliseconds +NSDate* convertJSTimeToDate (double jsTime) { + double time = jsTime/1000.0; + return [NSDate dateWithTimeIntervalSince1970: time]; +} + +-(void)updateTextColor:(UIColor *)color +{ + if (@available(iOS 14.0, *)) { + if (_datePickerView.datePickerStyle != UIDatePickerStyleWheels) { + // prevents #247 + return; + } + } + + if (color == nil) { + // Default Text color + if (@available(iOS 13.0, *)) { + color = [UIColor labelColor]; + } else { + color = [UIColor blackColor]; + } + } + + [_datePickerView setValue:color forKey:@"textColor"]; + [_datePickerView setValue:@(NO) forKey:@"highlightsToday"]; +} + +- (void)updateProps:(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); + + if (oldPickerProps.date != newPickerProps.date) { + _datePickerView.date = convertJSTimeToDate(newPickerProps.date); + } + + if (oldPickerProps.minimumDate != newPickerProps.minimumDate) { + _datePickerView.minimumDate = convertJSTimeToDate(newPickerProps.minimumDate); + } + + if (oldPickerProps.maximumDate != newPickerProps.maximumDate) { + _datePickerView.maximumDate = convertJSTimeToDate(newPickerProps.maximumDate); + } + + if (oldPickerProps.locale != newPickerProps.locale) { + NSString *convertedLocale = RCTNSStringFromString(newPickerProps.locale); + NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:convertedLocale]; + + _datePickerView.locale = locale; + } + + if (oldPickerProps.mode != newPickerProps.mode) { + switch(newPickerProps.mode) { + case RNDateTimePickerMode::Time: + _datePickerView.datePickerMode = UIDatePickerModeTime; + break; + case RNDateTimePickerMode::Datetime: + _datePickerView.datePickerMode = UIDatePickerModeDateAndTime; + break; + case RNDateTimePickerMode::Countdown: + _datePickerView.datePickerMode = UIDatePickerModeCountDownTimer; + break; + default: + _datePickerView.datePickerMode = UIDatePickerModeDate; + } + } + + if (@available(iOS 14.0, *)) { + if (oldPickerProps.displayIOS != newPickerProps.displayIOS) { + switch(newPickerProps.displayIOS) { + case RNDateTimePickerDisplayIOS::Compact: + _datePickerView.preferredDatePickerStyle = UIDatePickerStyleCompact; + break; + case RNDateTimePickerDisplayIOS::Inline: + _datePickerView.preferredDatePickerStyle = UIDatePickerStyleInline; + break; + case RNDateTimePickerDisplayIOS::Spinner: + _datePickerView.preferredDatePickerStyle = UIDatePickerStyleWheels; + break; + default: + _datePickerView.preferredDatePickerStyle = UIDatePickerStyleAutomatic; + } + } + } + + if (oldPickerProps.minuteInterval != newPickerProps.minuteInterval) { + _datePickerView.minuteInterval = newPickerProps.minuteInterval; + } + + if (oldPickerProps.timeZoneOffsetInMinutes != newPickerProps.timeZoneOffsetInMinutes) { + // JS standard for time zones is minutes. + _datePickerView.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:newPickerProps.timeZoneOffsetInMinutes * 60.0]; + } + + if (oldPickerProps.accentColor != newPickerProps.accentColor) { + UIColor *color = RCTUIColorFromSharedColor(newPickerProps.accentColor); + + if (color != nil) { + [_datePickerView setTintColor:color]; + } else { + if (@available(iOS 15.0, *)) { + [_datePickerView setTintColor:[UIColor tintColor]]; + } else { + [_datePickerView setTintColor:[UIColor systemBlueColor]]; + } + } + } + + if (oldPickerProps.textColor != newPickerProps.textColor) { + [self updateTextColor:RCTUIColorFromSharedColor(newPickerProps.textColor)]; + } + + if (@available(iOS 13.0, *)) { + if (oldPickerProps.themeVariant != newPickerProps.themeVariant) { + switch (newPickerProps.themeVariant) { + case RNDateTimePickerThemeVariant::Light: + _datePickerView.overrideUserInterfaceStyle = UIUserInterfaceStyleLight; + break; + case RNDateTimePickerThemeVariant::Dark: + _datePickerView.overrideUserInterfaceStyle = UIUserInterfaceStyleDark; + break; + default: + _datePickerView.overrideUserInterfaceStyle = UIUserInterfaceStyleUnspecified; + } + } + } + + if (oldPickerProps.enabled != newPickerProps.enabled) { + _datePickerView.enabled = newPickerProps.enabled; + } + + + [super updateProps:props oldProps:oldProps]; +} + +@end + +Class RNDateTimePickerCls(void) +{ + return RNDateTimePickerComponentView.class; +} + diff --git a/package.json b/package.json index bfa156cb..61aaa3ba 100644 --- a/package.json +++ b/package.json @@ -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 fed4dfee..44bc56e1 100644 --- a/src/datetimepicker.ios.js +++ b/src/datetimepicker.ios.js @@ -10,7 +10,7 @@ * @flow strict-local */ import RNDateTimePicker from './picker'; -import {sharedPropsValidation, toMilliseconds} from './utils'; +import {isFabricEnabled, sharedPropsValidation, toMilliseconds} from './utils'; import {IOS_DISPLAY, ANDROID_MODE, EVENT_TYPE_SET} from './constants'; import invariant from 'invariant'; import * as React from 'react'; @@ -18,13 +18,13 @@ import {getPickerHeightStyle} from './layoutUtilsIOS'; import {Platform, StyleSheet} 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); @@ -67,6 +67,11 @@ export default function Picker({ React.useEffect( function ensureNativeIsInSyncWithJS() { + if (isFabricEnabled) { + // we don't need this workaround when fabric is enabled + return; + } + const {current} = _picker; if (value && onChange && current) { @@ -111,7 +116,6 @@ export default function Picker({ 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/utils.js b/src/utils.js index cc7e87b3..f8f124d8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -30,3 +30,5 @@ export function sharedPropsValidation({value}: {value: ?Date}) { '`value` prop must be an instance of Date object', ); } + +export const isFabricEnabled = global.nativeFabricUIManager !== null;