react-native 0.73.8 → 0.73.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/Libraries/Components/Keyboard/KeyboardAvoidingView.js +17 -0
  2. package/Libraries/Core/ReactNativeVersion.js +1 -1
  3. package/Libraries/StyleSheet/StyleSheetTypes.d.ts +17 -15
  4. package/Libraries/Text/TextInput/RCTBaseTextInputView.mm +0 -13
  5. package/React/Base/RCTVersion.m +1 -1
  6. package/React/CoreModules/RCTDeviceInfo.mm +4 -4
  7. package/React/Views/RCTModalHostView.m +1 -0
  8. package/ReactAndroid/gradle.properties +1 -1
  9. package/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/ReactNativeVersion.java +1 -1
  10. package/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +8 -0
  11. package/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputKeyPressEvent.java +1 -1
  12. package/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +27 -29
  13. package/ReactCommon/cxxreact/ReactNativeVersion.h +1 -1
  14. package/ReactCommon/react/renderer/animations/LayoutAnimationKeyFrameManager.cpp +1 -4
  15. package/ReactCommon/react/renderer/animations/utils.h +45 -0
  16. package/package.json +6 -6
  17. package/scripts/cocoapods/privacy_manifest_utils.rb +170 -0
  18. package/scripts/cocoapods/utils.rb +5 -39
  19. package/scripts/react_native_pods.rb +11 -2
  20. package/sdks/hermesc/linux64-bin/hermesc +0 -0
  21. package/sdks/hermesc/osx-bin/hermes +0 -0
  22. package/sdks/hermesc/osx-bin/hermesc +0 -0
  23. package/sdks/hermesc/win64-bin/hermesc.exe +0 -0
  24. package/template/package.json +1 -1
  25. package/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputEvent.java +0 -70
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type {ViewStyleProp} from '../../StyleSheet/StyleSheet';
12
+ import type {DimensionsPayload} from '../../Utilities/NativeDeviceInfo';
12
13
  import type {
13
14
  ViewLayout,
14
15
  ViewLayoutEvent,
@@ -18,6 +19,7 @@ import type {KeyboardEvent, KeyboardMetrics} from './Keyboard';
18
19
 
19
20
  import LayoutAnimation from '../../LayoutAnimation/LayoutAnimation';
20
21
  import StyleSheet from '../../StyleSheet/StyleSheet';
22
+ import Dimensions from '../../Utilities/Dimensions';
21
23
  import Platform from '../../Utilities/Platform';
22
24
  import {type EventSubscription} from '../../vendor/emitter/EventEmitter';
23
25
  import AccessibilityInfo from '../AccessibilityInfo/AccessibilityInfo';
@@ -66,6 +68,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
66
68
  viewRef: {current: React.ElementRef<typeof View> | null, ...};
67
69
  _initialFrameHeight: number = 0;
68
70
  _bottom: number = 0;
71
+ _windowWidth: number = Dimensions.get('window').width;
69
72
 
70
73
  constructor(props: Props) {
71
74
  super(props);
@@ -130,6 +133,10 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
130
133
  }
131
134
  };
132
135
 
136
+ _onDimensionsChange = ({window}: DimensionsPayload) => {
137
+ this._windowWidth = window?.width ?? 0;
138
+ };
139
+
133
140
  // Avoid unnecessary renders if the KeyboardAvoidingView is disabled.
134
141
  _setBottom = (value: number) => {
135
142
  const enabled = this.props.enabled ?? true;
@@ -145,6 +152,15 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
145
152
  return;
146
153
  }
147
154
 
155
+ if (
156
+ Platform.OS === 'ios' &&
157
+ this._windowWidth !== this._keyboardEvent.endCoordinates.width
158
+ ) {
159
+ // The keyboard is not the standard bottom-of-the-screen keyboard. For example, floating keyboard on iPadOS.
160
+ this._setBottom(0);
161
+ return;
162
+ }
163
+
148
164
  const {duration, easing, endCoordinates} = this._keyboardEvent;
149
165
  const height = await this._relativeKeyboardHeight(endCoordinates);
150
166
 
@@ -178,6 +194,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
178
194
  if (Platform.OS === 'ios') {
179
195
  this._subscriptions = [
180
196
  Keyboard.addListener('keyboardWillChangeFrame', this._onKeyboardChange),
197
+ Dimensions.addEventListener('change', this._onDimensionsChange),
181
198
  ];
182
199
  } else {
183
200
  this._subscriptions = [
@@ -12,6 +12,6 @@
12
12
  exports.version = {
13
13
  major: 0,
14
14
  minor: 73,
15
- patch: 8,
15
+ patch: 10,
16
16
  prerelease: null,
17
17
  };
@@ -179,21 +179,23 @@ type MaximumOneOf<T, K extends keyof T = keyof T> = K extends keyof T
179
179
 
180
180
  export interface TransformsStyle {
181
181
  transform?:
182
- | MaximumOneOf<
183
- PerpectiveTransform &
184
- RotateTransform &
185
- RotateXTransform &
186
- RotateYTransform &
187
- RotateZTransform &
188
- ScaleTransform &
189
- ScaleXTransform &
190
- ScaleYTransform &
191
- TranslateXTransform &
192
- TranslateYTransform &
193
- SkewXTransform &
194
- SkewYTransform &
195
- MatrixTransform
196
- >[]
182
+ | Readonly<
183
+ MaximumOneOf<
184
+ PerpectiveTransform &
185
+ RotateTransform &
186
+ RotateXTransform &
187
+ RotateYTransform &
188
+ RotateZTransform &
189
+ ScaleTransform &
190
+ ScaleXTransform &
191
+ ScaleYTransform &
192
+ TranslateXTransform &
193
+ TranslateYTransform &
194
+ SkewXTransform &
195
+ SkewYTransform &
196
+ MatrixTransform
197
+ >[]
198
+ >
197
199
  | string
198
200
  | undefined;
199
201
  transformOrigin?: Array<string | number> | string | undefined;
@@ -493,8 +493,6 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
493
493
  }
494
494
  }
495
495
 
496
- NSString *previousText = [backedTextInputView.attributedText.string copy] ?: @"";
497
-
498
496
  if (range.location + range.length > backedTextInputView.attributedText.string.length) {
499
497
  _predictedText = backedTextInputView.attributedText.string;
500
498
  } else if (text != nil) {
@@ -502,17 +500,6 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithFrame : (CGRect)frame)
502
500
  withString:text];
503
501
  }
504
502
 
505
- if (_onTextInput) {
506
- _onTextInput(@{
507
- // We copy the string here because if it's a mutable string it may get released before we stop using it on a
508
- // different thread, causing a crash.
509
- @"text" : [text copy],
510
- @"previousText" : previousText,
511
- @"range" : @{@"start" : @(range.location), @"end" : @(range.location + range.length)},
512
- @"eventCount" : @(_nativeEventCount),
513
- });
514
- }
515
-
516
503
  return text; // Accepting the change.
517
504
  }
518
505
 
@@ -23,7 +23,7 @@ NSDictionary* RCTGetReactNativeVersion(void)
23
23
  __rnVersion = @{
24
24
  RCTVersionMajor: @(0),
25
25
  RCTVersionMinor: @(73),
26
- RCTVersionPatch: @(8),
26
+ RCTVersionPatch: @(10),
27
27
  RCTVersionPrerelease: [NSNull null],
28
28
  };
29
29
  });
@@ -89,6 +89,9 @@ RCT_EXPORT_MODULE()
89
89
 
90
90
  - (void)invalidate
91
91
  {
92
+ if (_invalidated) {
93
+ return;
94
+ }
92
95
  _invalidated = YES;
93
96
  [self _cleanupObservers];
94
97
  }
@@ -109,10 +112,7 @@ RCT_EXPORT_MODULE()
109
112
 
110
113
  [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTWindowFrameDidChangeNotification object:nil];
111
114
 
112
- [[NSNotificationCenter defaultCenter] addObserver:self
113
- selector:@selector(invalidate)
114
- name:RCTBridgeWillInvalidateModulesNotification
115
- object:nil];
115
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTBridgeWillInvalidateModulesNotification object:nil];
116
116
  }
117
117
 
118
118
  static BOOL RCTIsIPhoneNotched()
@@ -119,6 +119,7 @@ RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : coder)
119
119
  if (_isPresented) {
120
120
  [_delegate dismissModalHostView:self withViewController:_modalViewController animated:[self hasAnimationType]];
121
121
  _isPresented = NO;
122
+ [self setVisible:NO];
122
123
  }
123
124
  }
124
125
 
@@ -1,4 +1,4 @@
1
- VERSION_NAME=0.73.8
1
+ VERSION_NAME=0.73.10
2
2
  react.internal.publishingGroup=com.facebook.react
3
3
 
4
4
  android.useAndroidX=true
@@ -17,6 +17,6 @@ public class ReactNativeVersion {
17
17
  public static final Map<String, Object> VERSION = MapBuilder.<String, Object>of(
18
18
  "major", 0,
19
19
  "minor", 73,
20
- "patch", 8,
20
+ "patch", 10,
21
21
  "prerelease", null);
22
22
  }
@@ -119,6 +119,7 @@ public class ReactEditText extends AppCompatEditText {
119
119
  private int mFontWeight = UNSET;
120
120
  private int mFontStyle = UNSET;
121
121
  private boolean mAutoFocus = false;
122
+ private boolean mContextMenuHidden = false;
122
123
  private boolean mDidAttachToWindow = false;
123
124
  private @Nullable String mPlaceholder = null;
124
125
 
@@ -192,6 +193,9 @@ public class ReactEditText extends AppCompatEditText {
192
193
  */
193
194
  @Override
194
195
  public boolean onCreateActionMode(ActionMode mode, Menu menu) {
196
+ if (mContextMenuHidden) {
197
+ return false;
198
+ }
195
199
  menu.removeItem(android.R.id.pasteAsPlainText);
196
200
  return true;
197
201
  }
@@ -1139,6 +1143,10 @@ public class ReactEditText extends AppCompatEditText {
1139
1143
  mAutoFocus = autoFocus;
1140
1144
  }
1141
1145
 
1146
+ public void setContextMenuHidden(boolean contextMenuHidden) {
1147
+ mContextMenuHidden = contextMenuHidden;
1148
+ }
1149
+
1142
1150
  protected void applyTextAttributes() {
1143
1151
  // In general, the `getEffective*` functions return `Float.NaN` if the
1144
1152
  // property hasn't been set.
@@ -14,7 +14,7 @@ import com.facebook.react.uimanager.common.ViewUtil;
14
14
  import com.facebook.react.uimanager.events.Event;
15
15
 
16
16
  /** Event emitted by EditText native view when key pressed */
17
- public class ReactTextInputKeyPressEvent extends Event<ReactTextInputEvent> {
17
+ public class ReactTextInputKeyPressEvent extends Event<ReactTextInputKeyPressEvent> {
18
18
 
19
19
  public static final String EVENT_NAME = "topKeyPress";
20
20
 
@@ -904,14 +904,15 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
904
904
  }
905
905
 
906
906
  @ReactPropGroup(
907
- names = {
908
- ViewProps.BORDER_RADIUS,
909
- ViewProps.BORDER_TOP_LEFT_RADIUS,
910
- ViewProps.BORDER_TOP_RIGHT_RADIUS,
911
- ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
912
- ViewProps.BORDER_BOTTOM_LEFT_RADIUS
913
- },
914
- defaultFloat = YogaConstants.UNDEFINED)
907
+ names = {
908
+ ViewProps.BORDER_RADIUS,
909
+ ViewProps.BORDER_TOP_LEFT_RADIUS,
910
+ ViewProps.BORDER_TOP_RIGHT_RADIUS,
911
+ ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
912
+ ViewProps.BORDER_BOTTOM_LEFT_RADIUS
913
+ },
914
+ defaultFloat = YogaConstants.UNDEFINED
915
+ )
915
916
  public void setBorderRadius(ReactEditText view, int index, float borderRadius) {
916
917
  if (!YogaConstants.isUndefined(borderRadius)) {
917
918
  borderRadius = PixelUtil.toPixelFromDIP(borderRadius);
@@ -954,14 +955,15 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
954
955
  }
955
956
 
956
957
  @ReactPropGroup(
957
- names = {
958
- ViewProps.BORDER_WIDTH,
959
- ViewProps.BORDER_LEFT_WIDTH,
960
- ViewProps.BORDER_RIGHT_WIDTH,
961
- ViewProps.BORDER_TOP_WIDTH,
962
- ViewProps.BORDER_BOTTOM_WIDTH,
963
- },
964
- defaultFloat = YogaConstants.UNDEFINED)
958
+ names = {
959
+ ViewProps.BORDER_WIDTH,
960
+ ViewProps.BORDER_LEFT_WIDTH,
961
+ ViewProps.BORDER_RIGHT_WIDTH,
962
+ ViewProps.BORDER_TOP_WIDTH,
963
+ ViewProps.BORDER_BOTTOM_WIDTH,
964
+ },
965
+ defaultFloat = YogaConstants.UNDEFINED
966
+ )
965
967
  public void setBorderWidth(ReactEditText view, int index, float width) {
966
968
  if (!YogaConstants.isUndefined(width)) {
967
969
  width = PixelUtil.toPixelFromDIP(width);
@@ -970,14 +972,15 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
970
972
  }
971
973
 
972
974
  @ReactPropGroup(
973
- names = {
974
- "borderColor",
975
- "borderLeftColor",
976
- "borderRightColor",
977
- "borderTopColor",
978
- "borderBottomColor"
979
- },
980
- customType = "Color")
975
+ names = {
976
+ "borderColor",
977
+ "borderLeftColor",
978
+ "borderRightColor",
979
+ "borderTopColor",
980
+ "borderBottomColor"
981
+ },
982
+ customType = "Color"
983
+ )
981
984
  public void setBorderColor(ReactEditText view, int index, Integer color) {
982
985
  float rgbComponent =
983
986
  color == null ? YogaConstants.UNDEFINED : (float) ((int) color & 0x00FFFFFF);
@@ -1062,17 +1065,12 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
1062
1065
  }
1063
1066
 
1064
1067
  // The event that contains the event counter and updates it must be sent first.
1065
- // TODO: t7936714 merge these events
1066
1068
  mEventDispatcher.dispatchEvent(
1067
1069
  new ReactTextChangedEvent(
1068
1070
  mSurfaceId,
1069
1071
  mEditText.getId(),
1070
1072
  s.toString(),
1071
1073
  mEditText.incrementAndGetEventCounter()));
1072
-
1073
- mEventDispatcher.dispatchEvent(
1074
- new ReactTextInputEvent(
1075
- mSurfaceId, mEditText.getId(), newText, oldText, start, start + before));
1076
1074
  }
1077
1075
 
1078
1076
  @Override
@@ -17,7 +17,7 @@ namespace facebook::react {
17
17
  constexpr struct {
18
18
  int32_t Major = 0;
19
19
  int32_t Minor = 73;
20
- int32_t Patch = 8;
20
+ int32_t Patch = 10;
21
21
  std::string_view Prerelease = "";
22
22
  } ReactNativeVersion;
23
23
 
@@ -834,10 +834,7 @@ LayoutAnimationKeyFrameManager::pullTransaction(
834
834
  finalConflictingMutations.end(),
835
835
  &shouldFirstComeBeforeSecondMutation);
836
836
 
837
- std::stable_sort(
838
- immediateMutations.begin(),
839
- immediateMutations.end(),
840
- &shouldFirstComeBeforeSecondRemovesOnly);
837
+ handleShouldFirstComeBeforeSecondRemovesOnly(immediateMutations);
841
838
 
842
839
  animation.keyFrames = keyFramesToAnimate;
843
840
  inflightAnimations_.push_back(std::move(animation));
@@ -24,6 +24,40 @@ static inline bool shouldFirstComeBeforeSecondRemovesOnly(
24
24
  (lhs.index > rhs.index);
25
25
  }
26
26
 
27
+ static inline void handleShouldFirstComeBeforeSecondRemovesOnly(
28
+ ShadowViewMutation::List& list) noexcept {
29
+ std::unordered_map<std::string, std::vector<ShadowViewMutation>>
30
+ removeMutationsByTag;
31
+ ShadowViewMutation::List finalList;
32
+ for (auto& mutation : list) {
33
+ if (mutation.type == ShadowViewMutation::Type::Remove) {
34
+ auto key = std::to_string(mutation.parentShadowView.tag);
35
+ removeMutationsByTag[key].push_back(mutation);
36
+ } else {
37
+ finalList.push_back(mutation);
38
+ }
39
+ }
40
+
41
+ if (removeMutationsByTag.size() == 0) {
42
+ return;
43
+ }
44
+
45
+ for (auto& mutationsPair : removeMutationsByTag) {
46
+ if (mutationsPair.second.size() > 1) {
47
+ std::stable_sort(
48
+ mutationsPair.second.begin(),
49
+ mutationsPair.second.end(),
50
+ &shouldFirstComeBeforeSecondRemovesOnly);
51
+ }
52
+ finalList.insert(
53
+ finalList.begin(),
54
+ mutationsPair.second.begin(),
55
+ mutationsPair.second.end());
56
+ }
57
+
58
+ list = finalList;
59
+ }
60
+
27
61
  static inline bool shouldFirstComeBeforeSecondMutation(
28
62
  const ShadowViewMutation& lhs,
29
63
  const ShadowViewMutation& rhs) noexcept {
@@ -55,6 +89,17 @@ static inline bool shouldFirstComeBeforeSecondMutation(
55
89
  lhs.type == ShadowViewMutation::Type::Insert) {
56
90
  return false;
57
91
  }
92
+
93
+ // Remove comes before Update
94
+ if (lhs.type == ShadowViewMutation::Type::Remove &&
95
+ rhs.type == ShadowViewMutation::Type::Update) {
96
+ return true;
97
+ }
98
+ if (rhs.type == ShadowViewMutation::Type::Remove &&
99
+ lhs.type == ShadowViewMutation::Type::Update) {
100
+ return false;
101
+ }
102
+
58
103
  } else {
59
104
  // Make sure that removes on the same level are sorted - highest indices
60
105
  // must come first.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native",
3
- "version": "0.73.8",
3
+ "version": "0.73.10",
4
4
  "description": "A framework for building native apps using React",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -93,13 +93,13 @@
93
93
  },
94
94
  "dependencies": {
95
95
  "@jest/create-cache-key-function": "^29.6.3",
96
- "@react-native-community/cli": "12.3.6",
97
- "@react-native-community/cli-platform-android": "12.3.6",
98
- "@react-native-community/cli-platform-ios": "12.3.6",
96
+ "@react-native-community/cli": "12.3.7",
97
+ "@react-native-community/cli-platform-android": "12.3.7",
98
+ "@react-native-community/cli-platform-ios": "12.3.7",
99
99
  "@react-native/assets-registry": "0.73.1",
100
- "@react-native/community-cli-plugin": "0.73.17",
100
+ "@react-native/community-cli-plugin": "0.73.18",
101
101
  "@react-native/codegen": "0.73.3",
102
- "@react-native/gradle-plugin": "0.73.4",
102
+ "@react-native/gradle-plugin": "0.73.5",
103
103
  "@react-native/js-polyfills": "0.73.1",
104
104
  "@react-native/normalize-colors": "0.73.2",
105
105
  "@react-native/virtualized-lists": "0.73.4",
@@ -0,0 +1,170 @@
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ #
3
+ # This source code is licensed under the MIT license found in the
4
+ # LICENSE file in the root directory of this source tree.
5
+
6
+ module PrivacyManifestUtils
7
+ def self.add_aggregated_privacy_manifest(installer)
8
+ user_project = get_user_project_from(installer)
9
+ targets = get_application_targets(user_project)
10
+ file_path = get_privacyinfo_file_path(user_project, targets)
11
+
12
+ privacy_info = read_privacyinfo_file(file_path) || {
13
+ "NSPrivacyCollectedDataTypes" => [],
14
+ "NSPrivacyTracking" => false
15
+ }
16
+
17
+ # Get all required reason APIs defined in current pods
18
+ required_reason_apis = get_used_required_reason_apis(installer)
19
+
20
+ # Add the Required Reason APIs from React Native core
21
+ get_core_accessed_apis.each do |accessed_api|
22
+ api_type = accessed_api["NSPrivacyAccessedAPIType"]
23
+ reasons = accessed_api["NSPrivacyAccessedAPITypeReasons"]
24
+ required_reason_apis[api_type] ||= []
25
+ required_reason_apis[api_type] += reasons
26
+ end
27
+
28
+ # Merge the Required Reason APIs from pods with the ones from the existing PrivacyInfo file
29
+ (privacy_info["NSPrivacyAccessedAPITypes"] || []).each do |accessed_api|
30
+ api_type = accessed_api["NSPrivacyAccessedAPIType"]
31
+ reasons = accessed_api["NSPrivacyAccessedAPITypeReasons"]
32
+ # Add reasons from existing PrivacyInfo file to the ones from pods
33
+ required_reason_apis[api_type] ||= []
34
+ required_reason_apis[api_type] += reasons
35
+ end
36
+
37
+ # Update the existing PrivacyInfo file with the new aggregated data
38
+ privacy_info["NSPrivacyAccessedAPITypes"] = required_reason_apis.map { |api_type, reasons|
39
+ {
40
+ "NSPrivacyAccessedAPIType" => api_type,
41
+ "NSPrivacyAccessedAPITypeReasons" => reasons.uniq
42
+ }
43
+ }
44
+
45
+ Xcodeproj::Plist.write_to_path(privacy_info, file_path)
46
+
47
+ targets.each do |target|
48
+ ensure_reference(file_path, user_project, target)
49
+ end
50
+ end
51
+
52
+ def self.get_application_targets(user_project)
53
+ return user_project.targets.filter { |t| t.symbol_type == :application }
54
+ end
55
+
56
+ def self.read_privacyinfo_file(file_path)
57
+ # Maybe add missing default NSPrivacyTracking, NSPrivacyTrackingDomains, NSPrivacyCollectedDataTypes, but this works without those keys
58
+ source_data = nil
59
+ # Try to read an existing PrivacyInfo.xcprivacy file
60
+ begin
61
+ source_data = Xcodeproj::Plist.read_from_path(file_path)
62
+ Pod::UI.puts "[Privacy Manifest Aggregation] Appending aggregated reasons to existing PrivacyInfo.xcprivacy file."
63
+ rescue => e
64
+ Pod::UI.puts "[Privacy Manifest Aggregation] No existing PrivacyInfo.xcprivacy file found, creating a new one."
65
+ end
66
+ return source_data
67
+ end
68
+
69
+ def self.ensure_reference(file_path, user_project, target)
70
+ reference_exists = target.resources_build_phase.files_references.any? { |file_ref| file_ref.path&.end_with? "PrivacyInfo.xcprivacy" }
71
+ unless reference_exists
72
+ # We try to find the main group, but if it doesn't exist, we default to adding the file to the project root – both work
73
+ file_root = user_project.root_object.main_group.children.find { |group| group.class == Xcodeproj::Project::Object::PBXGroup && (group.name == target.name || group.path == target.name) } || user_project
74
+ file_ref = file_root.new_file(file_path)
75
+ build_file = target.resources_build_phase.add_file_reference(file_ref, true)
76
+ end
77
+ end
78
+
79
+ def self.get_privacyinfo_file_path(user_project, targets)
80
+ file_refs = targets.flat_map { |target| target.resources_build_phase.files_references }
81
+ existing_file = file_refs.find { |file_ref| file_ref.path&.end_with? "PrivacyInfo.xcprivacy" }
82
+ if existing_file
83
+ return existing_file.real_path
84
+ end
85
+ # We try to find a file we know exists in the project to get the path to the main group directory
86
+ info_plist_path = user_project.files.find { |file_ref| file_ref.name == "Info.plist" }
87
+ if info_plist_path.nil?
88
+ # return path that is sibling to .xcodeproj
89
+ path = user_project.path
90
+ return File.join(File.dirname(path), "PrivacyInfo.xcprivacy")
91
+ end
92
+ return File.join(File.dirname(info_plist_path.real_path),"PrivacyInfo.xcprivacy")
93
+ end
94
+
95
+ def self.get_used_required_reason_apis(installer)
96
+ # A dictionary with keys of type string (NSPrivacyAccessedAPIType) and values of type string[] (NSPrivacyAccessedAPITypeReasons[])
97
+ used_apis = {}
98
+ Pod::UI.puts "[Privacy Manifest Aggregation] Reading .xcprivacy files to aggregate all used Required Reason APIs."
99
+ installer.pod_targets.each do |pod_target|
100
+ # puts pod_target
101
+ pod_target.file_accessors.each do |file_accessor|
102
+ file_accessor.resource_bundles.each do |bundle_name, bundle_files|
103
+ bundle_files.each do |file_path|
104
+ # This needs to be named like that due to apple requirements
105
+ if File.basename(file_path) == 'PrivacyInfo.xcprivacy'
106
+ content = Xcodeproj::Plist.read_from_path(file_path)
107
+ accessed_api_types = content["NSPrivacyAccessedAPITypes"]
108
+ accessed_api_types&.each do |accessed_api|
109
+ api_type = accessed_api["NSPrivacyAccessedAPIType"]
110
+ reasons = accessed_api["NSPrivacyAccessedAPITypeReasons"]
111
+ next unless api_type
112
+ used_apis[api_type] ||= []
113
+ used_apis[api_type] += reasons
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ return used_apis
121
+ end
122
+
123
+ def self.get_privacy_manifest_paths_from(user_project)
124
+ privacy_manifests = user_project
125
+ .files
126
+ .select { |p|
127
+ p.path&.end_with?('PrivacyInfo.xcprivacy')
128
+ }
129
+ return privacy_manifests
130
+ end
131
+
132
+ def self.get_core_accessed_apis()
133
+ file_timestamp_accessed_api = {
134
+ "NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategoryFileTimestamp",
135
+ "NSPrivacyAccessedAPITypeReasons" => ["C617.1"],
136
+ }
137
+ user_defaults_accessed_api = {
138
+ "NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategoryUserDefaults",
139
+ "NSPrivacyAccessedAPITypeReasons" => ["CA92.1"],
140
+ }
141
+ boot_time_accessed_api = {
142
+ "NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategorySystemBootTime",
143
+ "NSPrivacyAccessedAPITypeReasons" => ["35F9.1"],
144
+ }
145
+ return [file_timestamp_accessed_api, user_defaults_accessed_api, boot_time_accessed_api]
146
+ end
147
+
148
+
149
+ def self.get_user_project_from(installer)
150
+ user_project = installer.aggregate_targets
151
+ .map{ |t| t.user_project }
152
+ .first
153
+ return user_project
154
+ end
155
+
156
+ def self.add_privacy_manifest_if_needed(installer)
157
+ user_project = get_user_project_from(installer)
158
+ privacy_manifest = self.get_privacy_manifest_paths_from(user_project).first
159
+ if privacy_manifest.nil?
160
+ privacy_manifest = {
161
+ "NSPrivacyCollectedDataTypes" => [],
162
+ "NSPrivacyTracking" => false,
163
+ "NSPrivacyAccessedAPITypes" => get_core_accessed_apis
164
+ }
165
+ path = File.join(user_project.path.parent, "PrivacyInfo.xcprivacy")
166
+ Xcodeproj::Plist.write_to_path(privacy_manifest, path)
167
+ Pod::UI.puts "Your app does not have a privacy manifest! A template has been generated containing Required Reasons API usage in the core React Native library. Please add the PrivacyInfo.xcprivacy file to your project and complete data use, tracking and any additional required reasons your app is using according to Apple's guidance: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files. Then, you will need to manually add this file to your project in Xcode.".red
168
+ end
169
+ end
170
+ end
@@ -249,7 +249,11 @@ class ReactNativePodsUtils
249
249
  end
250
250
 
251
251
  if !file_manager.exist?("#{file_path}.local")
252
- node_binary = `command -v node`
252
+ # When installing pods with a yarn alias, yarn creates a fake yarn and node executables
253
+ # in a temporary folder.
254
+ # Using `node --print "process.argv[0]";` we are able to retrieve the actual path from which node is running.
255
+ # see https://github.com/facebook/react-native/issues/43285 for more info
256
+ node_binary = `node --print "process.argv[0]";`
253
257
  system("echo 'export NODE_BINARY=#{node_binary}' > #{file_path}.local")
254
258
  end
255
259
  end
@@ -561,44 +565,6 @@ class ReactNativePodsUtils
561
565
  ReactNativePodsUtils.update_header_paths_if_depends_on(target_installation_result, "React-ImageManager", header_search_paths)
562
566
  end
563
567
 
564
- def self.get_privacy_manifest_paths_from(user_project)
565
- privacy_manifests = user_project
566
- .files
567
- .select { |p|
568
- p.path&.end_with?('PrivacyInfo.xcprivacy')
569
- }
570
- return privacy_manifests
571
- end
572
-
573
- def self.add_privacy_manifest_if_needed(installer)
574
- user_project = installer.aggregate_targets
575
- .map{ |t| t.user_project }
576
- .first
577
- privacy_manifest = self.get_privacy_manifest_paths_from(user_project).first
578
- if privacy_manifest.nil?
579
- file_timestamp_reason = {
580
- "NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategoryFileTimestamp",
581
- "NSPrivacyAccessedAPITypeReasons" => ["C617.1"],
582
- }
583
- user_defaults_reason = {
584
- "NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategoryUserDefaults",
585
- "NSPrivacyAccessedAPITypeReasons" => ["CA92.1"],
586
- }
587
- boot_time_reason = {
588
- "NSPrivacyAccessedAPIType" => "NSPrivacyAccessedAPICategorySystemBootTime",
589
- "NSPrivacyAccessedAPITypeReasons" => ["35F9.1"],
590
- }
591
- privacy_manifest = {
592
- "NSPrivacyCollectedDataTypes" => [],
593
- "NSPrivacyTracking" => false,
594
- "NSPrivacyAccessedAPITypes" => [file_timestamp_reason, user_defaults_reason, boot_time_reason]
595
- }
596
- path = File.join(user_project.path.parent, "PrivacyInfo.xcprivacy")
597
- Xcodeproj::Plist.write_to_path(privacy_manifest, path)
598
- Pod::UI.puts "Your app does not have a privacy manifest! A template has been generated containing Required Reasons API usage in the core React Native library. Please add the PrivacyInfo.xcprivacy file to your project and complete data use, tracking and any additional required reasons your app is using according to Apple's guidance: https://developer.apple.com/.../privacy_manifest_files. Then, you will need to manually add this file to your project in Xcode.".red
599
- end
600
- end
601
-
602
568
  def self.react_native_pods
603
569
  return [
604
570
  "DoubleConversion",
@@ -17,6 +17,7 @@ require_relative './cocoapods/new_architecture.rb'
17
17
  require_relative './cocoapods/local_podspec_patch.rb'
18
18
  require_relative './cocoapods/runtime.rb'
19
19
  require_relative './cocoapods/helpers.rb'
20
+ require_relative './cocoapods/privacy_manifest_utils.rb'
20
21
 
21
22
  $CODEGEN_OUTPUT_DIR = 'build/generated/ios'
22
23
  $CODEGEN_COMPONENT_DIR = 'react/renderer/components'
@@ -79,7 +80,8 @@ def use_react_native! (
79
80
  flipper_configuration: FlipperConfiguration.disabled,
80
81
  app_path: '..',
81
82
  config_file_dir: '',
82
- ios_folder: 'ios'
83
+ ios_folder: 'ios',
84
+ privacy_file_aggregation_enabled: true
83
85
  )
84
86
 
85
87
  # Set the app_path as env variable so the podspecs can access it.
@@ -101,6 +103,7 @@ def use_react_native! (
101
103
  fabric_enabled = fabric_enabled || NewArchitectureHelper.new_arch_enabled
102
104
  ENV['RCT_FABRIC_ENABLED'] = fabric_enabled ? "1" : "0"
103
105
  ENV['USE_HERMES'] = hermes_enabled ? "1" : "0"
106
+ ENV['RCT_AGGREGATE_PRIVACY_FILES'] = privacy_file_aggregation_enabled ? "1" : "0"
104
107
 
105
108
  prefix = path
106
109
 
@@ -295,6 +298,7 @@ def react_native_post_install(
295
298
 
296
299
  fabric_enabled = ENV['RCT_FABRIC_ENABLED'] == '1'
297
300
  hermes_enabled = ENV['USE_HERMES'] == '1'
301
+ privacy_file_aggregation_enabled = ENV['RCT_AGGREGATE_PRIVACY_FILES'] == '1'
298
302
 
299
303
  if hermes_enabled
300
304
  ReactNativePodsUtils.set_gcc_preprocessor_definition_for_React_hermes(installer)
@@ -309,7 +313,12 @@ def react_native_post_install(
309
313
  ReactNativePodsUtils.apply_xcode_15_patch(installer)
310
314
  ReactNativePodsUtils.updateOSDeploymentTarget(installer)
311
315
  ReactNativePodsUtils.fix_flipper_for_xcode_15_3(installer)
312
- ReactNativePodsUtils.add_privacy_manifest_if_needed(installer)
316
+
317
+ if privacy_file_aggregation_enabled
318
+ PrivacyManifestUtils.add_aggregated_privacy_manifest(installer)
319
+ else
320
+ PrivacyManifestUtils.add_privacy_manifest_if_needed(installer)
321
+ end
313
322
 
314
323
  NewArchitectureHelper.set_clang_cxx_language_standard_if_needed(installer)
315
324
  NewArchitectureHelper.modify_flags_for_new_architecture(installer, NewArchitectureHelper.new_arch_enabled)
Binary file
Binary file
Binary file
Binary file
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "react": "18.2.0",
14
- "react-native": "0.73.8"
14
+ "react-native": "0.73.10"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@babel/core": "^7.20.0",
@@ -1,70 +0,0 @@
1
- /*
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-
8
- package com.facebook.react.views.textinput;
9
-
10
- import androidx.annotation.Nullable;
11
- import com.facebook.react.bridge.Arguments;
12
- import com.facebook.react.bridge.WritableMap;
13
- import com.facebook.react.uimanager.common.ViewUtil;
14
- import com.facebook.react.uimanager.events.Event;
15
-
16
- /**
17
- * Event emitted by EditText native view when text changes. VisibleForTesting from {@link
18
- * TextInputEventsTestCase}.
19
- */
20
- public class ReactTextInputEvent extends Event<ReactTextInputEvent> {
21
-
22
- public static final String EVENT_NAME = "topTextInput";
23
-
24
- private String mText;
25
- private String mPreviousText;
26
- private int mRangeStart;
27
- private int mRangeEnd;
28
-
29
- @Deprecated
30
- public ReactTextInputEvent(
31
- int viewId, String text, String previousText, int rangeStart, int rangeEnd) {
32
- this(ViewUtil.NO_SURFACE_ID, viewId, text, previousText, rangeStart, rangeEnd);
33
- }
34
-
35
- public ReactTextInputEvent(
36
- int surfaceId, int viewId, String text, String previousText, int rangeStart, int rangeEnd) {
37
- super(surfaceId, viewId);
38
- mText = text;
39
- mPreviousText = previousText;
40
- mRangeStart = rangeStart;
41
- mRangeEnd = rangeEnd;
42
- }
43
-
44
- @Override
45
- public String getEventName() {
46
- return EVENT_NAME;
47
- }
48
-
49
- @Override
50
- public boolean canCoalesce() {
51
- // We don't want to miss any textinput event, as event data is incremental.
52
- return false;
53
- }
54
-
55
- @Nullable
56
- @Override
57
- protected WritableMap getEventData() {
58
- WritableMap eventData = Arguments.createMap();
59
- WritableMap range = Arguments.createMap();
60
- range.putDouble("start", mRangeStart);
61
- range.putDouble("end", mRangeEnd);
62
-
63
- eventData.putString("text", mText);
64
- eventData.putString("previousText", mPreviousText);
65
- eventData.putMap("range", range);
66
-
67
- eventData.putInt("target", getViewTag());
68
- return eventData;
69
- }
70
- }