From 9e0d4d8454a832662617d9737417b2cdb5236198 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 12:47:17 +0100 Subject: [PATCH 01/57] disable building android from source --- android/settings.gradle | 9 - ...eact-native+0.72.4+002+NumberOfLines.patch | 978 ------------------ 2 files changed, 987 deletions(-) delete mode 100644 patches/react-native+0.72.4+002+NumberOfLines.patch diff --git a/android/settings.gradle b/android/settings.gradle index c2bb3db7845a..680dfbc32521 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -16,12 +16,3 @@ project(':react-native-dev-menu').projectDir = new File(rootProject.projectDir, apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' includeBuild('../node_modules/@react-native/gradle-plugin') - -includeBuild('../node_modules/react-native') { - dependencySubstitution { - substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid")) - substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid")) - substitute(module("com.facebook.react:hermes-android")).using(project(":packages:react-native:ReactAndroid:hermes-engine")) - substitute(module("com.facebook.react:hermes-engine")).using(project(":packages:react-native:ReactAndroid:hermes-engine")) - } -} diff --git a/patches/react-native+0.72.4+002+NumberOfLines.patch b/patches/react-native+0.72.4+002+NumberOfLines.patch deleted file mode 100644 index 75422f84708e..000000000000 --- a/patches/react-native+0.72.4+002+NumberOfLines.patch +++ /dev/null @@ -1,978 +0,0 @@ -diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -index 55b770d..4073836 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -@@ -179,6 +179,13 @@ export type NativeProps = $ReadOnly<{| - */ - numberOfLines?: ?Int32, - -+ /** -+ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ * @platform android -+ */ -+ maximumNumberOfLines?: ?Int32, -+ - /** - * When `false`, if there is a small amount of space available around a text input - * (e.g. landscape orientation on a phone), the OS may choose to have the user edit -diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -index 6f69329..d531bee 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -@@ -144,6 +144,8 @@ const RCTTextInputViewConfig = { - placeholder: true, - autoCorrect: true, - multiline: true, -+ numberOfLines: true, -+ maximumNumberOfLines: true, - textContentType: true, - maxLength: true, - autoCapitalize: true, -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -index 8badb2a..b19f197 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -@@ -347,12 +347,6 @@ export interface TextInputAndroidProps { - */ - inlineImagePadding?: number | undefined; - -- /** -- * Sets the number of lines for a TextInput. -- * Use it with multiline set to true to be able to fill the lines. -- */ -- numberOfLines?: number | undefined; -- - /** - * Sets the return key to the label. Use it instead of `returnKeyType`. - * @platform android -@@ -663,11 +657,30 @@ export interface TextInputProps - */ - maxLength?: number | undefined; - -+ /** -+ * Sets the maximum number of lines for a TextInput. -+ * Use it with multiline set to true to be able to fill the lines. -+ */ -+ maxNumberOfLines?: number | undefined; -+ - /** - * If true, the text input can be multiple lines. The default value is false. - */ - multiline?: boolean | undefined; - -+ /** -+ * Sets the number of lines for a TextInput. -+ * Use it with multiline set to true to be able to fill the lines. -+ */ -+ numberOfLines?: number | undefined; -+ -+ /** -+ * Sets the number of rows for a TextInput. -+ * Use it with multiline set to true to be able to fill the lines. -+ */ -+ rows?: number | undefined; -+ -+ - /** - * Callback that is called when the text input is blurred - */ -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -index 7ed4579..b1d994e 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -@@ -343,26 +343,12 @@ type AndroidProps = $ReadOnly<{| - */ - inlineImagePadding?: ?number, - -- /** -- * Sets the number of lines for a `TextInput`. Use it with multiline set to -- * `true` to be able to fill the lines. -- * @platform android -- */ -- numberOfLines?: ?number, -- - /** - * Sets the return key to the label. Use it instead of `returnKeyType`. - * @platform android - */ - returnKeyLabel?: ?string, - -- /** -- * Sets the number of rows for a `TextInput`. Use it with multiline set to -- * `true` to be able to fill the lines. -- * @platform android -- */ -- rows?: ?number, -- - /** - * When `false`, it will prevent the soft keyboard from showing when the field is focused. - * Defaults to `true`. -@@ -632,6 +618,12 @@ export type Props = $ReadOnly<{| - */ - keyboardType?: ?KeyboardType, - -+ /** -+ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ */ -+ maxNumberOfLines?: ?number, -+ - /** - * Specifies largest possible scale a font can reach when `allowFontScaling` is enabled. - * Possible values: -@@ -653,6 +645,12 @@ export type Props = $ReadOnly<{| - */ - multiline?: ?boolean, - -+ /** -+ * Sets the number of lines for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ */ -+ numberOfLines?: ?number, -+ - /** - * Callback that is called when the text input is blurred. - */ -@@ -814,6 +812,12 @@ export type Props = $ReadOnly<{| - */ - returnKeyType?: ?ReturnKeyType, - -+ /** -+ * Sets the number of rows for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ */ -+ rows?: ?number, -+ - /** - * If `true`, the text input obscures the text entered so that sensitive text - * like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'. -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -index 2127191..542fc06 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -@@ -390,7 +390,6 @@ type AndroidProps = $ReadOnly<{| - /** - * Sets the number of lines for a `TextInput`. Use it with multiline set to - * `true` to be able to fill the lines. -- * @platform android - */ - numberOfLines?: ?number, - -@@ -403,10 +402,14 @@ type AndroidProps = $ReadOnly<{| - /** - * Sets the number of rows for a `TextInput`. Use it with multiline set to - * `true` to be able to fill the lines. -- * @platform android - */ - rows?: ?number, - -+ /** -+ * Sets the maximum number of lines the TextInput can have. -+ */ -+ maxNumberOfLines?: ?number, -+ - /** - * When `false`, it will prevent the soft keyboard from showing when the field is focused. - * Defaults to `true`. -@@ -1069,6 +1072,9 @@ function InternalTextInput(props: Props): React.Node { - accessibilityState, - id, - tabIndex, -+ rows, -+ numberOfLines, -+ maxNumberOfLines, - selection: propsSelection, - ...otherProps - } = props; -@@ -1427,6 +1433,8 @@ function InternalTextInput(props: Props): React.Node { - focusable={tabIndex !== undefined ? !tabIndex : focusable} - mostRecentEventCount={mostRecentEventCount} - nativeID={id ?? props.nativeID} -+ numberOfLines={props.rows ?? props.numberOfLines} -+ maximumNumberOfLines={maxNumberOfLines} - onBlur={_onBlur} - onKeyPressSync={props.unstable_onKeyPressSync} - onChange={_onChange} -@@ -1482,6 +1490,7 @@ function InternalTextInput(props: Props): React.Node { - mostRecentEventCount={mostRecentEventCount} - nativeID={id ?? props.nativeID} - numberOfLines={props.rows ?? props.numberOfLines} -+ maximumNumberOfLines={maxNumberOfLines} - onBlur={_onBlur} - onChange={_onChange} - onFocus={_onFocus} -diff --git a/node_modules/react-native/Libraries/Text/Text.js b/node_modules/react-native/Libraries/Text/Text.js -index df548af..e02f5da 100644 ---- a/node_modules/react-native/Libraries/Text/Text.js -+++ b/node_modules/react-native/Libraries/Text/Text.js -@@ -18,7 +18,11 @@ import processColor from '../StyleSheet/processColor'; - import {getAccessibilityRoleFromRole} from '../Utilities/AcessibilityMapping'; - import Platform from '../Utilities/Platform'; - import TextAncestor from './TextAncestor'; --import {NativeText, NativeVirtualText} from './TextNativeComponent'; -+import { -+ CONTAINS_MAX_NUMBER_OF_LINES_RENAME, -+ NativeText, -+ NativeVirtualText, -+} from './TextNativeComponent'; - import * as React from 'react'; - import {useContext, useMemo, useState} from 'react'; - -@@ -59,6 +63,7 @@ const Text: React.AbstractComponent< - pressRetentionOffset, - role, - suppressHighlighting, -+ numberOfLines, - ...restProps - } = props; - -@@ -192,14 +197,33 @@ const Text: React.AbstractComponent< - } - } - -- let numberOfLines = restProps.numberOfLines; -+ let numberOfLinesValue = numberOfLines; - if (numberOfLines != null && !(numberOfLines >= 0)) { - console.error( - `'numberOfLines' in must be a non-negative number, received: ${numberOfLines}. The value will be set to 0.`, - ); -- numberOfLines = 0; -+ numberOfLinesValue = 0; - } - -+ const numberOfLinesProps = useMemo((): { -+ maximumNumberOfLines?: ?number, -+ numberOfLines?: ?number, -+ } => { -+ // FIXME: Current logic is breaking all Text components. -+ // if (CONTAINS_MAX_NUMBER_OF_LINES_RENAME) { -+ // return { -+ // maximumNumberOfLines: numberOfLinesValue, -+ // }; -+ // } else { -+ // return { -+ // numberOfLines: numberOfLinesValue, -+ // }; -+ // } -+ return { -+ maximumNumberOfLines: numberOfLinesValue, -+ }; -+ }, [numberOfLinesValue]); -+ - const hasTextAncestor = useContext(TextAncestor); - - const _accessible = Platform.select({ -@@ -241,7 +265,6 @@ const Text: React.AbstractComponent< - isHighlighted={isHighlighted} - isPressable={isPressable} - nativeID={id ?? nativeID} -- numberOfLines={numberOfLines} - ref={forwardedRef} - selectable={_selectable} - selectionColor={selectionColor} -@@ -252,6 +275,7 @@ const Text: React.AbstractComponent< - - #import -+#import -+#import - - @implementation RCTMultilineTextInputViewManager - -@@ -17,8 +19,21 @@ - (UIView *)view - return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge]; - } - -+- (RCTShadowView *)shadowView -+{ -+ RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; -+ -+ shadowView.maximumNumberOfLines = 0; -+ shadowView.exactNumberOfLines = 0; -+ -+ return shadowView; -+} -+ - #pragma mark - Multiline (aka TextView) specific properties - - RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) - -+RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) -+RCT_REMAP_SHADOW_PROPERTY(numberOfLines, exactNumberOfLines, NSInteger) -+ - @end -diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h -index 8f4cf7e..6238ebc 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h -+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h -@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN - @property (nonatomic, copy, nullable) NSString *text; - @property (nonatomic, copy, nullable) NSString *placeholder; - @property (nonatomic, assign) NSInteger maximumNumberOfLines; -+@property (nonatomic, assign) NSInteger exactNumberOfLines; - @property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange; - - - (void)uiManagerWillPerformMounting; -diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m -index 04d2446..9d77743 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m -+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m -@@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText - - - (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize - { -- NSAttributedString *attributedText = [self measurableAttributedText]; -+ NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy]; -+ -+ /* -+ * The block below is responsible for setting the exact height of the view in lines -+ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines -+ * prop and then add random lines at the front. However, they are only used for layout -+ * so they are not visible on the screen. -+ */ -+ if (self.exactNumberOfLines) { -+ NSMutableString *newLines = [NSMutableString stringWithCapacity:self.exactNumberOfLines]; -+ for (NSUInteger i = 0UL; i < self.exactNumberOfLines; ++i) { -+ [newLines appendString:@"\n"]; -+ } -+ [attributedText insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:self.textAttributes.effectiveTextAttributes] atIndex:0]; -+ _maximumNumberOfLines = self.exactNumberOfLines; -+ } - - if (!_textStorage) { - _textContainer = [NSTextContainer new]; -diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m -index 413ac42..56d039c 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m -+++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m -@@ -19,6 +19,7 @@ - (RCTShadowView *)shadowView - RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; - - shadowView.maximumNumberOfLines = 1; -+ shadowView.exactNumberOfLines = 0; - - return shadowView; - } -diff --git a/node_modules/react-native/Libraries/Text/TextNativeComponent.js b/node_modules/react-native/Libraries/Text/TextNativeComponent.js -index 0d59904..3216e43 100644 ---- a/node_modules/react-native/Libraries/Text/TextNativeComponent.js -+++ b/node_modules/react-native/Libraries/Text/TextNativeComponent.js -@@ -9,6 +9,7 @@ - */ - - import {createViewConfig} from '../NativeComponent/ViewConfig'; -+import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes'; - import UIManager from '../ReactNative/UIManager'; - import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass'; - import {type HostComponent} from '../Renderer/shims/ReactNativeTypes'; -@@ -18,6 +19,7 @@ import {type TextProps} from './TextProps'; - - type NativeTextProps = $ReadOnly<{ - ...TextProps, -+ maximumNumberOfLines?: ?number, - isHighlighted?: ?boolean, - selectionColor?: ?ProcessedColorValue, - onClick?: ?(event: PressEvent) => mixed, -@@ -31,7 +33,7 @@ const textViewConfig = { - validAttributes: { - isHighlighted: true, - isPressable: true, -- numberOfLines: true, -+ maximumNumberOfLines: true, - ellipsizeMode: true, - allowFontScaling: true, - dynamicTypeRamp: true, -@@ -73,6 +75,12 @@ export const NativeText: HostComponent = - createViewConfig(textViewConfig), - ): any); - -+const jestIsDefined = typeof jest !== 'undefined'; -+export const CONTAINS_MAX_NUMBER_OF_LINES_RENAME: boolean = jestIsDefined -+ ? true -+ : getNativeComponentAttributes('RCTText')?.NativeProps -+ ?.maximumNumberOfLines === 'number'; -+ - export const NativeVirtualText: HostComponent = - !global.RN$Bridgeless && !UIManager.hasViewManagerConfig('RCTVirtualText') - ? NativeText -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java -index 8cab407..ad5fa96 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java -@@ -12,5 +12,6 @@ public class ViewDefaults { - - public static final float FONT_SIZE_SP = 14.0f; - public static final int LINE_HEIGHT = 0; -- public static final int NUMBER_OF_LINES = Integer.MAX_VALUE; -+ public static final int NUMBER_OF_LINES = -1; -+ public static final int MAXIMUM_NUMBER_OF_LINES = Integer.MAX_VALUE; - } -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java -index 3f76fa7..7a5d096 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java -@@ -96,6 +96,7 @@ public class ViewProps { - public static final String LETTER_SPACING = "letterSpacing"; - public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing"; - public static final String NUMBER_OF_LINES = "numberOfLines"; -+ public static final String MAXIMUM_NUMBER_OF_LINES = "maximumNumberOfLines"; - public static final String ELLIPSIZE_MODE = "ellipsizeMode"; - public static final String ADJUSTS_FONT_SIZE_TO_FIT = "adjustsFontSizeToFit"; - public static final String MINIMUM_FONT_SCALE = "minimumFontScale"; -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java -index b5811c7..96eef96 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java -@@ -303,6 +303,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { - protected boolean mIsAccessibilityLink = false; - - protected int mNumberOfLines = UNSET; -+ protected int mMaxNumberOfLines = UNSET; - protected int mTextAlign = Gravity.NO_GRAVITY; - protected int mTextBreakStrategy = - (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; -@@ -387,6 +388,12 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { - markUpdated(); - } - -+ @ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = UNSET) -+ public void setMaxNumberOfLines(int numberOfLines) { -+ mMaxNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines; -+ markUpdated(); -+ } -+ - @ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN) - public void setLineHeight(float lineHeight) { - mTextAttributes.setLineHeight(lineHeight); -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java -index 7b5d0c1..c3032eb 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java -@@ -49,8 +49,8 @@ public abstract class ReactTextAnchorViewManager minimumFontSize -- && (mNumberOfLines != UNSET && layout.getLineCount() > mNumberOfLines -+ && (mMaxNumberOfLines != UNSET && layout.getLineCount() > mMaxNumberOfLines - || heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) { - // TODO: We could probably use a smarter algorithm here. This will require 0(n) - // measurements -@@ -124,9 +124,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode { - } - - final int lineCount = -- mNumberOfLines == UNSET -+ mMaxNumberOfLines == UNSET - ? layout.getLineCount() -- : Math.min(mNumberOfLines, layout.getLineCount()); -+ : Math.min(mMaxNumberOfLines, layout.getLineCount()); - - // Instead of using `layout.getWidth()` (which may yield a significantly larger width for - // text that is wrapping), compute width using the longest line. -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java -index 190bc27..c2bcdc1 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java -@@ -87,7 +87,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie - - mReactBackgroundManager = new ReactViewBackgroundManager(this); - -- mNumberOfLines = ViewDefaults.NUMBER_OF_LINES; -+ mNumberOfLines = ViewDefaults.MAXIMUM_NUMBER_OF_LINES; - mAdjustsFontSizeToFit = false; - mLinkifyMaskType = 0; - mNotifyOnInlineViewLayout = false; -@@ -576,7 +576,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie - } - - public void setNumberOfLines(int numberOfLines) { -- mNumberOfLines = numberOfLines == 0 ? ViewDefaults.NUMBER_OF_LINES : numberOfLines; -+ mNumberOfLines = numberOfLines == 0 ? ViewDefaults.MAXIMUM_NUMBER_OF_LINES : numberOfLines; - setSingleLine(mNumberOfLines == 1); - setMaxLines(mNumberOfLines); - } -@@ -596,7 +596,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie - public void updateView() { - @Nullable - TextUtils.TruncateAt ellipsizeLocation = -- mNumberOfLines == ViewDefaults.NUMBER_OF_LINES || mAdjustsFontSizeToFit -+ mNumberOfLines == ViewDefaults.MAXIMUM_NUMBER_OF_LINES || mAdjustsFontSizeToFit - ? null - : mEllipsizeLocation; - setEllipsize(ellipsizeLocation); -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java -index 561a2d0..9409cfc 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java -@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; - import android.text.Spanned; - import android.text.StaticLayout; - import android.text.TextPaint; -+import android.text.TextUtils; - import android.util.LayoutDirection; - import android.util.LruCache; - import android.view.View; -@@ -65,6 +66,7 @@ public class TextLayoutManager { - private static final String TEXT_BREAK_STRATEGY_KEY = "textBreakStrategy"; - private static final String HYPHENATION_FREQUENCY_KEY = "android_hyphenationFrequency"; - private static final String MAXIMUM_NUMBER_OF_LINES_KEY = "maximumNumberOfLines"; -+ private static final String NUMBER_OF_LINES_KEY = "numberOfLines"; - private static final LruCache sSpannableCache = - new LruCache<>(spannableCacheSize); - private static final ConcurrentHashMap sTagToSpannableCache = -@@ -385,6 +387,48 @@ public class TextLayoutManager { - ? paragraphAttributes.getInt(MAXIMUM_NUMBER_OF_LINES_KEY) - : UNSET; - -+ int numberOfLines = -+ paragraphAttributes.hasKey(NUMBER_OF_LINES_KEY) -+ ? paragraphAttributes.getInt(NUMBER_OF_LINES_KEY) -+ : UNSET; -+ -+ int lines = layout.getLineCount(); -+ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines >= lines && text.length() > 0) { -+ int numberOfEmptyLines = numberOfLines - lines; -+ SpannableStringBuilder ssb = new SpannableStringBuilder(); -+ -+ // for some reason a newline on end causes issues with computing height so we add a character -+ if (text.toString().endsWith("\n")) { -+ ssb.append("A"); -+ } -+ -+ for (int i = 0; i < numberOfEmptyLines; ++i) { -+ ssb.append("\nA"); -+ } -+ -+ Object[] spans = text.getSpans(0, 0, Object.class); -+ for (Object span : spans) { // It's possible we need to set exl-exl -+ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); -+ }; -+ -+ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); -+ boring = null; -+ layout = createLayout( -+ text, -+ boring, -+ width, -+ widthYogaMeasureMode, -+ includeFontPadding, -+ textBreakStrategy, -+ hyphenationFrequency); -+ } -+ -+ -+ if (numberOfLines != UNSET && numberOfLines != 0) { -+ maximumNumberOfLines = numberOfLines; -+ } -+ -+ - int calculatedLineCount = - maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 - ? layout.getLineCount() -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java -index 0d118f0..0ae44b7 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java -@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; - import android.text.Spanned; - import android.text.StaticLayout; - import android.text.TextPaint; -+import android.text.TextUtils; - import android.util.LayoutDirection; - import android.util.LruCache; - import android.view.View; -@@ -61,6 +62,7 @@ public class TextLayoutManagerMapBuffer { - public static final short PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; - public static final short PA_KEY_INCLUDE_FONT_PADDING = 4; - public static final short PA_KEY_HYPHENATION_FREQUENCY = 5; -+ public static final short PA_KEY_NUMBER_OF_LINES = 6; - - private static final boolean ENABLE_MEASURE_LOGGING = ReactBuildConfig.DEBUG && false; - -@@ -399,6 +401,47 @@ public class TextLayoutManagerMapBuffer { - ? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES) - : UNSET; - -+ int numberOfLines = -+ paragraphAttributes.contains(PA_KEY_NUMBER_OF_LINES) -+ ? paragraphAttributes.getInt(PA_KEY_NUMBER_OF_LINES) -+ : UNSET; -+ -+ int lines = layout.getLineCount(); -+ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines > lines && text.length() > 0) { -+ int numberOfEmptyLines = numberOfLines - lines; -+ SpannableStringBuilder ssb = new SpannableStringBuilder(); -+ -+ // for some reason a newline on end causes issues with computing height so we add a character -+ if (text.toString().endsWith("\n")) { -+ ssb.append("A"); -+ } -+ -+ for (int i = 0; i < numberOfEmptyLines; ++i) { -+ ssb.append("\nA"); -+ } -+ -+ Object[] spans = text.getSpans(0, 0, Object.class); -+ for (Object span : spans) { // It's possible we need to set exl-exl -+ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); -+ }; -+ -+ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); -+ boring = null; -+ layout = createLayout( -+ text, -+ boring, -+ width, -+ widthYogaMeasureMode, -+ includeFontPadding, -+ textBreakStrategy, -+ hyphenationFrequency); -+ } -+ -+ if (numberOfLines != UNSET && numberOfLines != 0) { -+ maximumNumberOfLines = numberOfLines; -+ } -+ -+ - int calculatedLineCount = - maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 - ? layout.getLineCount() -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -index ced37be..ef2f321 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -@@ -548,7 +548,13 @@ public class ReactEditText extends AppCompatEditText - * href='https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/widget/TextView.java'>TextView.java} - */ - if (isMultiline()) { -+ // we save max lines as setSingleLines overwrites it -+ // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/TextView.java#10671 -+ int maxLines = getMaxLines(); - setSingleLine(false); -+ if (maxLines != -1) { -+ setMaxLines(maxLines); -+ } - } - - // We override the KeyListener so that all keys on the soft input keyboard as well as hardware -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java -index a850510..c59be1d 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java -@@ -41,9 +41,9 @@ public final class ReactTextInputLocalData { - public void apply(EditText editText) { - editText.setText(mText); - editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); -+ editText.setInputType(mInputType); - editText.setMinLines(mMinLines); - editText.setMaxLines(mMaxLines); -- editText.setInputType(mInputType); - editText.setHint(mPlaceholder); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - editText.setBreakStrategy(mBreakStrategy); -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -index b27ace4..c6a2d63 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -@@ -737,9 +737,18 @@ public class ReactTextInputManager extends BaseViewManager= Build.VERSION_CODES.M -diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp -index 2994aca..fff0d5e 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp -+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp -@@ -16,6 +16,7 @@ namespace facebook::react { - - bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { - return std::tie( -+ numberOfLines, - maximumNumberOfLines, - ellipsizeMode, - textBreakStrategy, -@@ -23,6 +24,7 @@ bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { - includeFontPadding, - android_hyphenationFrequency) == - std::tie( -+ rhs.numberOfLines, - rhs.maximumNumberOfLines, - rhs.ellipsizeMode, - rhs.textBreakStrategy, -@@ -42,6 +44,7 @@ bool ParagraphAttributes::operator!=(const ParagraphAttributes &rhs) const { - #if RN_DEBUG_STRING_CONVERTIBLE - SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const { - return { -+ debugStringConvertibleItem("numberOfLines", numberOfLines), - debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines), - debugStringConvertibleItem("ellipsizeMode", ellipsizeMode), - debugStringConvertibleItem("textBreakStrategy", textBreakStrategy), -diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h -index f5f87c6..b7d1e90 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h -+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h -@@ -30,6 +30,11 @@ class ParagraphAttributes : public DebugStringConvertible { - public: - #pragma mark - Fields - -+ /* -+ * Number of lines which paragraph takes. -+ */ -+ int numberOfLines{}; -+ - /* - * Maximum number of lines which paragraph can take. - * Zero value represents "no limit". -@@ -92,6 +97,7 @@ struct hash { - const facebook::react::ParagraphAttributes &attributes) const { - return folly::hash::hash_combine( - 0, -+ attributes.numberOfLines, - attributes.maximumNumberOfLines, - attributes.ellipsizeMode, - attributes.textBreakStrategy, -diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -index 8687b89..eab75f4 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -@@ -835,10 +835,16 @@ inline ParagraphAttributes convertRawProp( - ParagraphAttributes const &defaultParagraphAttributes) { - auto paragraphAttributes = ParagraphAttributes{}; - -- paragraphAttributes.maximumNumberOfLines = convertRawProp( -+ paragraphAttributes.numberOfLines = convertRawProp( - context, - rawProps, - "numberOfLines", -+ sourceParagraphAttributes.numberOfLines, -+ defaultParagraphAttributes.numberOfLines); -+ paragraphAttributes.maximumNumberOfLines = convertRawProp( -+ context, -+ rawProps, -+ "maximumNumberOfLines", - sourceParagraphAttributes.maximumNumberOfLines, - defaultParagraphAttributes.maximumNumberOfLines); - paragraphAttributes.ellipsizeMode = convertRawProp( -@@ -913,6 +919,7 @@ inline std::string toString(AttributedString::Range const &range) { - inline folly::dynamic toDynamic( - const ParagraphAttributes ¶graphAttributes) { - auto values = folly::dynamic::object(); -+ values("numberOfLines", paragraphAttributes.numberOfLines); - values("maximumNumberOfLines", paragraphAttributes.maximumNumberOfLines); - values("ellipsizeMode", toString(paragraphAttributes.ellipsizeMode)); - values("textBreakStrategy", toString(paragraphAttributes.textBreakStrategy)); -@@ -1118,6 +1125,7 @@ constexpr static MapBuffer::Key PA_KEY_TEXT_BREAK_STRATEGY = 2; - constexpr static MapBuffer::Key PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; - constexpr static MapBuffer::Key PA_KEY_INCLUDE_FONT_PADDING = 4; - constexpr static MapBuffer::Key PA_KEY_HYPHENATION_FREQUENCY = 5; -+constexpr static MapBuffer::Key PA_KEY_NUMBER_OF_LINES = 6; - - inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { - auto builder = MapBufferBuilder(); -@@ -1135,6 +1143,8 @@ inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { - builder.putString( - PA_KEY_HYPHENATION_FREQUENCY, - toString(paragraphAttributes.android_hyphenationFrequency)); -+ builder.putInt( -+ PA_KEY_NUMBER_OF_LINES, paragraphAttributes.numberOfLines); - - return builder.build(); - } -diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp -index 9953e22..98eb3da 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp -+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp -@@ -56,6 +56,10 @@ AndroidTextInputProps::AndroidTextInputProps( - "numberOfLines", - sourceProps.numberOfLines, - {0})), -+ maximumNumberOfLines(CoreFeatures::enablePropIteratorSetter? sourceProps.maximumNumberOfLines : convertRawProp(context, rawProps, -+ "maximumNumberOfLines", -+ sourceProps.maximumNumberOfLines, -+ {0})), - disableFullscreenUI(CoreFeatures::enablePropIteratorSetter? sourceProps.disableFullscreenUI : convertRawProp(context, rawProps, - "disableFullscreenUI", - sourceProps.disableFullscreenUI, -@@ -281,6 +285,12 @@ void AndroidTextInputProps::setProp( - value, - paragraphAttributes, - maximumNumberOfLines, -+ "maximumNumberOfLines"); -+ REBUILD_FIELD_SWITCH_CASE( -+ paDefaults, -+ value, -+ paragraphAttributes, -+ numberOfLines, - "numberOfLines"); - REBUILD_FIELD_SWITCH_CASE( - paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode"); -@@ -323,6 +333,7 @@ void AndroidTextInputProps::setProp( - } - - switch (hash) { -+ RAW_SET_PROP_SWITCH_CASE_BASIC(maximumNumberOfLines); - RAW_SET_PROP_SWITCH_CASE_BASIC(autoComplete); - RAW_SET_PROP_SWITCH_CASE_BASIC(returnKeyLabel); - RAW_SET_PROP_SWITCH_CASE_BASIC(numberOfLines); -@@ -422,6 +433,7 @@ void AndroidTextInputProps::setProp( - // TODO T53300085: support this in codegen; this was hand-written - folly::dynamic AndroidTextInputProps::getDynamic() const { - folly::dynamic props = folly::dynamic::object(); -+ props["maximumNumberOfLines"] = maximumNumberOfLines; - props["autoComplete"] = autoComplete; - props["returnKeyLabel"] = returnKeyLabel; - props["numberOfLines"] = numberOfLines; -diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h -index ba39ebb..ead28e3 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h -+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h -@@ -84,6 +84,7 @@ class AndroidTextInputProps final : public ViewProps, public BaseTextProps { - std::string autoComplete{}; - std::string returnKeyLabel{}; - int numberOfLines{0}; -+ int maximumNumberOfLines{0}; - bool disableFullscreenUI{false}; - std::string textBreakStrategy{}; - SharedColor underlineColorAndroid{}; -diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -index 368c334..a1bb33e 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -+++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -@@ -244,26 +244,51 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString - - #pragma mark - Private - --- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString -++- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)inputAttributedString - paragraphAttributes:(ParagraphAttributes)paragraphAttributes - size:(CGSize)size - { -- NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size]; -+ NSMutableAttributedString *attributedString = [ inputAttributedString mutableCopy]; -+ -+ /* -+ * The block below is responsible for setting the exact height of the view in lines -+ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines -+ * prop and then add random lines at the front. However, they are only used for layout -+ * so they are not visible on the screen. This method is used for drawing only for Paragraph component -+ * but we set exact height in lines only on TextInput that doesn't use it. -+ */ -+ if (paragraphAttributes.numberOfLines) { -+ paragraphAttributes.maximumNumberOfLines = paragraphAttributes.numberOfLines; -+ NSMutableString *newLines = [NSMutableString stringWithCapacity: paragraphAttributes.numberOfLines]; -+ for (NSUInteger i = 0UL; i < paragraphAttributes.numberOfLines; ++i) { -+ // K is added on purpose. New line seems to be not enough for NTtextContainer -+ [newLines appendString:@"K\n"]; -+ } -+ NSDictionary * attributesOfFirstCharacter = [inputAttributedString attributesAtIndex:0 effectiveRange:NULL]; - -- textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. -- textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 -- ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) -- : NSLineBreakByClipping; -- textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; -+ [attributedString insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:attributesOfFirstCharacter] atIndex:0]; -+ } -+ -+ NSTextContainer *textContainer = [NSTextContainer new]; - - NSLayoutManager *layoutManager = [NSLayoutManager new]; - layoutManager.usesFontLeading = NO; - [layoutManager addTextContainer:textContainer]; - -- NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; -+ NSTextStorage *textStorage = [NSTextStorage new]; - - [textStorage addLayoutManager:layoutManager]; - -+ textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. -+ textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 -+ ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) -+ : NSLineBreakByClipping; -+ textContainer.size = size; -+ textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; -+ -+ [textStorage replaceCharactersInRange:(NSRange){0, textStorage.length} withAttributedString:attributedString]; -+ -+ - if (paragraphAttributes.adjustsFontSizeToFit) { - CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0; - CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0; From 0c3ba38b6c3e5bdcac15eba3aaefb289196958be Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 12:47:35 +0100 Subject: [PATCH 02/57] add numberOfLines workaround --- src/components/Composer/index.android.js | 35 +++++++++---------- src/components/Composer/index.ios.js | 30 ++++++++-------- .../updateNumberOfLines/index.native.ts | 2 ++ 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index 278965cbc81a..be5511b70005 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -1,11 +1,14 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; +import styles from '@styles/styles'; import themeColors from '@styles/themes/default'; +const COMPOSER_LINE_HEIGHT = styles.textInputCompose.lineHeight || 0; + const propTypes = { /** Maximum number of lines in the text input */ maxLines: PropTypes.number, @@ -92,16 +95,17 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - /** - * Set maximum number of lines - * @return {Number} - */ - const maxNumberOfLines = useMemo(() => { - if (isComposerFullSize) { - return 1000000; - } - return maxLines; - }, [isComposerFullSize, maxLines]); + const [numberOfLines, setNumberOfLines] = useState(1); + const heightStyle = useMemo( + () => + StyleSheet.create({ + composerHeight: { + height: numberOfLines * COMPOSER_LINE_HEIGHT, + maxHeight: maxLines * COMPOSER_LINE_HEIGHT, + }, + }).composerHeight, + [maxLines, numberOfLines], + ); const composerStyles = useMemo(() => { StyleSheet.flatten(props.style); @@ -112,15 +116,10 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC autoComplete="off" placeholderTextColor={themeColors.placeholderText} ref={setTextInputRef} - onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} + onContentSizeChange={(e) => setNumberOfLines(ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e))} rejectResponderTermination={false} - // Setting a really high number here fixes an issue with the `maxNumberOfLines` prop on TextInput, where on Android the text input would collapse to only one line, - // when it should actually expand to the container (https://github.com/Expensify/App/issues/11694#issuecomment-1560520670) - // @Szymon20000 is working on fixing this (android-only) issue in the in the upstream PR (https://github.com/facebook/react-native/pulls?q=is%3Apr+is%3Aopen+maxNumberOfLines) - // TODO: remove this comment once upstream PR is merged and available in a future release - maxNumberOfLines={maxNumberOfLines} textAlignVertical="center" - style={[composerStyles]} + style={[composerStyles, heightStyle]} /* eslint-disable-next-line react/jsx-props-no-spreading */ {...props} readOnly={isDisabled} diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index a1b8c1a4ffe6..980989ff6412 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; @@ -7,6 +7,8 @@ import * as ComposerUtils from '@libs/ComposerUtils'; import styles from '@styles/styles'; import themeColors from '@styles/themes/default'; +const COMPOSER_LINE_HEIGHT = styles.textInputCompose.lineHeight || 0; + const propTypes = { /** If the input should clear, it actually gets intercepted instead of .clear() */ shouldClear: PropTypes.bool, @@ -93,16 +95,17 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - /** - * Set maximum number of lines - * @return {Number} - */ - const maxNumberOfLines = useMemo(() => { - if (isComposerFullSize) { - return; - } - return maxLines; - }, [isComposerFullSize, maxLines]); + const [numberOfLines, setNumberOfLines] = useState(1); + const heightStyle = useMemo( + () => + StyleSheet.create({ + composerHeight: { + height: numberOfLines * COMPOSER_LINE_HEIGHT, + maxHeight: maxLines * COMPOSER_LINE_HEIGHT, + }, + }).composerHeight, + [maxLines, numberOfLines], + ); const composerStyles = useMemo(() => { StyleSheet.flatten(props.style); @@ -117,11 +120,10 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC autoComplete="off" placeholderTextColor={themeColors.placeholderText} ref={setTextInputRef} - onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} + onContentSizeChange={(e) => setNumberOfLines(ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e))} rejectResponderTermination={false} smartInsertDelete={false} - maxNumberOfLines={maxNumberOfLines} - style={[composerStyles, styles.verticalAlignMiddle]} + style={[composerStyles, styles.verticalAlignMiddle, heightStyle]} /* eslint-disable-next-line react/jsx-props-no-spreading */ {...propsToPass} readOnly={isDisabled} diff --git a/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts index df9292ecd690..ef89ef2dc0d8 100644 --- a/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts +++ b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts @@ -16,6 +16,8 @@ const updateNumberOfLines: UpdateNumberOfLines = (props, event) => { } const numberOfLines = getNumberOfLines(lineHeight, paddingTopAndBottom, inputHeight); updateIsFullComposerAvailable(props, numberOfLines); + + return numberOfLines; }; export default updateNumberOfLines; From 8f830ab1d06e2b506d7aee35e20e866c67ba5565 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 13:57:04 +0100 Subject: [PATCH 03/57] fix: workaround for iOS --- src/components/Composer/index.ios.js | 30 ++++++++++------------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index 980989ff6412..090e67827ccd 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {StyleSheet} from 'react-native'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; @@ -95,37 +94,28 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - const [numberOfLines, setNumberOfLines] = useState(1); - const heightStyle = useMemo( - () => - StyleSheet.create({ - composerHeight: { - height: numberOfLines * COMPOSER_LINE_HEIGHT, - maxHeight: maxLines * COMPOSER_LINE_HEIGHT, - }, - }).composerHeight, - [maxLines, numberOfLines], + const maxHeightStyle = useMemo( + () => ({ + maxHeight: maxLines * COMPOSER_LINE_HEIGHT, + }), + [maxLines], ); - const composerStyles = useMemo(() => { - StyleSheet.flatten(props.style); - }, [props.style]); - // On native layers we like to have the Text Input not focused so the // user can read new chats without the keyboard in the way of the view. // On Android the selection prop is required on the TextInput but this prop has issues on IOS const propsToPass = _.omit(props, 'selection'); return ( setNumberOfLines(ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e))} + onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} smartInsertDelete={false} - style={[composerStyles, styles.verticalAlignMiddle, heightStyle]} - /* eslint-disable-next-line react/jsx-props-no-spreading */ - {...propsToPass} + style={[...props.style, isComposerFullSize ? undefined : maxHeightStyle]} readOnly={isDisabled} /> ); From 569c388e19a59040447b530e317fedd26e3084c1 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 14:08:28 +0100 Subject: [PATCH 04/57] fix: android implementation --- src/components/Composer/index.android.js | 25 ++++++++---------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index be5511b70005..9e27c5bb9b60 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -1,6 +1,5 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; -import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; @@ -96,32 +95,24 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC }, [shouldClear, onClear]); const [numberOfLines, setNumberOfLines] = useState(1); - const heightStyle = useMemo( - () => - StyleSheet.create({ - composerHeight: { - height: numberOfLines * COMPOSER_LINE_HEIGHT, - maxHeight: maxLines * COMPOSER_LINE_HEIGHT, - }, - }).composerHeight, - [maxLines, numberOfLines], + const maxHeightStyle = useMemo( + () => ({ + maxHeight: maxLines * COMPOSER_LINE_HEIGHT, + }), + [maxLines], ); - const composerStyles = useMemo(() => { - StyleSheet.flatten(props.style); - }, [props.style]); - return ( setNumberOfLines(ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e))} rejectResponderTermination={false} textAlignVertical="center" - style={[composerStyles, heightStyle]} - /* eslint-disable-next-line react/jsx-props-no-spreading */ - {...props} + style={[...props.style, maxHeightStyle]} readOnly={isDisabled} /> ); From ae5d0aa530067aa5b79de63a1c02ebd27c7c1d61 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 16:21:54 +0100 Subject: [PATCH 05/57] fix: android --- src/components/Composer/index.android.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index 9e27c5bb9b60..477e8b2f88a3 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -94,7 +94,6 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - const [numberOfLines, setNumberOfLines] = useState(1); const maxHeightStyle = useMemo( () => ({ maxHeight: maxLines * COMPOSER_LINE_HEIGHT, @@ -109,10 +108,10 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC autoComplete="off" placeholderTextColor={themeColors.placeholderText} ref={setTextInputRef} - onContentSizeChange={(e) => setNumberOfLines(ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e))} + onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} textAlignVertical="center" - style={[...props.style, maxHeightStyle]} + style={[...props.style, isComposerFullSize ? undefined : maxHeightStyle]} readOnly={isDisabled} /> ); From a6c6602a0cb89ae18ad9e56d78f0de11427b845c Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 16:26:10 +0100 Subject: [PATCH 06/57] fix: extract style --- src/components/Composer/index.android.js | 15 ++++----------- src/components/Composer/index.ios.js | 13 +++---------- src/styles/StyleUtils.ts | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index 477e8b2f88a3..7b72e17ae5fe 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -1,13 +1,11 @@ import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; -import styles from '@styles/styles'; +import {getComposerMaxHeightStyle} from '@styles/StyleUtils'; import themeColors from '@styles/themes/default'; -const COMPOSER_LINE_HEIGHT = styles.textInputCompose.lineHeight || 0; - const propTypes = { /** Maximum number of lines in the text input */ maxLines: PropTypes.number, @@ -94,12 +92,7 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - const maxHeightStyle = useMemo( - () => ({ - maxHeight: maxLines * COMPOSER_LINE_HEIGHT, - }), - [maxLines], - ); + const maxHeightStyle = useMemo(() => getComposerMaxHeightStyle(maxLines, isComposerFullSize), [isComposerFullSize, maxLines]); return ( ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} textAlignVertical="center" - style={[...props.style, isComposerFullSize ? undefined : maxHeightStyle]} + style={[...props.style, maxHeightStyle]} readOnly={isDisabled} /> ); diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index 090e67827ccd..efbda8cae2ad 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -3,11 +3,9 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; -import styles from '@styles/styles'; +import {getComposerMaxHeightStyle} from '@styles/StyleUtils'; import themeColors from '@styles/themes/default'; -const COMPOSER_LINE_HEIGHT = styles.textInputCompose.lineHeight || 0; - const propTypes = { /** If the input should clear, it actually gets intercepted instead of .clear() */ shouldClear: PropTypes.bool, @@ -94,12 +92,7 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - const maxHeightStyle = useMemo( - () => ({ - maxHeight: maxLines * COMPOSER_LINE_HEIGHT, - }), - [maxLines], - ); + const maxHeightStyle = useMemo(() => getComposerMaxHeightStyle(maxLines, isComposerFullSize), [isComposerFullSize, maxLines]); // On native layers we like to have the Text Input not focused so the // user can read new chats without the keyboard in the way of the view. @@ -115,7 +108,7 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} smartInsertDelete={false} - style={[...props.style, isComposerFullSize ? undefined : maxHeightStyle]} + style={[...props.style, maxHeightStyle]} readOnly={isDisabled} /> ); diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index a99d292e9dc6..b9b0347c9d7c 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1389,6 +1389,19 @@ function getDotIndicatorTextStyles(isErrorText = true): TextStyle { return isErrorText ? {...styles.offlineFeedback.text, color: styles.formError.color} : {...styles.offlineFeedback.text}; } +/** + * Returns container styles for showing the icons in MultipleAvatars/SubscriptAvatar + */ +function getComposerMaxHeightStyle(maxLines: number, isComposerFullSize: boolean): ViewStyle | undefined { + const composerLineHeight = styles.textInputCompose.lineHeight ?? 0; + + return isComposerFullSize + ? undefined + : { + maxHeight: maxLines * composerLineHeight, + }; +} + export { combineStyles, displayIfTrue, @@ -1473,4 +1486,5 @@ export { getContainerStyles, getEReceiptColorStyles, getEReceiptColorCode, + getComposerMaxHeightStyle, }; From abbd499e46b5509e6fbcac9b23830e1728f023b5 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 16:53:01 +0100 Subject: [PATCH 07/57] remove unused style --- src/styles/styles.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 63e219d7e953..a0c17d01a7a8 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -328,10 +328,6 @@ const styles = (theme: ThemeColors) => textAlign: 'left', }, - verticalAlignMiddle: { - verticalAlign: 'middle', - }, - verticalAlignTop: { verticalAlign: 'top', }, From 43a03239629f22af6360349889f973d1c875588e Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Fri, 17 Nov 2023 16:53:43 +0100 Subject: [PATCH 08/57] remove unused return --- src/libs/ComposerUtils/updateNumberOfLines/index.native.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts index ef89ef2dc0d8..df9292ecd690 100644 --- a/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts +++ b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts @@ -16,8 +16,6 @@ const updateNumberOfLines: UpdateNumberOfLines = (props, event) => { } const numberOfLines = getNumberOfLines(lineHeight, paddingTopAndBottom, inputHeight); updateIsFullComposerAvailable(props, numberOfLines); - - return numberOfLines; }; export default updateNumberOfLines; From 221f30e250f79ac22935d7a063f9206468c50709 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 23 Nov 2023 14:40:15 +0100 Subject: [PATCH 09/57] rename patch --- ....patch => react-native+0.72.4+002+ModalKeyboardFlashing.patch} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename patches/{react-native+0.72.4+004+ModalKeyboardFlashing.patch => react-native+0.72.4+002+ModalKeyboardFlashing.patch} (100%) diff --git a/patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch b/patches/react-native+0.72.4+002+ModalKeyboardFlashing.patch similarity index 100% rename from patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch rename to patches/react-native+0.72.4+002+ModalKeyboardFlashing.patch From 110943e716aaafa1b97e6fbba78043e13aafbfc7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 17:47:09 +0100 Subject: [PATCH 10/57] allow dark statusbar content --- src/components/CustomStatusBar/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js index a724c71059ef..4039c7225861 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.js @@ -2,9 +2,13 @@ import React, {useEffect} from 'react'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; import useTheme from '@styles/themes/useTheme'; +import useThemePreference from '@styles/themes/useThemePreference'; +import CONST from '@src/CONST'; function CustomStatusBar() { + const preferredTheme = useThemePreference(); const theme = useTheme(); + useEffect(() => { Navigation.isNavigationReady().then(() => { // Set the status bar colour depending on the current route. @@ -15,10 +19,10 @@ function CustomStatusBar() { if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; } - StatusBar.setBarStyle('light-content', true); + StatusBar.setBarStyle(preferredTheme === CONST.THEMES.LIGHT ? 'dark-content' : 'light-content', true); StatusBar.setBackgroundColor(currentScreenBackgroundColor); }); - }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG]); + }, [preferredTheme, theme.PAGE_BACKGROUND_COLORS, theme.appBG]); return ; } From 945953fdd20d820b6117b54e2ac4463f2d1623fe Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 18:11:44 +0100 Subject: [PATCH 11/57] add CustomScrollbarWrapper and use in SignInPage --- .../CustomScrollbarWrapper/index.tsx | 13 +++++++++++++ src/pages/signin/SignInPage.js | 19 +++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 src/components/CustomScrollbarWrapper/index.tsx diff --git a/src/components/CustomScrollbarWrapper/index.tsx b/src/components/CustomScrollbarWrapper/index.tsx new file mode 100644 index 000000000000..a49c6bb5cfe9 --- /dev/null +++ b/src/components/CustomScrollbarWrapper/index.tsx @@ -0,0 +1,13 @@ +import {View} from 'react-native'; +import {ThemePreferenceWithoutSystem} from '@styles/themes/types'; +import useThemePreferenceWithStaticOverride from '@styles/themes/useThemePreferenceWithStaticOverride'; + +export type CustomScrollbarWrapperProps = React.PropsWithChildren & { + theme?: ThemePreferenceWithoutSystem; +}; + +export const CustomScrollbarWrapper: React.FC = ({children, theme: staticThemePreference}) => { + const preferredTheme = useThemePreferenceWithStaticOverride(staticThemePreference); + + return {children}; +}; diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index b0ced580b30e..e25621f1f3d2 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -5,6 +5,7 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import _ from 'underscore'; +import {CustomScrollbarWrapper} from '@components/CustomScrollbarWrapper'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as ActiveClientManager from '@libs/ActiveClientManager'; @@ -277,14 +278,16 @@ SignInPageInner.displayName = 'SignInPage'; function SignInPage(props) { return ( - - - - - + + + + + + + ); } From 4ddd49818cf1993f157e5514ce921af63ef9e1ef Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 18:28:29 +0100 Subject: [PATCH 12/57] fix: typo --- src/components/CustomStatusBar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js index 4039c7225861..afca120374b4 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.js @@ -19,7 +19,7 @@ function CustomStatusBar() { if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; } - StatusBar.setBarStyle(preferredTheme === CONST.THEMES.LIGHT ? 'dark-content' : 'light-content', true); + StatusBar.setBarStyle(preferredTheme === CONST.THEME.LIGHT ? 'dark-content' : 'light-content', true); StatusBar.setBackgroundColor(currentScreenBackgroundColor); }); }, [preferredTheme, theme.PAGE_BACKGROUND_COLORS, theme.appBG]); From 89f924e528686cabcd605d50142d915af33e03f7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 18:40:56 +0100 Subject: [PATCH 13/57] adapt Themes --- src/styles/themes/ThemeProvider.tsx | 2 +- src/styles/themes/Themes.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/styles/themes/ThemeProvider.tsx b/src/styles/themes/ThemeProvider.tsx index 20f97025c36a..5d4560314812 100644 --- a/src/styles/themes/ThemeProvider.tsx +++ b/src/styles/themes/ThemeProvider.tsx @@ -18,7 +18,7 @@ type ThemeProviderProps = React.PropsWithChildren & { function ThemeProvider({children, theme: staticThemePreference}: ThemeProviderProps) { const themePreference = useThemePreferenceWithStaticOverride(staticThemePreference); - const theme = useMemo(() => Themes[themePreference], [themePreference]); + const theme = useMemo(() => Themes[themePreference].theme, [themePreference]); return {children}; } diff --git a/src/styles/themes/Themes.ts b/src/styles/themes/Themes.ts index a87407790502..f5729c2ccae2 100644 --- a/src/styles/themes/Themes.ts +++ b/src/styles/themes/Themes.ts @@ -3,9 +3,12 @@ import darkTheme from './default'; import lightTheme from './light'; import {ThemeColors, ThemePreferenceWithoutSystem} from './types'; +// There might be more themes than just "dark" and "light". +// Still, status bar, scrollbar and other components might need to adapt based on if the theme is overly light or dark +// e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme const Themes = { - [CONST.THEME.LIGHT]: lightTheme, - [CONST.THEME.DARK]: darkTheme, -} satisfies Record; + [CONST.THEME.LIGHT]: {theme: lightTheme, isLight: true}, + [CONST.THEME.DARK]: {theme: darkTheme, isLight: false}, +} satisfies Record; export default Themes; From 81515d4c360728f528597eb1db3ad325d0676955 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 19:13:55 +0100 Subject: [PATCH 14/57] remove isLight condition --- src/styles/themes/ThemeProvider.tsx | 2 +- src/styles/themes/Themes.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/styles/themes/ThemeProvider.tsx b/src/styles/themes/ThemeProvider.tsx index 5d4560314812..20f97025c36a 100644 --- a/src/styles/themes/ThemeProvider.tsx +++ b/src/styles/themes/ThemeProvider.tsx @@ -18,7 +18,7 @@ type ThemeProviderProps = React.PropsWithChildren & { function ThemeProvider({children, theme: staticThemePreference}: ThemeProviderProps) { const themePreference = useThemePreferenceWithStaticOverride(staticThemePreference); - const theme = useMemo(() => Themes[themePreference].theme, [themePreference]); + const theme = useMemo(() => Themes[themePreference], [themePreference]); return {children}; } diff --git a/src/styles/themes/Themes.ts b/src/styles/themes/Themes.ts index f5729c2ccae2..082749d931bb 100644 --- a/src/styles/themes/Themes.ts +++ b/src/styles/themes/Themes.ts @@ -7,8 +7,8 @@ import {ThemeColors, ThemePreferenceWithoutSystem} from './types'; // Still, status bar, scrollbar and other components might need to adapt based on if the theme is overly light or dark // e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme const Themes = { - [CONST.THEME.LIGHT]: {theme: lightTheme, isLight: true}, - [CONST.THEME.DARK]: {theme: darkTheme, isLight: false}, -} satisfies Record; + [CONST.THEME.LIGHT]: lightTheme, + [CONST.THEME.DARK]: darkTheme, +} satisfies Record; export default Themes; From b2d29280cc43fed05d3252927fbd22adb87bf4f2 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 19:14:11 +0100 Subject: [PATCH 15/57] remove comment --- src/styles/themes/Themes.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/styles/themes/Themes.ts b/src/styles/themes/Themes.ts index 082749d931bb..a87407790502 100644 --- a/src/styles/themes/Themes.ts +++ b/src/styles/themes/Themes.ts @@ -3,9 +3,6 @@ import darkTheme from './default'; import lightTheme from './light'; import {ThemeColors, ThemePreferenceWithoutSystem} from './types'; -// There might be more themes than just "dark" and "light". -// Still, status bar, scrollbar and other components might need to adapt based on if the theme is overly light or dark -// e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme const Themes = { [CONST.THEME.LIGHT]: lightTheme, [CONST.THEME.DARK]: darkTheme, From 8f23391491fe2013b881f674c11d2e7a06c81c86 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 19:15:24 +0100 Subject: [PATCH 16/57] add additional theme keys for status bar and scrollbar --- src/styles/themes/default.ts | 4 ++++ src/styles/themes/light.ts | 3 +++ src/styles/themes/types.ts | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index 8dd76a4776a4..a0d9e0eb72a1 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,3 +1,4 @@ +import {StatusBar} from 'react-native'; import colors from '@styles/colors'; import SCREENS from '@src/SCREENS'; import {ThemeColors} from './types'; @@ -99,6 +100,9 @@ const darkTheme = { [SCREENS.SETTINGS.STATUS]: colors.green700, [SCREENS.SETTINGS.ROOT]: colors.darkHighlightBackground, }, + + statusBarContentTheme: 'light', + scrollBarTheme: 'dark', } satisfies ThemeColors; export default darkTheme; diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index cbaadcf196ba..8640249d147c 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -99,6 +99,9 @@ const lightTheme = { [SCREENS.SETTINGS.STATUS]: colors.green700, [SCREENS.SETTINGS.ROOT]: colors.lightHighlightBackground, }, + + statusBarContentTheme: 'dark', + scrollBarTheme: 'light', } satisfies ThemeColors; export default lightTheme; diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index d6d1dcea0833..4b2ab4f62cbe 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -1,5 +1,6 @@ import CONST from '@src/CONST'; +type ContentTheme = 'light' | 'dark'; type Color = string; type ThemePreference = (typeof CONST.THEME)[keyof typeof CONST.THEME]; @@ -89,6 +90,12 @@ type ThemeColors = { white: Color; PAGE_BACKGROUND_COLORS: Record; + + // Status bar and scroll bars need to adapt their theme based on the currently displayed user theme for good contrast + // Therefore, we need to define the + // e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme + statusBarContentTheme: ContentTheme; + scrollBarTheme: ContentTheme; }; export {type ThemePreference, type ThemePreferenceWithoutSystem, type ThemeColors, type Color}; From 3bd413699219bf9145b38fbe7f1d0dc0a9b5a803 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 19:20:33 +0100 Subject: [PATCH 17/57] update CustomStatusBar wrapper --- src/components/CustomStatusBar/index.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js index afca120374b4..e0bdbd076b33 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.js @@ -2,11 +2,8 @@ import React, {useEffect} from 'react'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; import useTheme from '@styles/themes/useTheme'; -import useThemePreference from '@styles/themes/useThemePreference'; -import CONST from '@src/CONST'; function CustomStatusBar() { - const preferredTheme = useThemePreference(); const theme = useTheme(); useEffect(() => { @@ -19,10 +16,10 @@ function CustomStatusBar() { if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; } - StatusBar.setBarStyle(preferredTheme === CONST.THEME.LIGHT ? 'dark-content' : 'light-content', true); + StatusBar.setBarStyle(theme.statusBarContentTheme === 'light' ? 'light-content' : 'dark-content', true); StatusBar.setBackgroundColor(currentScreenBackgroundColor); }); - }, [preferredTheme, theme.PAGE_BACKGROUND_COLORS, theme.appBG]); + }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG, theme.statusBarContentTheme]); return ; } From 4abcfd7c2786d617feb7018ba16e009b2ede6a91 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 19:20:55 +0100 Subject: [PATCH 18/57] add noop for native --- .../CustomScrollbarWrapperProps.ts | 7 +++++++ .../CustomScrollbarWrapper/index.native.tsx | 7 +++++++ src/components/CustomScrollbarWrapper/index.tsx | 13 ------------- 3 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts create mode 100644 src/components/CustomScrollbarWrapper/index.native.tsx delete mode 100644 src/components/CustomScrollbarWrapper/index.tsx diff --git a/src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts b/src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts new file mode 100644 index 000000000000..ced7d72070c8 --- /dev/null +++ b/src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts @@ -0,0 +1,7 @@ +import {ThemePreferenceWithoutSystem} from '@styles/themes/types'; + +type CustomScrollbarWrapperProps = React.PropsWithChildren & { + theme?: ThemePreferenceWithoutSystem; +}; + +export default CustomScrollbarWrapperProps; diff --git a/src/components/CustomScrollbarWrapper/index.native.tsx b/src/components/CustomScrollbarWrapper/index.native.tsx new file mode 100644 index 000000000000..30437c7f6757 --- /dev/null +++ b/src/components/CustomScrollbarWrapper/index.native.tsx @@ -0,0 +1,7 @@ +import CustomScrollbarWrapperProps from './CustomScrollbarWrapperProps'; + +function CustomScrollbarWrapper({children}: CustomScrollbarWrapperProps) { + return <>{children}; +} + +export default CustomScrollbarWrapper; diff --git a/src/components/CustomScrollbarWrapper/index.tsx b/src/components/CustomScrollbarWrapper/index.tsx deleted file mode 100644 index a49c6bb5cfe9..000000000000 --- a/src/components/CustomScrollbarWrapper/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import {View} from 'react-native'; -import {ThemePreferenceWithoutSystem} from '@styles/themes/types'; -import useThemePreferenceWithStaticOverride from '@styles/themes/useThemePreferenceWithStaticOverride'; - -export type CustomScrollbarWrapperProps = React.PropsWithChildren & { - theme?: ThemePreferenceWithoutSystem; -}; - -export const CustomScrollbarWrapper: React.FC = ({children, theme: staticThemePreference}) => { - const preferredTheme = useThemePreferenceWithStaticOverride(staticThemePreference); - - return {children}; -}; From 7ac2d014f179330ed8b918eb1da9538c1dd2f956 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 19:32:14 +0100 Subject: [PATCH 19/57] implement custom scrollbar theme --- src/App.js | 5 ++++- src/components/CustomScrollbarWrapper/index.tsx | 14 ++++++++++++++ web/index.html | 5 ++++- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 src/components/CustomScrollbarWrapper/index.tsx diff --git a/src/App.js b/src/App.js index ac34ece5c6c7..4d680dd92d61 100644 --- a/src/App.js +++ b/src/App.js @@ -7,6 +7,7 @@ import {PickerStateProvider} from 'react-native-picker-select'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; import ComposeProviders from './components/ComposeProviders'; +import CustomScrollbarWrapper from './components/CustomScrollbarWrapper'; import CustomStatusBar from './components/CustomStatusBar'; import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; @@ -70,7 +71,9 @@ function App() { > - + + + diff --git a/src/components/CustomScrollbarWrapper/index.tsx b/src/components/CustomScrollbarWrapper/index.tsx new file mode 100644 index 000000000000..9454f5484c57 --- /dev/null +++ b/src/components/CustomScrollbarWrapper/index.tsx @@ -0,0 +1,14 @@ +import React, {CSSProperties, useMemo} from 'react'; +import {View} from 'react-native'; +import Themes from '@styles/themes/Themes'; +import useThemePreferenceWithStaticOverride from '@styles/themes/useThemePreferenceWithStaticOverride'; +import CustomScrollbarWrapperProps from './CustomScrollbarWrapperProps'; + +function CustomScrollbarWrapper({children, theme: staticThemePreference}: CustomScrollbarWrapperProps): React.ReactElement { + const preferredTheme = useThemePreferenceWithStaticOverride(staticThemePreference); + const scrollbarTheme = useMemo(() => Themes[preferredTheme].scrollBarTheme, [preferredTheme]); + + return {children}; +} + +export default CustomScrollbarWrapper; diff --git a/web/index.html b/web/index.html index 6e5d0cd3c5d6..15b58e8fe0c5 100644 --- a/web/index.html +++ b/web/index.html @@ -31,7 +31,10 @@ #root > div > div { height: 100% !important; } - :root { + .scrollbar-light { + color-scheme: light !important; + } + .scrollbar-light { color-scheme: dark !important; } * { From c0e1db1e59b0dff5b56d628d5327d92f6997d531 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 19:39:09 +0100 Subject: [PATCH 20/57] update CustomStatusBar --- src/components/CustomStatusBar/index.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.js index e0bdbd076b33..0b4d51817628 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.js @@ -1,10 +1,11 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useMemo} from 'react'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; import useTheme from '@styles/themes/useTheme'; function CustomStatusBar() { const theme = useTheme(); + const statusBarContentTheme = useMemo(() => (theme.statusBarContentTheme === 'light' ? 'light-content' : 'dark-content'), [theme.statusBarContentTheme]); useEffect(() => { Navigation.isNavigationReady().then(() => { @@ -16,10 +17,16 @@ function CustomStatusBar() { if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; } - StatusBar.setBarStyle(theme.statusBarContentTheme === 'light' ? 'light-content' : 'dark-content', true); + StatusBar.setBarStyle(statusBarContentTheme, true); StatusBar.setBackgroundColor(currentScreenBackgroundColor); }); - }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG, theme.statusBarContentTheme]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG]); + + useEffect(() => { + StatusBar.setBarStyle(statusBarContentTheme, true); + }, [statusBarContentTheme]); + return ; } From cde61a7d4067179206fd76b3f4cf55f5b982dbb0 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 21:11:31 +0100 Subject: [PATCH 21/57] implement CustomStatusBarContext to avoid multiple StatusBars from interferring --- src/App.js | 2 ++ .../CustomStatusBarContext.tsx | 11 +++++++ .../CustomStatusBarContextProvider.tsx | 17 +++++++++++ .../{index.android.js => index.android.tsx} | 0 .../CustomStatusBar/{index.js => index.tsx} | 29 +++++++++++++++++-- 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 src/components/CustomStatusBar/CustomStatusBarContext.tsx create mode 100644 src/components/CustomStatusBar/CustomStatusBarContextProvider.tsx rename src/components/CustomStatusBar/{index.android.js => index.android.tsx} (100%) rename src/components/CustomStatusBar/{index.js => index.tsx} (65%) diff --git a/src/App.js b/src/App.js index 4d680dd92d61..f3f342d3df09 100644 --- a/src/App.js +++ b/src/App.js @@ -9,6 +9,7 @@ import '../wdyr'; import ComposeProviders from './components/ComposeProviders'; import CustomScrollbarWrapper from './components/CustomScrollbarWrapper'; import CustomStatusBar from './components/CustomStatusBar'; +import CustomStatusBarContextProvider from './components/CustomStatusBar/CustomStatusBarContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; @@ -67,6 +68,7 @@ function App() { ThemeProvider, ThemeStylesProvider, ThemeIllustrationsProvider, + CustomStatusBarContextProvider, ]} > diff --git a/src/components/CustomStatusBar/CustomStatusBarContext.tsx b/src/components/CustomStatusBar/CustomStatusBarContext.tsx new file mode 100644 index 000000000000..b2c317b05c25 --- /dev/null +++ b/src/components/CustomStatusBar/CustomStatusBarContext.tsx @@ -0,0 +1,11 @@ +import {createContext} from 'react'; + +type CustomStatusBarContextType = { + isRootStatusBarDisabled: boolean; + disableRootStatusBar: (isDisabled: boolean) => void; +}; + +const CustomStatusBarContext = createContext({isRootStatusBarDisabled: false, disableRootStatusBar: () => undefined}); + +export default CustomStatusBarContext; +export {type CustomStatusBarContextType}; diff --git a/src/components/CustomStatusBar/CustomStatusBarContextProvider.tsx b/src/components/CustomStatusBar/CustomStatusBarContextProvider.tsx new file mode 100644 index 000000000000..27a5cac5d8cc --- /dev/null +++ b/src/components/CustomStatusBar/CustomStatusBarContextProvider.tsx @@ -0,0 +1,17 @@ +import React, {useMemo, useState} from 'react'; +import CustomStatusBarContext from './CustomStatusBarContext'; + +function CustomStatusBarContextProvider({children}: React.PropsWithChildren) { + const [isRootStatusBarDisabled, disableRootStatusBar] = useState(false); + const value = useMemo( + () => ({ + isRootStatusBarDisabled, + disableRootStatusBar, + }), + [isRootStatusBarDisabled], + ); + + return {children}; +} + +export default CustomStatusBarContextProvider; diff --git a/src/components/CustomStatusBar/index.android.js b/src/components/CustomStatusBar/index.android.tsx similarity index 100% rename from src/components/CustomStatusBar/index.android.js rename to src/components/CustomStatusBar/index.android.tsx diff --git a/src/components/CustomStatusBar/index.js b/src/components/CustomStatusBar/index.tsx similarity index 65% rename from src/components/CustomStatusBar/index.js rename to src/components/CustomStatusBar/index.tsx index 0b4d51817628..41e7c125e400 100644 --- a/src/components/CustomStatusBar/index.js +++ b/src/components/CustomStatusBar/index.tsx @@ -1,13 +1,34 @@ -import React, {useEffect, useMemo} from 'react'; +import React, {useContext, useEffect, useMemo} from 'react'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; import useTheme from '@styles/themes/useTheme'; +import CustomStatusBarContext from './CustomStatusBarContext'; -function CustomStatusBar() { +type CustomStatusBarProps = { + isNested: boolean; +}; + +function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactElement | null { + const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarContext); const theme = useTheme(); const statusBarContentTheme = useMemo(() => (theme.statusBarContentTheme === 'light' ? 'light-content' : 'dark-content'), [theme.statusBarContentTheme]); + const isDisabled = !isNested && isRootStatusBarDisabled; + + useEffect(() => { + if (isNested) { + disableRootStatusBar(true); + } + + return () => disableRootStatusBar(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + if (isDisabled) { + return; + } + Navigation.isNavigationReady().then(() => { // Set the status bar colour depending on the current route. // If we don't have any colour defined for a route, fall back to @@ -27,6 +48,10 @@ function CustomStatusBar() { StatusBar.setBarStyle(statusBarContentTheme, true); }, [statusBarContentTheme]); + if (isDisabled) { + return null; + } + return ; } From 8040abeec62013a9b3d6338c311c2494b311c427 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 21:11:45 +0100 Subject: [PATCH 22/57] fix: CustomScrollBarWrapper --- src/components/CustomScrollbarWrapper/index.native.tsx | 2 +- src/pages/signin/SignInPage.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/CustomScrollbarWrapper/index.native.tsx b/src/components/CustomScrollbarWrapper/index.native.tsx index 30437c7f6757..c055bc5f8d1c 100644 --- a/src/components/CustomScrollbarWrapper/index.native.tsx +++ b/src/components/CustomScrollbarWrapper/index.native.tsx @@ -1,7 +1,7 @@ import CustomScrollbarWrapperProps from './CustomScrollbarWrapperProps'; function CustomScrollbarWrapper({children}: CustomScrollbarWrapperProps) { - return <>{children}; + return children; } export default CustomScrollbarWrapper; diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index e25621f1f3d2..6c14304c6c29 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -5,7 +5,8 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import _ from 'underscore'; -import {CustomScrollbarWrapper} from '@components/CustomScrollbarWrapper'; +import CustomScrollbarWrapper from '@components/CustomScrollbarWrapper'; +import CustomStatusBar from '@components/CustomStatusBar'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as ActiveClientManager from '@libs/ActiveClientManager'; @@ -280,6 +281,7 @@ function SignInPage(props) { return ( + Date: Mon, 27 Nov 2023 21:31:54 +0100 Subject: [PATCH 23/57] remove unused import --- src/styles/themes/default.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index a0d9e0eb72a1..ea98fdf37fa7 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,4 +1,3 @@ -import {StatusBar} from 'react-native'; import colors from '@styles/colors'; import SCREENS from '@src/SCREENS'; import {ThemeColors} from './types'; From 33efc4006199725691a5f402820aa06e1a68ec5d Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 21:33:30 +0100 Subject: [PATCH 24/57] update comment --- src/styles/themes/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index 4b2ab4f62cbe..ca7ac00cf054 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -91,8 +91,8 @@ type ThemeColors = { PAGE_BACKGROUND_COLORS: Record; - // Status bar and scroll bars need to adapt their theme based on the currently displayed user theme for good contrast - // Therefore, we need to define the + // Status bar and scroll bars need to adapt their theme based on the active user theme for good contrast + // Therefore, we need to define specific themes for these elements // e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme statusBarContentTheme: ContentTheme; scrollBarTheme: ContentTheme; From 02f6e748bddc557cfee23165880be78a166949c5 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 21:43:23 +0100 Subject: [PATCH 25/57] revert number of lines changes --- android/settings.gradle | 9 + ...eact-native+0.72.4+002+NumberOfLines.patch | 978 ++++++++++++++++++ ...ive+0.72.4+004+ModalKeyboardFlashing.patch | 18 + src/components/Composer/index.android.js | 28 +- src/components/Composer/index.ios.js | 25 +- src/styles/StyleUtils.ts | 14 - src/styles/styles.ts | 4 + 7 files changed, 1052 insertions(+), 24 deletions(-) create mode 100644 patches/react-native+0.72.4+002+NumberOfLines.patch create mode 100644 patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch diff --git a/android/settings.gradle b/android/settings.gradle index 680dfbc32521..c2bb3db7845a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -16,3 +16,12 @@ project(':react-native-dev-menu').projectDir = new File(rootProject.projectDir, apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' includeBuild('../node_modules/@react-native/gradle-plugin') + +includeBuild('../node_modules/react-native') { + dependencySubstitution { + substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid")) + substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid")) + substitute(module("com.facebook.react:hermes-android")).using(project(":packages:react-native:ReactAndroid:hermes-engine")) + substitute(module("com.facebook.react:hermes-engine")).using(project(":packages:react-native:ReactAndroid:hermes-engine")) + } +} diff --git a/patches/react-native+0.72.4+002+NumberOfLines.patch b/patches/react-native+0.72.4+002+NumberOfLines.patch new file mode 100644 index 000000000000..75422f84708e --- /dev/null +++ b/patches/react-native+0.72.4+002+NumberOfLines.patch @@ -0,0 +1,978 @@ +diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +index 55b770d..4073836 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +@@ -179,6 +179,13 @@ export type NativeProps = $ReadOnly<{| + */ + numberOfLines?: ?Int32, + ++ /** ++ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ * @platform android ++ */ ++ maximumNumberOfLines?: ?Int32, ++ + /** + * When `false`, if there is a small amount of space available around a text input + * (e.g. landscape orientation on a phone), the OS may choose to have the user edit +diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +index 6f69329..d531bee 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +@@ -144,6 +144,8 @@ const RCTTextInputViewConfig = { + placeholder: true, + autoCorrect: true, + multiline: true, ++ numberOfLines: true, ++ maximumNumberOfLines: true, + textContentType: true, + maxLength: true, + autoCapitalize: true, +diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts +index 8badb2a..b19f197 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts ++++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts +@@ -347,12 +347,6 @@ export interface TextInputAndroidProps { + */ + inlineImagePadding?: number | undefined; + +- /** +- * Sets the number of lines for a TextInput. +- * Use it with multiline set to true to be able to fill the lines. +- */ +- numberOfLines?: number | undefined; +- + /** + * Sets the return key to the label. Use it instead of `returnKeyType`. + * @platform android +@@ -663,11 +657,30 @@ export interface TextInputProps + */ + maxLength?: number | undefined; + ++ /** ++ * Sets the maximum number of lines for a TextInput. ++ * Use it with multiline set to true to be able to fill the lines. ++ */ ++ maxNumberOfLines?: number | undefined; ++ + /** + * If true, the text input can be multiple lines. The default value is false. + */ + multiline?: boolean | undefined; + ++ /** ++ * Sets the number of lines for a TextInput. ++ * Use it with multiline set to true to be able to fill the lines. ++ */ ++ numberOfLines?: number | undefined; ++ ++ /** ++ * Sets the number of rows for a TextInput. ++ * Use it with multiline set to true to be able to fill the lines. ++ */ ++ rows?: number | undefined; ++ ++ + /** + * Callback that is called when the text input is blurred + */ +diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js +index 7ed4579..b1d994e 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js +@@ -343,26 +343,12 @@ type AndroidProps = $ReadOnly<{| + */ + inlineImagePadding?: ?number, + +- /** +- * Sets the number of lines for a `TextInput`. Use it with multiline set to +- * `true` to be able to fill the lines. +- * @platform android +- */ +- numberOfLines?: ?number, +- + /** + * Sets the return key to the label. Use it instead of `returnKeyType`. + * @platform android + */ + returnKeyLabel?: ?string, + +- /** +- * Sets the number of rows for a `TextInput`. Use it with multiline set to +- * `true` to be able to fill the lines. +- * @platform android +- */ +- rows?: ?number, +- + /** + * When `false`, it will prevent the soft keyboard from showing when the field is focused. + * Defaults to `true`. +@@ -632,6 +618,12 @@ export type Props = $ReadOnly<{| + */ + keyboardType?: ?KeyboardType, + ++ /** ++ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ */ ++ maxNumberOfLines?: ?number, ++ + /** + * Specifies largest possible scale a font can reach when `allowFontScaling` is enabled. + * Possible values: +@@ -653,6 +645,12 @@ export type Props = $ReadOnly<{| + */ + multiline?: ?boolean, + ++ /** ++ * Sets the number of lines for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ */ ++ numberOfLines?: ?number, ++ + /** + * Callback that is called when the text input is blurred. + */ +@@ -814,6 +812,12 @@ export type Props = $ReadOnly<{| + */ + returnKeyType?: ?ReturnKeyType, + ++ /** ++ * Sets the number of rows for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ */ ++ rows?: ?number, ++ + /** + * If `true`, the text input obscures the text entered so that sensitive text + * like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'. +diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js +index 2127191..542fc06 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js +@@ -390,7 +390,6 @@ type AndroidProps = $ReadOnly<{| + /** + * Sets the number of lines for a `TextInput`. Use it with multiline set to + * `true` to be able to fill the lines. +- * @platform android + */ + numberOfLines?: ?number, + +@@ -403,10 +402,14 @@ type AndroidProps = $ReadOnly<{| + /** + * Sets the number of rows for a `TextInput`. Use it with multiline set to + * `true` to be able to fill the lines. +- * @platform android + */ + rows?: ?number, + ++ /** ++ * Sets the maximum number of lines the TextInput can have. ++ */ ++ maxNumberOfLines?: ?number, ++ + /** + * When `false`, it will prevent the soft keyboard from showing when the field is focused. + * Defaults to `true`. +@@ -1069,6 +1072,9 @@ function InternalTextInput(props: Props): React.Node { + accessibilityState, + id, + tabIndex, ++ rows, ++ numberOfLines, ++ maxNumberOfLines, + selection: propsSelection, + ...otherProps + } = props; +@@ -1427,6 +1433,8 @@ function InternalTextInput(props: Props): React.Node { + focusable={tabIndex !== undefined ? !tabIndex : focusable} + mostRecentEventCount={mostRecentEventCount} + nativeID={id ?? props.nativeID} ++ numberOfLines={props.rows ?? props.numberOfLines} ++ maximumNumberOfLines={maxNumberOfLines} + onBlur={_onBlur} + onKeyPressSync={props.unstable_onKeyPressSync} + onChange={_onChange} +@@ -1482,6 +1490,7 @@ function InternalTextInput(props: Props): React.Node { + mostRecentEventCount={mostRecentEventCount} + nativeID={id ?? props.nativeID} + numberOfLines={props.rows ?? props.numberOfLines} ++ maximumNumberOfLines={maxNumberOfLines} + onBlur={_onBlur} + onChange={_onChange} + onFocus={_onFocus} +diff --git a/node_modules/react-native/Libraries/Text/Text.js b/node_modules/react-native/Libraries/Text/Text.js +index df548af..e02f5da 100644 +--- a/node_modules/react-native/Libraries/Text/Text.js ++++ b/node_modules/react-native/Libraries/Text/Text.js +@@ -18,7 +18,11 @@ import processColor from '../StyleSheet/processColor'; + import {getAccessibilityRoleFromRole} from '../Utilities/AcessibilityMapping'; + import Platform from '../Utilities/Platform'; + import TextAncestor from './TextAncestor'; +-import {NativeText, NativeVirtualText} from './TextNativeComponent'; ++import { ++ CONTAINS_MAX_NUMBER_OF_LINES_RENAME, ++ NativeText, ++ NativeVirtualText, ++} from './TextNativeComponent'; + import * as React from 'react'; + import {useContext, useMemo, useState} from 'react'; + +@@ -59,6 +63,7 @@ const Text: React.AbstractComponent< + pressRetentionOffset, + role, + suppressHighlighting, ++ numberOfLines, + ...restProps + } = props; + +@@ -192,14 +197,33 @@ const Text: React.AbstractComponent< + } + } + +- let numberOfLines = restProps.numberOfLines; ++ let numberOfLinesValue = numberOfLines; + if (numberOfLines != null && !(numberOfLines >= 0)) { + console.error( + `'numberOfLines' in must be a non-negative number, received: ${numberOfLines}. The value will be set to 0.`, + ); +- numberOfLines = 0; ++ numberOfLinesValue = 0; + } + ++ const numberOfLinesProps = useMemo((): { ++ maximumNumberOfLines?: ?number, ++ numberOfLines?: ?number, ++ } => { ++ // FIXME: Current logic is breaking all Text components. ++ // if (CONTAINS_MAX_NUMBER_OF_LINES_RENAME) { ++ // return { ++ // maximumNumberOfLines: numberOfLinesValue, ++ // }; ++ // } else { ++ // return { ++ // numberOfLines: numberOfLinesValue, ++ // }; ++ // } ++ return { ++ maximumNumberOfLines: numberOfLinesValue, ++ }; ++ }, [numberOfLinesValue]); ++ + const hasTextAncestor = useContext(TextAncestor); + + const _accessible = Platform.select({ +@@ -241,7 +265,6 @@ const Text: React.AbstractComponent< + isHighlighted={isHighlighted} + isPressable={isPressable} + nativeID={id ?? nativeID} +- numberOfLines={numberOfLines} + ref={forwardedRef} + selectable={_selectable} + selectionColor={selectionColor} +@@ -252,6 +275,7 @@ const Text: React.AbstractComponent< + + #import ++#import ++#import + + @implementation RCTMultilineTextInputViewManager + +@@ -17,8 +19,21 @@ - (UIView *)view + return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge]; + } + ++- (RCTShadowView *)shadowView ++{ ++ RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; ++ ++ shadowView.maximumNumberOfLines = 0; ++ shadowView.exactNumberOfLines = 0; ++ ++ return shadowView; ++} ++ + #pragma mark - Multiline (aka TextView) specific properties + + RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) + ++RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) ++RCT_REMAP_SHADOW_PROPERTY(numberOfLines, exactNumberOfLines, NSInteger) ++ + @end +diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h +index 8f4cf7e..6238ebc 100644 +--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h ++++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h +@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN + @property (nonatomic, copy, nullable) NSString *text; + @property (nonatomic, copy, nullable) NSString *placeholder; + @property (nonatomic, assign) NSInteger maximumNumberOfLines; ++@property (nonatomic, assign) NSInteger exactNumberOfLines; + @property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange; + + - (void)uiManagerWillPerformMounting; +diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m +index 04d2446..9d77743 100644 +--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m ++++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m +@@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText + + - (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize + { +- NSAttributedString *attributedText = [self measurableAttributedText]; ++ NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy]; ++ ++ /* ++ * The block below is responsible for setting the exact height of the view in lines ++ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines ++ * prop and then add random lines at the front. However, they are only used for layout ++ * so they are not visible on the screen. ++ */ ++ if (self.exactNumberOfLines) { ++ NSMutableString *newLines = [NSMutableString stringWithCapacity:self.exactNumberOfLines]; ++ for (NSUInteger i = 0UL; i < self.exactNumberOfLines; ++i) { ++ [newLines appendString:@"\n"]; ++ } ++ [attributedText insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:self.textAttributes.effectiveTextAttributes] atIndex:0]; ++ _maximumNumberOfLines = self.exactNumberOfLines; ++ } + + if (!_textStorage) { + _textContainer = [NSTextContainer new]; +diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m +index 413ac42..56d039c 100644 +--- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m ++++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m +@@ -19,6 +19,7 @@ - (RCTShadowView *)shadowView + RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; + + shadowView.maximumNumberOfLines = 1; ++ shadowView.exactNumberOfLines = 0; + + return shadowView; + } +diff --git a/node_modules/react-native/Libraries/Text/TextNativeComponent.js b/node_modules/react-native/Libraries/Text/TextNativeComponent.js +index 0d59904..3216e43 100644 +--- a/node_modules/react-native/Libraries/Text/TextNativeComponent.js ++++ b/node_modules/react-native/Libraries/Text/TextNativeComponent.js +@@ -9,6 +9,7 @@ + */ + + import {createViewConfig} from '../NativeComponent/ViewConfig'; ++import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes'; + import UIManager from '../ReactNative/UIManager'; + import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass'; + import {type HostComponent} from '../Renderer/shims/ReactNativeTypes'; +@@ -18,6 +19,7 @@ import {type TextProps} from './TextProps'; + + type NativeTextProps = $ReadOnly<{ + ...TextProps, ++ maximumNumberOfLines?: ?number, + isHighlighted?: ?boolean, + selectionColor?: ?ProcessedColorValue, + onClick?: ?(event: PressEvent) => mixed, +@@ -31,7 +33,7 @@ const textViewConfig = { + validAttributes: { + isHighlighted: true, + isPressable: true, +- numberOfLines: true, ++ maximumNumberOfLines: true, + ellipsizeMode: true, + allowFontScaling: true, + dynamicTypeRamp: true, +@@ -73,6 +75,12 @@ export const NativeText: HostComponent = + createViewConfig(textViewConfig), + ): any); + ++const jestIsDefined = typeof jest !== 'undefined'; ++export const CONTAINS_MAX_NUMBER_OF_LINES_RENAME: boolean = jestIsDefined ++ ? true ++ : getNativeComponentAttributes('RCTText')?.NativeProps ++ ?.maximumNumberOfLines === 'number'; ++ + export const NativeVirtualText: HostComponent = + !global.RN$Bridgeless && !UIManager.hasViewManagerConfig('RCTVirtualText') + ? NativeText +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java +index 8cab407..ad5fa96 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java +@@ -12,5 +12,6 @@ public class ViewDefaults { + + public static final float FONT_SIZE_SP = 14.0f; + public static final int LINE_HEIGHT = 0; +- public static final int NUMBER_OF_LINES = Integer.MAX_VALUE; ++ public static final int NUMBER_OF_LINES = -1; ++ public static final int MAXIMUM_NUMBER_OF_LINES = Integer.MAX_VALUE; + } +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +index 3f76fa7..7a5d096 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +@@ -96,6 +96,7 @@ public class ViewProps { + public static final String LETTER_SPACING = "letterSpacing"; + public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing"; + public static final String NUMBER_OF_LINES = "numberOfLines"; ++ public static final String MAXIMUM_NUMBER_OF_LINES = "maximumNumberOfLines"; + public static final String ELLIPSIZE_MODE = "ellipsizeMode"; + public static final String ADJUSTS_FONT_SIZE_TO_FIT = "adjustsFontSizeToFit"; + public static final String MINIMUM_FONT_SCALE = "minimumFontScale"; +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +index b5811c7..96eef96 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +@@ -303,6 +303,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { + protected boolean mIsAccessibilityLink = false; + + protected int mNumberOfLines = UNSET; ++ protected int mMaxNumberOfLines = UNSET; + protected int mTextAlign = Gravity.NO_GRAVITY; + protected int mTextBreakStrategy = + (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; +@@ -387,6 +388,12 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { + markUpdated(); + } + ++ @ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = UNSET) ++ public void setMaxNumberOfLines(int numberOfLines) { ++ mMaxNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines; ++ markUpdated(); ++ } ++ + @ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN) + public void setLineHeight(float lineHeight) { + mTextAttributes.setLineHeight(lineHeight); +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java +index 7b5d0c1..c3032eb 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java +@@ -49,8 +49,8 @@ public abstract class ReactTextAnchorViewManager minimumFontSize +- && (mNumberOfLines != UNSET && layout.getLineCount() > mNumberOfLines ++ && (mMaxNumberOfLines != UNSET && layout.getLineCount() > mMaxNumberOfLines + || heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) { + // TODO: We could probably use a smarter algorithm here. This will require 0(n) + // measurements +@@ -124,9 +124,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode { + } + + final int lineCount = +- mNumberOfLines == UNSET ++ mMaxNumberOfLines == UNSET + ? layout.getLineCount() +- : Math.min(mNumberOfLines, layout.getLineCount()); ++ : Math.min(mMaxNumberOfLines, layout.getLineCount()); + + // Instead of using `layout.getWidth()` (which may yield a significantly larger width for + // text that is wrapping), compute width using the longest line. +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +index 190bc27..c2bcdc1 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +@@ -87,7 +87,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie + + mReactBackgroundManager = new ReactViewBackgroundManager(this); + +- mNumberOfLines = ViewDefaults.NUMBER_OF_LINES; ++ mNumberOfLines = ViewDefaults.MAXIMUM_NUMBER_OF_LINES; + mAdjustsFontSizeToFit = false; + mLinkifyMaskType = 0; + mNotifyOnInlineViewLayout = false; +@@ -576,7 +576,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie + } + + public void setNumberOfLines(int numberOfLines) { +- mNumberOfLines = numberOfLines == 0 ? ViewDefaults.NUMBER_OF_LINES : numberOfLines; ++ mNumberOfLines = numberOfLines == 0 ? ViewDefaults.MAXIMUM_NUMBER_OF_LINES : numberOfLines; + setSingleLine(mNumberOfLines == 1); + setMaxLines(mNumberOfLines); + } +@@ -596,7 +596,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie + public void updateView() { + @Nullable + TextUtils.TruncateAt ellipsizeLocation = +- mNumberOfLines == ViewDefaults.NUMBER_OF_LINES || mAdjustsFontSizeToFit ++ mNumberOfLines == ViewDefaults.MAXIMUM_NUMBER_OF_LINES || mAdjustsFontSizeToFit + ? null + : mEllipsizeLocation; + setEllipsize(ellipsizeLocation); +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +index 561a2d0..9409cfc 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; + import android.text.Spanned; + import android.text.StaticLayout; + import android.text.TextPaint; ++import android.text.TextUtils; + import android.util.LayoutDirection; + import android.util.LruCache; + import android.view.View; +@@ -65,6 +66,7 @@ public class TextLayoutManager { + private static final String TEXT_BREAK_STRATEGY_KEY = "textBreakStrategy"; + private static final String HYPHENATION_FREQUENCY_KEY = "android_hyphenationFrequency"; + private static final String MAXIMUM_NUMBER_OF_LINES_KEY = "maximumNumberOfLines"; ++ private static final String NUMBER_OF_LINES_KEY = "numberOfLines"; + private static final LruCache sSpannableCache = + new LruCache<>(spannableCacheSize); + private static final ConcurrentHashMap sTagToSpannableCache = +@@ -385,6 +387,48 @@ public class TextLayoutManager { + ? paragraphAttributes.getInt(MAXIMUM_NUMBER_OF_LINES_KEY) + : UNSET; + ++ int numberOfLines = ++ paragraphAttributes.hasKey(NUMBER_OF_LINES_KEY) ++ ? paragraphAttributes.getInt(NUMBER_OF_LINES_KEY) ++ : UNSET; ++ ++ int lines = layout.getLineCount(); ++ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines >= lines && text.length() > 0) { ++ int numberOfEmptyLines = numberOfLines - lines; ++ SpannableStringBuilder ssb = new SpannableStringBuilder(); ++ ++ // for some reason a newline on end causes issues with computing height so we add a character ++ if (text.toString().endsWith("\n")) { ++ ssb.append("A"); ++ } ++ ++ for (int i = 0; i < numberOfEmptyLines; ++i) { ++ ssb.append("\nA"); ++ } ++ ++ Object[] spans = text.getSpans(0, 0, Object.class); ++ for (Object span : spans) { // It's possible we need to set exl-exl ++ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); ++ }; ++ ++ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); ++ boring = null; ++ layout = createLayout( ++ text, ++ boring, ++ width, ++ widthYogaMeasureMode, ++ includeFontPadding, ++ textBreakStrategy, ++ hyphenationFrequency); ++ } ++ ++ ++ if (numberOfLines != UNSET && numberOfLines != 0) { ++ maximumNumberOfLines = numberOfLines; ++ } ++ ++ + int calculatedLineCount = + maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 + ? layout.getLineCount() +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +index 0d118f0..0ae44b7 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; + import android.text.Spanned; + import android.text.StaticLayout; + import android.text.TextPaint; ++import android.text.TextUtils; + import android.util.LayoutDirection; + import android.util.LruCache; + import android.view.View; +@@ -61,6 +62,7 @@ public class TextLayoutManagerMapBuffer { + public static final short PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; + public static final short PA_KEY_INCLUDE_FONT_PADDING = 4; + public static final short PA_KEY_HYPHENATION_FREQUENCY = 5; ++ public static final short PA_KEY_NUMBER_OF_LINES = 6; + + private static final boolean ENABLE_MEASURE_LOGGING = ReactBuildConfig.DEBUG && false; + +@@ -399,6 +401,47 @@ public class TextLayoutManagerMapBuffer { + ? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES) + : UNSET; + ++ int numberOfLines = ++ paragraphAttributes.contains(PA_KEY_NUMBER_OF_LINES) ++ ? paragraphAttributes.getInt(PA_KEY_NUMBER_OF_LINES) ++ : UNSET; ++ ++ int lines = layout.getLineCount(); ++ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines > lines && text.length() > 0) { ++ int numberOfEmptyLines = numberOfLines - lines; ++ SpannableStringBuilder ssb = new SpannableStringBuilder(); ++ ++ // for some reason a newline on end causes issues with computing height so we add a character ++ if (text.toString().endsWith("\n")) { ++ ssb.append("A"); ++ } ++ ++ for (int i = 0; i < numberOfEmptyLines; ++i) { ++ ssb.append("\nA"); ++ } ++ ++ Object[] spans = text.getSpans(0, 0, Object.class); ++ for (Object span : spans) { // It's possible we need to set exl-exl ++ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); ++ }; ++ ++ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); ++ boring = null; ++ layout = createLayout( ++ text, ++ boring, ++ width, ++ widthYogaMeasureMode, ++ includeFontPadding, ++ textBreakStrategy, ++ hyphenationFrequency); ++ } ++ ++ if (numberOfLines != UNSET && numberOfLines != 0) { ++ maximumNumberOfLines = numberOfLines; ++ } ++ ++ + int calculatedLineCount = + maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 + ? layout.getLineCount() +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +index ced37be..ef2f321 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +@@ -548,7 +548,13 @@ public class ReactEditText extends AppCompatEditText + * href='https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/widget/TextView.java'>TextView.java} + */ + if (isMultiline()) { ++ // we save max lines as setSingleLines overwrites it ++ // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/TextView.java#10671 ++ int maxLines = getMaxLines(); + setSingleLine(false); ++ if (maxLines != -1) { ++ setMaxLines(maxLines); ++ } + } + + // We override the KeyListener so that all keys on the soft input keyboard as well as hardware +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java +index a850510..c59be1d 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java +@@ -41,9 +41,9 @@ public final class ReactTextInputLocalData { + public void apply(EditText editText) { + editText.setText(mText); + editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); ++ editText.setInputType(mInputType); + editText.setMinLines(mMinLines); + editText.setMaxLines(mMaxLines); +- editText.setInputType(mInputType); + editText.setHint(mPlaceholder); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + editText.setBreakStrategy(mBreakStrategy); +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +index b27ace4..c6a2d63 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +@@ -737,9 +737,18 @@ public class ReactTextInputManager extends BaseViewManager= Build.VERSION_CODES.M +diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp +index 2994aca..fff0d5e 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp ++++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp +@@ -16,6 +16,7 @@ namespace facebook::react { + + bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { + return std::tie( ++ numberOfLines, + maximumNumberOfLines, + ellipsizeMode, + textBreakStrategy, +@@ -23,6 +24,7 @@ bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { + includeFontPadding, + android_hyphenationFrequency) == + std::tie( ++ rhs.numberOfLines, + rhs.maximumNumberOfLines, + rhs.ellipsizeMode, + rhs.textBreakStrategy, +@@ -42,6 +44,7 @@ bool ParagraphAttributes::operator!=(const ParagraphAttributes &rhs) const { + #if RN_DEBUG_STRING_CONVERTIBLE + SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const { + return { ++ debugStringConvertibleItem("numberOfLines", numberOfLines), + debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines), + debugStringConvertibleItem("ellipsizeMode", ellipsizeMode), + debugStringConvertibleItem("textBreakStrategy", textBreakStrategy), +diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h +index f5f87c6..b7d1e90 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h +@@ -30,6 +30,11 @@ class ParagraphAttributes : public DebugStringConvertible { + public: + #pragma mark - Fields + ++ /* ++ * Number of lines which paragraph takes. ++ */ ++ int numberOfLines{}; ++ + /* + * Maximum number of lines which paragraph can take. + * Zero value represents "no limit". +@@ -92,6 +97,7 @@ struct hash { + const facebook::react::ParagraphAttributes &attributes) const { + return folly::hash::hash_combine( + 0, ++ attributes.numberOfLines, + attributes.maximumNumberOfLines, + attributes.ellipsizeMode, + attributes.textBreakStrategy, +diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +index 8687b89..eab75f4 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +@@ -835,10 +835,16 @@ inline ParagraphAttributes convertRawProp( + ParagraphAttributes const &defaultParagraphAttributes) { + auto paragraphAttributes = ParagraphAttributes{}; + +- paragraphAttributes.maximumNumberOfLines = convertRawProp( ++ paragraphAttributes.numberOfLines = convertRawProp( + context, + rawProps, + "numberOfLines", ++ sourceParagraphAttributes.numberOfLines, ++ defaultParagraphAttributes.numberOfLines); ++ paragraphAttributes.maximumNumberOfLines = convertRawProp( ++ context, ++ rawProps, ++ "maximumNumberOfLines", + sourceParagraphAttributes.maximumNumberOfLines, + defaultParagraphAttributes.maximumNumberOfLines); + paragraphAttributes.ellipsizeMode = convertRawProp( +@@ -913,6 +919,7 @@ inline std::string toString(AttributedString::Range const &range) { + inline folly::dynamic toDynamic( + const ParagraphAttributes ¶graphAttributes) { + auto values = folly::dynamic::object(); ++ values("numberOfLines", paragraphAttributes.numberOfLines); + values("maximumNumberOfLines", paragraphAttributes.maximumNumberOfLines); + values("ellipsizeMode", toString(paragraphAttributes.ellipsizeMode)); + values("textBreakStrategy", toString(paragraphAttributes.textBreakStrategy)); +@@ -1118,6 +1125,7 @@ constexpr static MapBuffer::Key PA_KEY_TEXT_BREAK_STRATEGY = 2; + constexpr static MapBuffer::Key PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; + constexpr static MapBuffer::Key PA_KEY_INCLUDE_FONT_PADDING = 4; + constexpr static MapBuffer::Key PA_KEY_HYPHENATION_FREQUENCY = 5; ++constexpr static MapBuffer::Key PA_KEY_NUMBER_OF_LINES = 6; + + inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { + auto builder = MapBufferBuilder(); +@@ -1135,6 +1143,8 @@ inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { + builder.putString( + PA_KEY_HYPHENATION_FREQUENCY, + toString(paragraphAttributes.android_hyphenationFrequency)); ++ builder.putInt( ++ PA_KEY_NUMBER_OF_LINES, paragraphAttributes.numberOfLines); + + return builder.build(); + } +diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp +index 9953e22..98eb3da 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp ++++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp +@@ -56,6 +56,10 @@ AndroidTextInputProps::AndroidTextInputProps( + "numberOfLines", + sourceProps.numberOfLines, + {0})), ++ maximumNumberOfLines(CoreFeatures::enablePropIteratorSetter? sourceProps.maximumNumberOfLines : convertRawProp(context, rawProps, ++ "maximumNumberOfLines", ++ sourceProps.maximumNumberOfLines, ++ {0})), + disableFullscreenUI(CoreFeatures::enablePropIteratorSetter? sourceProps.disableFullscreenUI : convertRawProp(context, rawProps, + "disableFullscreenUI", + sourceProps.disableFullscreenUI, +@@ -281,6 +285,12 @@ void AndroidTextInputProps::setProp( + value, + paragraphAttributes, + maximumNumberOfLines, ++ "maximumNumberOfLines"); ++ REBUILD_FIELD_SWITCH_CASE( ++ paDefaults, ++ value, ++ paragraphAttributes, ++ numberOfLines, + "numberOfLines"); + REBUILD_FIELD_SWITCH_CASE( + paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode"); +@@ -323,6 +333,7 @@ void AndroidTextInputProps::setProp( + } + + switch (hash) { ++ RAW_SET_PROP_SWITCH_CASE_BASIC(maximumNumberOfLines); + RAW_SET_PROP_SWITCH_CASE_BASIC(autoComplete); + RAW_SET_PROP_SWITCH_CASE_BASIC(returnKeyLabel); + RAW_SET_PROP_SWITCH_CASE_BASIC(numberOfLines); +@@ -422,6 +433,7 @@ void AndroidTextInputProps::setProp( + // TODO T53300085: support this in codegen; this was hand-written + folly::dynamic AndroidTextInputProps::getDynamic() const { + folly::dynamic props = folly::dynamic::object(); ++ props["maximumNumberOfLines"] = maximumNumberOfLines; + props["autoComplete"] = autoComplete; + props["returnKeyLabel"] = returnKeyLabel; + props["numberOfLines"] = numberOfLines; +diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h +index ba39ebb..ead28e3 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h +@@ -84,6 +84,7 @@ class AndroidTextInputProps final : public ViewProps, public BaseTextProps { + std::string autoComplete{}; + std::string returnKeyLabel{}; + int numberOfLines{0}; ++ int maximumNumberOfLines{0}; + bool disableFullscreenUI{false}; + std::string textBreakStrategy{}; + SharedColor underlineColorAndroid{}; +diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +index 368c334..a1bb33e 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm ++++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +@@ -244,26 +244,51 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString + + #pragma mark - Private + +-- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString +++- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)inputAttributedString + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + size:(CGSize)size + { +- NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size]; ++ NSMutableAttributedString *attributedString = [ inputAttributedString mutableCopy]; ++ ++ /* ++ * The block below is responsible for setting the exact height of the view in lines ++ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines ++ * prop and then add random lines at the front. However, they are only used for layout ++ * so they are not visible on the screen. This method is used for drawing only for Paragraph component ++ * but we set exact height in lines only on TextInput that doesn't use it. ++ */ ++ if (paragraphAttributes.numberOfLines) { ++ paragraphAttributes.maximumNumberOfLines = paragraphAttributes.numberOfLines; ++ NSMutableString *newLines = [NSMutableString stringWithCapacity: paragraphAttributes.numberOfLines]; ++ for (NSUInteger i = 0UL; i < paragraphAttributes.numberOfLines; ++i) { ++ // K is added on purpose. New line seems to be not enough for NTtextContainer ++ [newLines appendString:@"K\n"]; ++ } ++ NSDictionary * attributesOfFirstCharacter = [inputAttributedString attributesAtIndex:0 effectiveRange:NULL]; + +- textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. +- textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 +- ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) +- : NSLineBreakByClipping; +- textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; ++ [attributedString insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:attributesOfFirstCharacter] atIndex:0]; ++ } ++ ++ NSTextContainer *textContainer = [NSTextContainer new]; + + NSLayoutManager *layoutManager = [NSLayoutManager new]; + layoutManager.usesFontLeading = NO; + [layoutManager addTextContainer:textContainer]; + +- NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; ++ NSTextStorage *textStorage = [NSTextStorage new]; + + [textStorage addLayoutManager:layoutManager]; + ++ textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. ++ textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 ++ ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) ++ : NSLineBreakByClipping; ++ textContainer.size = size; ++ textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; ++ ++ [textStorage replaceCharactersInRange:(NSRange){0, textStorage.length} withAttributedString:attributedString]; ++ ++ + if (paragraphAttributes.adjustsFontSizeToFit) { + CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0; + CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0; diff --git a/patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch b/patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch new file mode 100644 index 000000000000..84a233894f94 --- /dev/null +++ b/patches/react-native+0.72.4+004+ModalKeyboardFlashing.patch @@ -0,0 +1,18 @@ +diff --git a/node_modules/react-native/React/Views/RCTModalHostViewManager.m b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +index 4b9f9ad..b72984c 100644 +--- a/node_modules/react-native/React/Views/RCTModalHostViewManager.m ++++ b/node_modules/react-native/React/Views/RCTModalHostViewManager.m +@@ -79,6 +79,13 @@ RCT_EXPORT_MODULE() + if (self->_presentationBlock) { + self->_presentationBlock([modalHostView reactViewController], viewController, animated, completionBlock); + } else { ++ // In our App, If an input is blurred and a modal is opened, the rootView will become the firstResponder, which ++ // will cause system to retain a wrong keyboard state, and then the keyboard to flicker when the modal is closed. ++ // We first resign the rootView to avoid this problem. ++ UIWindow *window = RCTKeyWindow(); ++ if (window && window.rootViewController && [window.rootViewController.view isFirstResponder]) { ++ [window.rootViewController.view resignFirstResponder]; ++ } + [[modalHostView reactViewController] presentViewController:viewController + animated:animated + completion:completionBlock]; diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index 7b72e17ae5fe..278965cbc81a 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; -import {getComposerMaxHeightStyle} from '@styles/StyleUtils'; import themeColors from '@styles/themes/default'; const propTypes = { @@ -92,19 +92,37 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - const maxHeightStyle = useMemo(() => getComposerMaxHeightStyle(maxLines, isComposerFullSize), [isComposerFullSize, maxLines]); + /** + * Set maximum number of lines + * @return {Number} + */ + const maxNumberOfLines = useMemo(() => { + if (isComposerFullSize) { + return 1000000; + } + return maxLines; + }, [isComposerFullSize, maxLines]); + + const composerStyles = useMemo(() => { + StyleSheet.flatten(props.style); + }, [props.style]); return ( ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} + // Setting a really high number here fixes an issue with the `maxNumberOfLines` prop on TextInput, where on Android the text input would collapse to only one line, + // when it should actually expand to the container (https://github.com/Expensify/App/issues/11694#issuecomment-1560520670) + // @Szymon20000 is working on fixing this (android-only) issue in the in the upstream PR (https://github.com/facebook/react-native/pulls?q=is%3Apr+is%3Aopen+maxNumberOfLines) + // TODO: remove this comment once upstream PR is merged and available in a future release + maxNumberOfLines={maxNumberOfLines} textAlignVertical="center" - style={[...props.style, maxHeightStyle]} + style={[composerStyles]} + /* eslint-disable-next-line react/jsx-props-no-spreading */ + {...props} readOnly={isDisabled} /> ); diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index efbda8cae2ad..a1b8c1a4ffe6 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -1,9 +1,10 @@ import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; import * as ComposerUtils from '@libs/ComposerUtils'; -import {getComposerMaxHeightStyle} from '@styles/StyleUtils'; +import styles from '@styles/styles'; import themeColors from '@styles/themes/default'; const propTypes = { @@ -92,7 +93,20 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC onClear(); }, [shouldClear, onClear]); - const maxHeightStyle = useMemo(() => getComposerMaxHeightStyle(maxLines, isComposerFullSize), [isComposerFullSize, maxLines]); + /** + * Set maximum number of lines + * @return {Number} + */ + const maxNumberOfLines = useMemo(() => { + if (isComposerFullSize) { + return; + } + return maxLines; + }, [isComposerFullSize, maxLines]); + + const composerStyles = useMemo(() => { + StyleSheet.flatten(props.style); + }, [props.style]); // On native layers we like to have the Text Input not focused so the // user can read new chats without the keyboard in the way of the view. @@ -100,15 +114,16 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC const propsToPass = _.omit(props, 'selection'); return ( ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e)} rejectResponderTermination={false} smartInsertDelete={false} - style={[...props.style, maxHeightStyle]} + maxNumberOfLines={maxNumberOfLines} + style={[composerStyles, styles.verticalAlignMiddle]} + /* eslint-disable-next-line react/jsx-props-no-spreading */ + {...propsToPass} readOnly={isDisabled} /> ); diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 1037e12f2a31..4b998f940244 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -1390,19 +1390,6 @@ function getDotIndicatorTextStyles(isErrorText = true): TextStyle { return isErrorText ? {...styles.offlineFeedback.text, color: styles.formError.color} : {...styles.offlineFeedback.text}; } -/** - * Returns container styles for showing the icons in MultipleAvatars/SubscriptAvatar - */ -function getComposerMaxHeightStyle(maxLines: number, isComposerFullSize: boolean): ViewStyle | undefined { - const composerLineHeight = styles.textInputCompose.lineHeight ?? 0; - - return isComposerFullSize - ? undefined - : { - maxHeight: maxLines * composerLineHeight, - }; -} - export { combineStyles, displayIfTrue, @@ -1487,5 +1474,4 @@ export { getContainerStyles, getEReceiptColorStyles, getEReceiptColorCode, - getComposerMaxHeightStyle, }; diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 6139391bc8d0..c1b78a224eb3 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -328,6 +328,10 @@ const styles = (theme: ThemeColors) => textAlign: 'left', }, + verticalAlignMiddle: { + verticalAlign: 'middle', + }, + verticalAlignTop: { verticalAlign: 'top', }, From faefaf5261b5f916674288002a1e55b72962e4df Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 22:33:37 +0100 Subject: [PATCH 26/57] fix: minor fixes --- src/CONST.ts | 8 ++++++++ src/components/CustomScrollbarWrapper/index.tsx | 9 ++++++--- src/components/CustomStatusBar/index.tsx | 6 +++++- src/pages/signin/SignInPage.js | 14 +++++++------- src/styles/styles.ts | 4 +++- src/styles/themes/types.ts | 8 ++++---- 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 9b284752d074..d596f9029b44 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -697,6 +697,14 @@ const CONST = { DARK: 'dark', SYSTEM: 'system', }, + STATUS_BAR_AND_SCROLLBAR_THEME: { + LIGHT: 'light', + DARK: 'dark', + }, + STATUS_BAR_THEME: { + LIGHT_CONTENT: 'light-content', + DARK_CONTENT: 'dark-content', + }, TRANSACTION: { DEFAULT_MERCHANT: 'Request', UNKNOWN_MERCHANT: 'Unknown Merchant', diff --git a/src/components/CustomScrollbarWrapper/index.tsx b/src/components/CustomScrollbarWrapper/index.tsx index 9454f5484c57..d8c97ad5b7a6 100644 --- a/src/components/CustomScrollbarWrapper/index.tsx +++ b/src/components/CustomScrollbarWrapper/index.tsx @@ -1,14 +1,17 @@ -import React, {CSSProperties, useMemo} from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import Themes from '@styles/themes/Themes'; import useThemePreferenceWithStaticOverride from '@styles/themes/useThemePreferenceWithStaticOverride'; +import useThemeStyles from '@styles/useThemeStyles'; import CustomScrollbarWrapperProps from './CustomScrollbarWrapperProps'; function CustomScrollbarWrapper({children, theme: staticThemePreference}: CustomScrollbarWrapperProps): React.ReactElement { + const themeStyles = useThemeStyles(); + const preferredTheme = useThemePreferenceWithStaticOverride(staticThemePreference); - const scrollbarTheme = useMemo(() => Themes[preferredTheme].scrollBarTheme, [preferredTheme]); + const scrollbarTheme = useMemo(() => Themes[preferredTheme].scrollBarTheme, [preferredTheme]); - return {children}; + return {children}; } export default CustomScrollbarWrapper; diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index 41e7c125e400..b9ff18a083c6 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -2,6 +2,7 @@ import React, {useContext, useEffect, useMemo} from 'react'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; import useTheme from '@styles/themes/useTheme'; +import CONST from '@src/CONST'; import CustomStatusBarContext from './CustomStatusBarContext'; type CustomStatusBarProps = { @@ -11,7 +12,10 @@ type CustomStatusBarProps = { function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactElement | null { const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarContext); const theme = useTheme(); - const statusBarContentTheme = useMemo(() => (theme.statusBarContentTheme === 'light' ? 'light-content' : 'dark-content'), [theme.statusBarContentTheme]); + const statusBarContentTheme = useMemo( + () => (theme.statusBarContentTheme === CONST.STATUS_BAR_AND_SCROLLBAR_THEME.LIGHT ? CONST.STATUS_BAR_THEME.LIGHT_CONTENT : CONST.STATUS_BAR_THEME.DARK_CONTENT), + [theme.statusBarContentTheme], + ); const isDisabled = !isNested && isRootStatusBarDisabled; diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 6c14304c6c29..2e2ad64f08f4 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -279,17 +279,17 @@ SignInPageInner.displayName = 'SignInPage'; function SignInPage(props) { return ( - - - - + + + + - - - + + + ); } diff --git a/src/styles/styles.ts b/src/styles/styles.ts index c1b78a224eb3..e26d79979cac 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -21,7 +21,7 @@ import pointerEventsAuto from './pointerEventsAuto'; import pointerEventsBoxNone from './pointerEventsBoxNone'; import pointerEventsNone from './pointerEventsNone'; import defaultTheme from './themes/default'; -import {ThemeColors} from './themes/types'; +import {StatusBarAndScrollbarTheme, ThemeColors} from './themes/types'; import borders from './utilities/borders'; import cursor from './utilities/cursor'; import display from './utilities/display'; @@ -3979,6 +3979,8 @@ const styles = (theme: ThemeColors) => singleOptionSelectorCircle: { borderColor: theme.icon, }, + + colorSchemeStyle: (colorScheme: StatusBarAndScrollbarTheme | undefined) => ({colorScheme}), } satisfies Styles); const stylesGenerator = styles; diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index ca7ac00cf054..eecd4acecfab 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -1,6 +1,6 @@ import CONST from '@src/CONST'; -type ContentTheme = 'light' | 'dark'; +type StatusBarAndScrollbarTheme = 'light' | 'dark'; type Color = string; type ThemePreference = (typeof CONST.THEME)[keyof typeof CONST.THEME]; @@ -94,8 +94,8 @@ type ThemeColors = { // Status bar and scroll bars need to adapt their theme based on the active user theme for good contrast // Therefore, we need to define specific themes for these elements // e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme - statusBarContentTheme: ContentTheme; - scrollBarTheme: ContentTheme; + statusBarContentTheme: StatusBarAndScrollbarTheme; + scrollBarTheme: StatusBarAndScrollbarTheme; }; -export {type ThemePreference, type ThemePreferenceWithoutSystem, type ThemeColors, type Color}; +export {type ThemePreference, type ThemePreferenceWithoutSystem, type ThemeColors, type Color, type StatusBarAndScrollbarTheme}; From e335bd03b296a66beba49aa8dd4f12bb931a45b1 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 22:34:31 +0100 Subject: [PATCH 27/57] remove index.html style --- web/index.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/web/index.html b/web/index.html index 15b58e8fe0c5..6e5d0cd3c5d6 100644 --- a/web/index.html +++ b/web/index.html @@ -31,10 +31,7 @@ #root > div > div { height: 100% !important; } - .scrollbar-light { - color-scheme: light !important; - } - .scrollbar-light { + :root { color-scheme: dark !important; } * { From 38bcce7cd593bdd7b78843e26f5da2d592eb4257 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 22:38:02 +0100 Subject: [PATCH 28/57] remove patch --- ...ive+0.72.4+002+ModalKeyboardFlashing.patch | 18 - ...eact-native+0.72.4+002+NumberOfLines.patch | 978 ------------------ 2 files changed, 996 deletions(-) delete mode 100644 patches/react-native+0.72.4+002+ModalKeyboardFlashing.patch delete mode 100644 patches/react-native+0.72.4+002+NumberOfLines.patch diff --git a/patches/react-native+0.72.4+002+ModalKeyboardFlashing.patch b/patches/react-native+0.72.4+002+ModalKeyboardFlashing.patch deleted file mode 100644 index 84a233894f94..000000000000 --- a/patches/react-native+0.72.4+002+ModalKeyboardFlashing.patch +++ /dev/null @@ -1,18 +0,0 @@ -diff --git a/node_modules/react-native/React/Views/RCTModalHostViewManager.m b/node_modules/react-native/React/Views/RCTModalHostViewManager.m -index 4b9f9ad..b72984c 100644 ---- a/node_modules/react-native/React/Views/RCTModalHostViewManager.m -+++ b/node_modules/react-native/React/Views/RCTModalHostViewManager.m -@@ -79,6 +79,13 @@ RCT_EXPORT_MODULE() - if (self->_presentationBlock) { - self->_presentationBlock([modalHostView reactViewController], viewController, animated, completionBlock); - } else { -+ // In our App, If an input is blurred and a modal is opened, the rootView will become the firstResponder, which -+ // will cause system to retain a wrong keyboard state, and then the keyboard to flicker when the modal is closed. -+ // We first resign the rootView to avoid this problem. -+ UIWindow *window = RCTKeyWindow(); -+ if (window && window.rootViewController && [window.rootViewController.view isFirstResponder]) { -+ [window.rootViewController.view resignFirstResponder]; -+ } - [[modalHostView reactViewController] presentViewController:viewController - animated:animated - completion:completionBlock]; diff --git a/patches/react-native+0.72.4+002+NumberOfLines.patch b/patches/react-native+0.72.4+002+NumberOfLines.patch deleted file mode 100644 index 75422f84708e..000000000000 --- a/patches/react-native+0.72.4+002+NumberOfLines.patch +++ /dev/null @@ -1,978 +0,0 @@ -diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -index 55b770d..4073836 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js -@@ -179,6 +179,13 @@ export type NativeProps = $ReadOnly<{| - */ - numberOfLines?: ?Int32, - -+ /** -+ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ * @platform android -+ */ -+ maximumNumberOfLines?: ?Int32, -+ - /** - * When `false`, if there is a small amount of space available around a text input - * (e.g. landscape orientation on a phone), the OS may choose to have the user edit -diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -index 6f69329..d531bee 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js -@@ -144,6 +144,8 @@ const RCTTextInputViewConfig = { - placeholder: true, - autoCorrect: true, - multiline: true, -+ numberOfLines: true, -+ maximumNumberOfLines: true, - textContentType: true, - maxLength: true, - autoCapitalize: true, -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -index 8badb2a..b19f197 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts -@@ -347,12 +347,6 @@ export interface TextInputAndroidProps { - */ - inlineImagePadding?: number | undefined; - -- /** -- * Sets the number of lines for a TextInput. -- * Use it with multiline set to true to be able to fill the lines. -- */ -- numberOfLines?: number | undefined; -- - /** - * Sets the return key to the label. Use it instead of `returnKeyType`. - * @platform android -@@ -663,11 +657,30 @@ export interface TextInputProps - */ - maxLength?: number | undefined; - -+ /** -+ * Sets the maximum number of lines for a TextInput. -+ * Use it with multiline set to true to be able to fill the lines. -+ */ -+ maxNumberOfLines?: number | undefined; -+ - /** - * If true, the text input can be multiple lines. The default value is false. - */ - multiline?: boolean | undefined; - -+ /** -+ * Sets the number of lines for a TextInput. -+ * Use it with multiline set to true to be able to fill the lines. -+ */ -+ numberOfLines?: number | undefined; -+ -+ /** -+ * Sets the number of rows for a TextInput. -+ * Use it with multiline set to true to be able to fill the lines. -+ */ -+ rows?: number | undefined; -+ -+ - /** - * Callback that is called when the text input is blurred - */ -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -index 7ed4579..b1d994e 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js -@@ -343,26 +343,12 @@ type AndroidProps = $ReadOnly<{| - */ - inlineImagePadding?: ?number, - -- /** -- * Sets the number of lines for a `TextInput`. Use it with multiline set to -- * `true` to be able to fill the lines. -- * @platform android -- */ -- numberOfLines?: ?number, -- - /** - * Sets the return key to the label. Use it instead of `returnKeyType`. - * @platform android - */ - returnKeyLabel?: ?string, - -- /** -- * Sets the number of rows for a `TextInput`. Use it with multiline set to -- * `true` to be able to fill the lines. -- * @platform android -- */ -- rows?: ?number, -- - /** - * When `false`, it will prevent the soft keyboard from showing when the field is focused. - * Defaults to `true`. -@@ -632,6 +618,12 @@ export type Props = $ReadOnly<{| - */ - keyboardType?: ?KeyboardType, - -+ /** -+ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ */ -+ maxNumberOfLines?: ?number, -+ - /** - * Specifies largest possible scale a font can reach when `allowFontScaling` is enabled. - * Possible values: -@@ -653,6 +645,12 @@ export type Props = $ReadOnly<{| - */ - multiline?: ?boolean, - -+ /** -+ * Sets the number of lines for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ */ -+ numberOfLines?: ?number, -+ - /** - * Callback that is called when the text input is blurred. - */ -@@ -814,6 +812,12 @@ export type Props = $ReadOnly<{| - */ - returnKeyType?: ?ReturnKeyType, - -+ /** -+ * Sets the number of rows for a `TextInput`. Use it with multiline set to -+ * `true` to be able to fill the lines. -+ */ -+ rows?: ?number, -+ - /** - * If `true`, the text input obscures the text entered so that sensitive text - * like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'. -diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -index 2127191..542fc06 100644 ---- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -+++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js -@@ -390,7 +390,6 @@ type AndroidProps = $ReadOnly<{| - /** - * Sets the number of lines for a `TextInput`. Use it with multiline set to - * `true` to be able to fill the lines. -- * @platform android - */ - numberOfLines?: ?number, - -@@ -403,10 +402,14 @@ type AndroidProps = $ReadOnly<{| - /** - * Sets the number of rows for a `TextInput`. Use it with multiline set to - * `true` to be able to fill the lines. -- * @platform android - */ - rows?: ?number, - -+ /** -+ * Sets the maximum number of lines the TextInput can have. -+ */ -+ maxNumberOfLines?: ?number, -+ - /** - * When `false`, it will prevent the soft keyboard from showing when the field is focused. - * Defaults to `true`. -@@ -1069,6 +1072,9 @@ function InternalTextInput(props: Props): React.Node { - accessibilityState, - id, - tabIndex, -+ rows, -+ numberOfLines, -+ maxNumberOfLines, - selection: propsSelection, - ...otherProps - } = props; -@@ -1427,6 +1433,8 @@ function InternalTextInput(props: Props): React.Node { - focusable={tabIndex !== undefined ? !tabIndex : focusable} - mostRecentEventCount={mostRecentEventCount} - nativeID={id ?? props.nativeID} -+ numberOfLines={props.rows ?? props.numberOfLines} -+ maximumNumberOfLines={maxNumberOfLines} - onBlur={_onBlur} - onKeyPressSync={props.unstable_onKeyPressSync} - onChange={_onChange} -@@ -1482,6 +1490,7 @@ function InternalTextInput(props: Props): React.Node { - mostRecentEventCount={mostRecentEventCount} - nativeID={id ?? props.nativeID} - numberOfLines={props.rows ?? props.numberOfLines} -+ maximumNumberOfLines={maxNumberOfLines} - onBlur={_onBlur} - onChange={_onChange} - onFocus={_onFocus} -diff --git a/node_modules/react-native/Libraries/Text/Text.js b/node_modules/react-native/Libraries/Text/Text.js -index df548af..e02f5da 100644 ---- a/node_modules/react-native/Libraries/Text/Text.js -+++ b/node_modules/react-native/Libraries/Text/Text.js -@@ -18,7 +18,11 @@ import processColor from '../StyleSheet/processColor'; - import {getAccessibilityRoleFromRole} from '../Utilities/AcessibilityMapping'; - import Platform from '../Utilities/Platform'; - import TextAncestor from './TextAncestor'; --import {NativeText, NativeVirtualText} from './TextNativeComponent'; -+import { -+ CONTAINS_MAX_NUMBER_OF_LINES_RENAME, -+ NativeText, -+ NativeVirtualText, -+} from './TextNativeComponent'; - import * as React from 'react'; - import {useContext, useMemo, useState} from 'react'; - -@@ -59,6 +63,7 @@ const Text: React.AbstractComponent< - pressRetentionOffset, - role, - suppressHighlighting, -+ numberOfLines, - ...restProps - } = props; - -@@ -192,14 +197,33 @@ const Text: React.AbstractComponent< - } - } - -- let numberOfLines = restProps.numberOfLines; -+ let numberOfLinesValue = numberOfLines; - if (numberOfLines != null && !(numberOfLines >= 0)) { - console.error( - `'numberOfLines' in must be a non-negative number, received: ${numberOfLines}. The value will be set to 0.`, - ); -- numberOfLines = 0; -+ numberOfLinesValue = 0; - } - -+ const numberOfLinesProps = useMemo((): { -+ maximumNumberOfLines?: ?number, -+ numberOfLines?: ?number, -+ } => { -+ // FIXME: Current logic is breaking all Text components. -+ // if (CONTAINS_MAX_NUMBER_OF_LINES_RENAME) { -+ // return { -+ // maximumNumberOfLines: numberOfLinesValue, -+ // }; -+ // } else { -+ // return { -+ // numberOfLines: numberOfLinesValue, -+ // }; -+ // } -+ return { -+ maximumNumberOfLines: numberOfLinesValue, -+ }; -+ }, [numberOfLinesValue]); -+ - const hasTextAncestor = useContext(TextAncestor); - - const _accessible = Platform.select({ -@@ -241,7 +265,6 @@ const Text: React.AbstractComponent< - isHighlighted={isHighlighted} - isPressable={isPressable} - nativeID={id ?? nativeID} -- numberOfLines={numberOfLines} - ref={forwardedRef} - selectable={_selectable} - selectionColor={selectionColor} -@@ -252,6 +275,7 @@ const Text: React.AbstractComponent< - - #import -+#import -+#import - - @implementation RCTMultilineTextInputViewManager - -@@ -17,8 +19,21 @@ - (UIView *)view - return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge]; - } - -+- (RCTShadowView *)shadowView -+{ -+ RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; -+ -+ shadowView.maximumNumberOfLines = 0; -+ shadowView.exactNumberOfLines = 0; -+ -+ return shadowView; -+} -+ - #pragma mark - Multiline (aka TextView) specific properties - - RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) - -+RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) -+RCT_REMAP_SHADOW_PROPERTY(numberOfLines, exactNumberOfLines, NSInteger) -+ - @end -diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h -index 8f4cf7e..6238ebc 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h -+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h -@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN - @property (nonatomic, copy, nullable) NSString *text; - @property (nonatomic, copy, nullable) NSString *placeholder; - @property (nonatomic, assign) NSInteger maximumNumberOfLines; -+@property (nonatomic, assign) NSInteger exactNumberOfLines; - @property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange; - - - (void)uiManagerWillPerformMounting; -diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m -index 04d2446..9d77743 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m -+++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m -@@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText - - - (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize - { -- NSAttributedString *attributedText = [self measurableAttributedText]; -+ NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy]; -+ -+ /* -+ * The block below is responsible for setting the exact height of the view in lines -+ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines -+ * prop and then add random lines at the front. However, they are only used for layout -+ * so they are not visible on the screen. -+ */ -+ if (self.exactNumberOfLines) { -+ NSMutableString *newLines = [NSMutableString stringWithCapacity:self.exactNumberOfLines]; -+ for (NSUInteger i = 0UL; i < self.exactNumberOfLines; ++i) { -+ [newLines appendString:@"\n"]; -+ } -+ [attributedText insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:self.textAttributes.effectiveTextAttributes] atIndex:0]; -+ _maximumNumberOfLines = self.exactNumberOfLines; -+ } - - if (!_textStorage) { - _textContainer = [NSTextContainer new]; -diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m -index 413ac42..56d039c 100644 ---- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m -+++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m -@@ -19,6 +19,7 @@ - (RCTShadowView *)shadowView - RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; - - shadowView.maximumNumberOfLines = 1; -+ shadowView.exactNumberOfLines = 0; - - return shadowView; - } -diff --git a/node_modules/react-native/Libraries/Text/TextNativeComponent.js b/node_modules/react-native/Libraries/Text/TextNativeComponent.js -index 0d59904..3216e43 100644 ---- a/node_modules/react-native/Libraries/Text/TextNativeComponent.js -+++ b/node_modules/react-native/Libraries/Text/TextNativeComponent.js -@@ -9,6 +9,7 @@ - */ - - import {createViewConfig} from '../NativeComponent/ViewConfig'; -+import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes'; - import UIManager from '../ReactNative/UIManager'; - import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass'; - import {type HostComponent} from '../Renderer/shims/ReactNativeTypes'; -@@ -18,6 +19,7 @@ import {type TextProps} from './TextProps'; - - type NativeTextProps = $ReadOnly<{ - ...TextProps, -+ maximumNumberOfLines?: ?number, - isHighlighted?: ?boolean, - selectionColor?: ?ProcessedColorValue, - onClick?: ?(event: PressEvent) => mixed, -@@ -31,7 +33,7 @@ const textViewConfig = { - validAttributes: { - isHighlighted: true, - isPressable: true, -- numberOfLines: true, -+ maximumNumberOfLines: true, - ellipsizeMode: true, - allowFontScaling: true, - dynamicTypeRamp: true, -@@ -73,6 +75,12 @@ export const NativeText: HostComponent = - createViewConfig(textViewConfig), - ): any); - -+const jestIsDefined = typeof jest !== 'undefined'; -+export const CONTAINS_MAX_NUMBER_OF_LINES_RENAME: boolean = jestIsDefined -+ ? true -+ : getNativeComponentAttributes('RCTText')?.NativeProps -+ ?.maximumNumberOfLines === 'number'; -+ - export const NativeVirtualText: HostComponent = - !global.RN$Bridgeless && !UIManager.hasViewManagerConfig('RCTVirtualText') - ? NativeText -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java -index 8cab407..ad5fa96 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java -@@ -12,5 +12,6 @@ public class ViewDefaults { - - public static final float FONT_SIZE_SP = 14.0f; - public static final int LINE_HEIGHT = 0; -- public static final int NUMBER_OF_LINES = Integer.MAX_VALUE; -+ public static final int NUMBER_OF_LINES = -1; -+ public static final int MAXIMUM_NUMBER_OF_LINES = Integer.MAX_VALUE; - } -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java -index 3f76fa7..7a5d096 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java -@@ -96,6 +96,7 @@ public class ViewProps { - public static final String LETTER_SPACING = "letterSpacing"; - public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing"; - public static final String NUMBER_OF_LINES = "numberOfLines"; -+ public static final String MAXIMUM_NUMBER_OF_LINES = "maximumNumberOfLines"; - public static final String ELLIPSIZE_MODE = "ellipsizeMode"; - public static final String ADJUSTS_FONT_SIZE_TO_FIT = "adjustsFontSizeToFit"; - public static final String MINIMUM_FONT_SCALE = "minimumFontScale"; -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java -index b5811c7..96eef96 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java -@@ -303,6 +303,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { - protected boolean mIsAccessibilityLink = false; - - protected int mNumberOfLines = UNSET; -+ protected int mMaxNumberOfLines = UNSET; - protected int mTextAlign = Gravity.NO_GRAVITY; - protected int mTextBreakStrategy = - (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; -@@ -387,6 +388,12 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { - markUpdated(); - } - -+ @ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = UNSET) -+ public void setMaxNumberOfLines(int numberOfLines) { -+ mMaxNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines; -+ markUpdated(); -+ } -+ - @ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN) - public void setLineHeight(float lineHeight) { - mTextAttributes.setLineHeight(lineHeight); -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java -index 7b5d0c1..c3032eb 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java -@@ -49,8 +49,8 @@ public abstract class ReactTextAnchorViewManager minimumFontSize -- && (mNumberOfLines != UNSET && layout.getLineCount() > mNumberOfLines -+ && (mMaxNumberOfLines != UNSET && layout.getLineCount() > mMaxNumberOfLines - || heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) { - // TODO: We could probably use a smarter algorithm here. This will require 0(n) - // measurements -@@ -124,9 +124,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode { - } - - final int lineCount = -- mNumberOfLines == UNSET -+ mMaxNumberOfLines == UNSET - ? layout.getLineCount() -- : Math.min(mNumberOfLines, layout.getLineCount()); -+ : Math.min(mMaxNumberOfLines, layout.getLineCount()); - - // Instead of using `layout.getWidth()` (which may yield a significantly larger width for - // text that is wrapping), compute width using the longest line. -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java -index 190bc27..c2bcdc1 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java -@@ -87,7 +87,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie - - mReactBackgroundManager = new ReactViewBackgroundManager(this); - -- mNumberOfLines = ViewDefaults.NUMBER_OF_LINES; -+ mNumberOfLines = ViewDefaults.MAXIMUM_NUMBER_OF_LINES; - mAdjustsFontSizeToFit = false; - mLinkifyMaskType = 0; - mNotifyOnInlineViewLayout = false; -@@ -576,7 +576,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie - } - - public void setNumberOfLines(int numberOfLines) { -- mNumberOfLines = numberOfLines == 0 ? ViewDefaults.NUMBER_OF_LINES : numberOfLines; -+ mNumberOfLines = numberOfLines == 0 ? ViewDefaults.MAXIMUM_NUMBER_OF_LINES : numberOfLines; - setSingleLine(mNumberOfLines == 1); - setMaxLines(mNumberOfLines); - } -@@ -596,7 +596,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie - public void updateView() { - @Nullable - TextUtils.TruncateAt ellipsizeLocation = -- mNumberOfLines == ViewDefaults.NUMBER_OF_LINES || mAdjustsFontSizeToFit -+ mNumberOfLines == ViewDefaults.MAXIMUM_NUMBER_OF_LINES || mAdjustsFontSizeToFit - ? null - : mEllipsizeLocation; - setEllipsize(ellipsizeLocation); -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java -index 561a2d0..9409cfc 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java -@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; - import android.text.Spanned; - import android.text.StaticLayout; - import android.text.TextPaint; -+import android.text.TextUtils; - import android.util.LayoutDirection; - import android.util.LruCache; - import android.view.View; -@@ -65,6 +66,7 @@ public class TextLayoutManager { - private static final String TEXT_BREAK_STRATEGY_KEY = "textBreakStrategy"; - private static final String HYPHENATION_FREQUENCY_KEY = "android_hyphenationFrequency"; - private static final String MAXIMUM_NUMBER_OF_LINES_KEY = "maximumNumberOfLines"; -+ private static final String NUMBER_OF_LINES_KEY = "numberOfLines"; - private static final LruCache sSpannableCache = - new LruCache<>(spannableCacheSize); - private static final ConcurrentHashMap sTagToSpannableCache = -@@ -385,6 +387,48 @@ public class TextLayoutManager { - ? paragraphAttributes.getInt(MAXIMUM_NUMBER_OF_LINES_KEY) - : UNSET; - -+ int numberOfLines = -+ paragraphAttributes.hasKey(NUMBER_OF_LINES_KEY) -+ ? paragraphAttributes.getInt(NUMBER_OF_LINES_KEY) -+ : UNSET; -+ -+ int lines = layout.getLineCount(); -+ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines >= lines && text.length() > 0) { -+ int numberOfEmptyLines = numberOfLines - lines; -+ SpannableStringBuilder ssb = new SpannableStringBuilder(); -+ -+ // for some reason a newline on end causes issues with computing height so we add a character -+ if (text.toString().endsWith("\n")) { -+ ssb.append("A"); -+ } -+ -+ for (int i = 0; i < numberOfEmptyLines; ++i) { -+ ssb.append("\nA"); -+ } -+ -+ Object[] spans = text.getSpans(0, 0, Object.class); -+ for (Object span : spans) { // It's possible we need to set exl-exl -+ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); -+ }; -+ -+ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); -+ boring = null; -+ layout = createLayout( -+ text, -+ boring, -+ width, -+ widthYogaMeasureMode, -+ includeFontPadding, -+ textBreakStrategy, -+ hyphenationFrequency); -+ } -+ -+ -+ if (numberOfLines != UNSET && numberOfLines != 0) { -+ maximumNumberOfLines = numberOfLines; -+ } -+ -+ - int calculatedLineCount = - maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 - ? layout.getLineCount() -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java -index 0d118f0..0ae44b7 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java -@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; - import android.text.Spanned; - import android.text.StaticLayout; - import android.text.TextPaint; -+import android.text.TextUtils; - import android.util.LayoutDirection; - import android.util.LruCache; - import android.view.View; -@@ -61,6 +62,7 @@ public class TextLayoutManagerMapBuffer { - public static final short PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; - public static final short PA_KEY_INCLUDE_FONT_PADDING = 4; - public static final short PA_KEY_HYPHENATION_FREQUENCY = 5; -+ public static final short PA_KEY_NUMBER_OF_LINES = 6; - - private static final boolean ENABLE_MEASURE_LOGGING = ReactBuildConfig.DEBUG && false; - -@@ -399,6 +401,47 @@ public class TextLayoutManagerMapBuffer { - ? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES) - : UNSET; - -+ int numberOfLines = -+ paragraphAttributes.contains(PA_KEY_NUMBER_OF_LINES) -+ ? paragraphAttributes.getInt(PA_KEY_NUMBER_OF_LINES) -+ : UNSET; -+ -+ int lines = layout.getLineCount(); -+ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines > lines && text.length() > 0) { -+ int numberOfEmptyLines = numberOfLines - lines; -+ SpannableStringBuilder ssb = new SpannableStringBuilder(); -+ -+ // for some reason a newline on end causes issues with computing height so we add a character -+ if (text.toString().endsWith("\n")) { -+ ssb.append("A"); -+ } -+ -+ for (int i = 0; i < numberOfEmptyLines; ++i) { -+ ssb.append("\nA"); -+ } -+ -+ Object[] spans = text.getSpans(0, 0, Object.class); -+ for (Object span : spans) { // It's possible we need to set exl-exl -+ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); -+ }; -+ -+ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); -+ boring = null; -+ layout = createLayout( -+ text, -+ boring, -+ width, -+ widthYogaMeasureMode, -+ includeFontPadding, -+ textBreakStrategy, -+ hyphenationFrequency); -+ } -+ -+ if (numberOfLines != UNSET && numberOfLines != 0) { -+ maximumNumberOfLines = numberOfLines; -+ } -+ -+ - int calculatedLineCount = - maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 - ? layout.getLineCount() -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -index ced37be..ef2f321 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java -@@ -548,7 +548,13 @@ public class ReactEditText extends AppCompatEditText - * href='https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/widget/TextView.java'>TextView.java} - */ - if (isMultiline()) { -+ // we save max lines as setSingleLines overwrites it -+ // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/TextView.java#10671 -+ int maxLines = getMaxLines(); - setSingleLine(false); -+ if (maxLines != -1) { -+ setMaxLines(maxLines); -+ } - } - - // We override the KeyListener so that all keys on the soft input keyboard as well as hardware -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java -index a850510..c59be1d 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java -@@ -41,9 +41,9 @@ public final class ReactTextInputLocalData { - public void apply(EditText editText) { - editText.setText(mText); - editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); -+ editText.setInputType(mInputType); - editText.setMinLines(mMinLines); - editText.setMaxLines(mMaxLines); -- editText.setInputType(mInputType); - editText.setHint(mPlaceholder); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - editText.setBreakStrategy(mBreakStrategy); -diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -index b27ace4..c6a2d63 100644 ---- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java -@@ -737,9 +737,18 @@ public class ReactTextInputManager extends BaseViewManager= Build.VERSION_CODES.M -diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp -index 2994aca..fff0d5e 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp -+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp -@@ -16,6 +16,7 @@ namespace facebook::react { - - bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { - return std::tie( -+ numberOfLines, - maximumNumberOfLines, - ellipsizeMode, - textBreakStrategy, -@@ -23,6 +24,7 @@ bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { - includeFontPadding, - android_hyphenationFrequency) == - std::tie( -+ rhs.numberOfLines, - rhs.maximumNumberOfLines, - rhs.ellipsizeMode, - rhs.textBreakStrategy, -@@ -42,6 +44,7 @@ bool ParagraphAttributes::operator!=(const ParagraphAttributes &rhs) const { - #if RN_DEBUG_STRING_CONVERTIBLE - SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const { - return { -+ debugStringConvertibleItem("numberOfLines", numberOfLines), - debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines), - debugStringConvertibleItem("ellipsizeMode", ellipsizeMode), - debugStringConvertibleItem("textBreakStrategy", textBreakStrategy), -diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h -index f5f87c6..b7d1e90 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h -+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h -@@ -30,6 +30,11 @@ class ParagraphAttributes : public DebugStringConvertible { - public: - #pragma mark - Fields - -+ /* -+ * Number of lines which paragraph takes. -+ */ -+ int numberOfLines{}; -+ - /* - * Maximum number of lines which paragraph can take. - * Zero value represents "no limit". -@@ -92,6 +97,7 @@ struct hash { - const facebook::react::ParagraphAttributes &attributes) const { - return folly::hash::hash_combine( - 0, -+ attributes.numberOfLines, - attributes.maximumNumberOfLines, - attributes.ellipsizeMode, - attributes.textBreakStrategy, -diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -index 8687b89..eab75f4 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -+++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h -@@ -835,10 +835,16 @@ inline ParagraphAttributes convertRawProp( - ParagraphAttributes const &defaultParagraphAttributes) { - auto paragraphAttributes = ParagraphAttributes{}; - -- paragraphAttributes.maximumNumberOfLines = convertRawProp( -+ paragraphAttributes.numberOfLines = convertRawProp( - context, - rawProps, - "numberOfLines", -+ sourceParagraphAttributes.numberOfLines, -+ defaultParagraphAttributes.numberOfLines); -+ paragraphAttributes.maximumNumberOfLines = convertRawProp( -+ context, -+ rawProps, -+ "maximumNumberOfLines", - sourceParagraphAttributes.maximumNumberOfLines, - defaultParagraphAttributes.maximumNumberOfLines); - paragraphAttributes.ellipsizeMode = convertRawProp( -@@ -913,6 +919,7 @@ inline std::string toString(AttributedString::Range const &range) { - inline folly::dynamic toDynamic( - const ParagraphAttributes ¶graphAttributes) { - auto values = folly::dynamic::object(); -+ values("numberOfLines", paragraphAttributes.numberOfLines); - values("maximumNumberOfLines", paragraphAttributes.maximumNumberOfLines); - values("ellipsizeMode", toString(paragraphAttributes.ellipsizeMode)); - values("textBreakStrategy", toString(paragraphAttributes.textBreakStrategy)); -@@ -1118,6 +1125,7 @@ constexpr static MapBuffer::Key PA_KEY_TEXT_BREAK_STRATEGY = 2; - constexpr static MapBuffer::Key PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; - constexpr static MapBuffer::Key PA_KEY_INCLUDE_FONT_PADDING = 4; - constexpr static MapBuffer::Key PA_KEY_HYPHENATION_FREQUENCY = 5; -+constexpr static MapBuffer::Key PA_KEY_NUMBER_OF_LINES = 6; - - inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { - auto builder = MapBufferBuilder(); -@@ -1135,6 +1143,8 @@ inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { - builder.putString( - PA_KEY_HYPHENATION_FREQUENCY, - toString(paragraphAttributes.android_hyphenationFrequency)); -+ builder.putInt( -+ PA_KEY_NUMBER_OF_LINES, paragraphAttributes.numberOfLines); - - return builder.build(); - } -diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp -index 9953e22..98eb3da 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp -+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp -@@ -56,6 +56,10 @@ AndroidTextInputProps::AndroidTextInputProps( - "numberOfLines", - sourceProps.numberOfLines, - {0})), -+ maximumNumberOfLines(CoreFeatures::enablePropIteratorSetter? sourceProps.maximumNumberOfLines : convertRawProp(context, rawProps, -+ "maximumNumberOfLines", -+ sourceProps.maximumNumberOfLines, -+ {0})), - disableFullscreenUI(CoreFeatures::enablePropIteratorSetter? sourceProps.disableFullscreenUI : convertRawProp(context, rawProps, - "disableFullscreenUI", - sourceProps.disableFullscreenUI, -@@ -281,6 +285,12 @@ void AndroidTextInputProps::setProp( - value, - paragraphAttributes, - maximumNumberOfLines, -+ "maximumNumberOfLines"); -+ REBUILD_FIELD_SWITCH_CASE( -+ paDefaults, -+ value, -+ paragraphAttributes, -+ numberOfLines, - "numberOfLines"); - REBUILD_FIELD_SWITCH_CASE( - paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode"); -@@ -323,6 +333,7 @@ void AndroidTextInputProps::setProp( - } - - switch (hash) { -+ RAW_SET_PROP_SWITCH_CASE_BASIC(maximumNumberOfLines); - RAW_SET_PROP_SWITCH_CASE_BASIC(autoComplete); - RAW_SET_PROP_SWITCH_CASE_BASIC(returnKeyLabel); - RAW_SET_PROP_SWITCH_CASE_BASIC(numberOfLines); -@@ -422,6 +433,7 @@ void AndroidTextInputProps::setProp( - // TODO T53300085: support this in codegen; this was hand-written - folly::dynamic AndroidTextInputProps::getDynamic() const { - folly::dynamic props = folly::dynamic::object(); -+ props["maximumNumberOfLines"] = maximumNumberOfLines; - props["autoComplete"] = autoComplete; - props["returnKeyLabel"] = returnKeyLabel; - props["numberOfLines"] = numberOfLines; -diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h -index ba39ebb..ead28e3 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h -+++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h -@@ -84,6 +84,7 @@ class AndroidTextInputProps final : public ViewProps, public BaseTextProps { - std::string autoComplete{}; - std::string returnKeyLabel{}; - int numberOfLines{0}; -+ int maximumNumberOfLines{0}; - bool disableFullscreenUI{false}; - std::string textBreakStrategy{}; - SharedColor underlineColorAndroid{}; -diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -index 368c334..a1bb33e 100644 ---- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -+++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm -@@ -244,26 +244,51 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString - - #pragma mark - Private - --- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString -++- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)inputAttributedString - paragraphAttributes:(ParagraphAttributes)paragraphAttributes - size:(CGSize)size - { -- NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size]; -+ NSMutableAttributedString *attributedString = [ inputAttributedString mutableCopy]; -+ -+ /* -+ * The block below is responsible for setting the exact height of the view in lines -+ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines -+ * prop and then add random lines at the front. However, they are only used for layout -+ * so they are not visible on the screen. This method is used for drawing only for Paragraph component -+ * but we set exact height in lines only on TextInput that doesn't use it. -+ */ -+ if (paragraphAttributes.numberOfLines) { -+ paragraphAttributes.maximumNumberOfLines = paragraphAttributes.numberOfLines; -+ NSMutableString *newLines = [NSMutableString stringWithCapacity: paragraphAttributes.numberOfLines]; -+ for (NSUInteger i = 0UL; i < paragraphAttributes.numberOfLines; ++i) { -+ // K is added on purpose. New line seems to be not enough for NTtextContainer -+ [newLines appendString:@"K\n"]; -+ } -+ NSDictionary * attributesOfFirstCharacter = [inputAttributedString attributesAtIndex:0 effectiveRange:NULL]; - -- textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. -- textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 -- ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) -- : NSLineBreakByClipping; -- textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; -+ [attributedString insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:attributesOfFirstCharacter] atIndex:0]; -+ } -+ -+ NSTextContainer *textContainer = [NSTextContainer new]; - - NSLayoutManager *layoutManager = [NSLayoutManager new]; - layoutManager.usesFontLeading = NO; - [layoutManager addTextContainer:textContainer]; - -- NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; -+ NSTextStorage *textStorage = [NSTextStorage new]; - - [textStorage addLayoutManager:layoutManager]; - -+ textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. -+ textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 -+ ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) -+ : NSLineBreakByClipping; -+ textContainer.size = size; -+ textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; -+ -+ [textStorage replaceCharactersInRange:(NSRange){0, textStorage.length} withAttributedString:attributedString]; -+ -+ - if (paragraphAttributes.adjustsFontSizeToFit) { - CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0; - CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0; From df2342f96f795c72da376e35a1b482f9b127ab02 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Mon, 27 Nov 2023 22:38:41 +0100 Subject: [PATCH 29/57] add back patch --- ...eact-native+0.72.4+002+NumberOfLines.patch | 978 ++++++++++++++++++ 1 file changed, 978 insertions(+) create mode 100644 patches/react-native+0.72.4+002+NumberOfLines.patch diff --git a/patches/react-native+0.72.4+002+NumberOfLines.patch b/patches/react-native+0.72.4+002+NumberOfLines.patch new file mode 100644 index 000000000000..75422f84708e --- /dev/null +++ b/patches/react-native+0.72.4+002+NumberOfLines.patch @@ -0,0 +1,978 @@ +diff --git a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +index 55b770d..4073836 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js +@@ -179,6 +179,13 @@ export type NativeProps = $ReadOnly<{| + */ + numberOfLines?: ?Int32, + ++ /** ++ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ * @platform android ++ */ ++ maximumNumberOfLines?: ?Int32, ++ + /** + * When `false`, if there is a small amount of space available around a text input + * (e.g. landscape orientation on a phone), the OS may choose to have the user edit +diff --git a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +index 6f69329..d531bee 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/RCTTextInputViewConfig.js +@@ -144,6 +144,8 @@ const RCTTextInputViewConfig = { + placeholder: true, + autoCorrect: true, + multiline: true, ++ numberOfLines: true, ++ maximumNumberOfLines: true, + textContentType: true, + maxLength: true, + autoCapitalize: true, +diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts +index 8badb2a..b19f197 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts ++++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.d.ts +@@ -347,12 +347,6 @@ export interface TextInputAndroidProps { + */ + inlineImagePadding?: number | undefined; + +- /** +- * Sets the number of lines for a TextInput. +- * Use it with multiline set to true to be able to fill the lines. +- */ +- numberOfLines?: number | undefined; +- + /** + * Sets the return key to the label. Use it instead of `returnKeyType`. + * @platform android +@@ -663,11 +657,30 @@ export interface TextInputProps + */ + maxLength?: number | undefined; + ++ /** ++ * Sets the maximum number of lines for a TextInput. ++ * Use it with multiline set to true to be able to fill the lines. ++ */ ++ maxNumberOfLines?: number | undefined; ++ + /** + * If true, the text input can be multiple lines. The default value is false. + */ + multiline?: boolean | undefined; + ++ /** ++ * Sets the number of lines for a TextInput. ++ * Use it with multiline set to true to be able to fill the lines. ++ */ ++ numberOfLines?: number | undefined; ++ ++ /** ++ * Sets the number of rows for a TextInput. ++ * Use it with multiline set to true to be able to fill the lines. ++ */ ++ rows?: number | undefined; ++ ++ + /** + * Callback that is called when the text input is blurred + */ +diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js +index 7ed4579..b1d994e 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.flow.js +@@ -343,26 +343,12 @@ type AndroidProps = $ReadOnly<{| + */ + inlineImagePadding?: ?number, + +- /** +- * Sets the number of lines for a `TextInput`. Use it with multiline set to +- * `true` to be able to fill the lines. +- * @platform android +- */ +- numberOfLines?: ?number, +- + /** + * Sets the return key to the label. Use it instead of `returnKeyType`. + * @platform android + */ + returnKeyLabel?: ?string, + +- /** +- * Sets the number of rows for a `TextInput`. Use it with multiline set to +- * `true` to be able to fill the lines. +- * @platform android +- */ +- rows?: ?number, +- + /** + * When `false`, it will prevent the soft keyboard from showing when the field is focused. + * Defaults to `true`. +@@ -632,6 +618,12 @@ export type Props = $ReadOnly<{| + */ + keyboardType?: ?KeyboardType, + ++ /** ++ * Sets the maximum number of lines for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ */ ++ maxNumberOfLines?: ?number, ++ + /** + * Specifies largest possible scale a font can reach when `allowFontScaling` is enabled. + * Possible values: +@@ -653,6 +645,12 @@ export type Props = $ReadOnly<{| + */ + multiline?: ?boolean, + ++ /** ++ * Sets the number of lines for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ */ ++ numberOfLines?: ?number, ++ + /** + * Callback that is called when the text input is blurred. + */ +@@ -814,6 +812,12 @@ export type Props = $ReadOnly<{| + */ + returnKeyType?: ?ReturnKeyType, + ++ /** ++ * Sets the number of rows for a `TextInput`. Use it with multiline set to ++ * `true` to be able to fill the lines. ++ */ ++ rows?: ?number, ++ + /** + * If `true`, the text input obscures the text entered so that sensitive text + * like passwords stay secure. The default value is `false`. Does not work with 'multiline={true}'. +diff --git a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js +index 2127191..542fc06 100644 +--- a/node_modules/react-native/Libraries/Components/TextInput/TextInput.js ++++ b/node_modules/react-native/Libraries/Components/TextInput/TextInput.js +@@ -390,7 +390,6 @@ type AndroidProps = $ReadOnly<{| + /** + * Sets the number of lines for a `TextInput`. Use it with multiline set to + * `true` to be able to fill the lines. +- * @platform android + */ + numberOfLines?: ?number, + +@@ -403,10 +402,14 @@ type AndroidProps = $ReadOnly<{| + /** + * Sets the number of rows for a `TextInput`. Use it with multiline set to + * `true` to be able to fill the lines. +- * @platform android + */ + rows?: ?number, + ++ /** ++ * Sets the maximum number of lines the TextInput can have. ++ */ ++ maxNumberOfLines?: ?number, ++ + /** + * When `false`, it will prevent the soft keyboard from showing when the field is focused. + * Defaults to `true`. +@@ -1069,6 +1072,9 @@ function InternalTextInput(props: Props): React.Node { + accessibilityState, + id, + tabIndex, ++ rows, ++ numberOfLines, ++ maxNumberOfLines, + selection: propsSelection, + ...otherProps + } = props; +@@ -1427,6 +1433,8 @@ function InternalTextInput(props: Props): React.Node { + focusable={tabIndex !== undefined ? !tabIndex : focusable} + mostRecentEventCount={mostRecentEventCount} + nativeID={id ?? props.nativeID} ++ numberOfLines={props.rows ?? props.numberOfLines} ++ maximumNumberOfLines={maxNumberOfLines} + onBlur={_onBlur} + onKeyPressSync={props.unstable_onKeyPressSync} + onChange={_onChange} +@@ -1482,6 +1490,7 @@ function InternalTextInput(props: Props): React.Node { + mostRecentEventCount={mostRecentEventCount} + nativeID={id ?? props.nativeID} + numberOfLines={props.rows ?? props.numberOfLines} ++ maximumNumberOfLines={maxNumberOfLines} + onBlur={_onBlur} + onChange={_onChange} + onFocus={_onFocus} +diff --git a/node_modules/react-native/Libraries/Text/Text.js b/node_modules/react-native/Libraries/Text/Text.js +index df548af..e02f5da 100644 +--- a/node_modules/react-native/Libraries/Text/Text.js ++++ b/node_modules/react-native/Libraries/Text/Text.js +@@ -18,7 +18,11 @@ import processColor from '../StyleSheet/processColor'; + import {getAccessibilityRoleFromRole} from '../Utilities/AcessibilityMapping'; + import Platform from '../Utilities/Platform'; + import TextAncestor from './TextAncestor'; +-import {NativeText, NativeVirtualText} from './TextNativeComponent'; ++import { ++ CONTAINS_MAX_NUMBER_OF_LINES_RENAME, ++ NativeText, ++ NativeVirtualText, ++} from './TextNativeComponent'; + import * as React from 'react'; + import {useContext, useMemo, useState} from 'react'; + +@@ -59,6 +63,7 @@ const Text: React.AbstractComponent< + pressRetentionOffset, + role, + suppressHighlighting, ++ numberOfLines, + ...restProps + } = props; + +@@ -192,14 +197,33 @@ const Text: React.AbstractComponent< + } + } + +- let numberOfLines = restProps.numberOfLines; ++ let numberOfLinesValue = numberOfLines; + if (numberOfLines != null && !(numberOfLines >= 0)) { + console.error( + `'numberOfLines' in must be a non-negative number, received: ${numberOfLines}. The value will be set to 0.`, + ); +- numberOfLines = 0; ++ numberOfLinesValue = 0; + } + ++ const numberOfLinesProps = useMemo((): { ++ maximumNumberOfLines?: ?number, ++ numberOfLines?: ?number, ++ } => { ++ // FIXME: Current logic is breaking all Text components. ++ // if (CONTAINS_MAX_NUMBER_OF_LINES_RENAME) { ++ // return { ++ // maximumNumberOfLines: numberOfLinesValue, ++ // }; ++ // } else { ++ // return { ++ // numberOfLines: numberOfLinesValue, ++ // }; ++ // } ++ return { ++ maximumNumberOfLines: numberOfLinesValue, ++ }; ++ }, [numberOfLinesValue]); ++ + const hasTextAncestor = useContext(TextAncestor); + + const _accessible = Platform.select({ +@@ -241,7 +265,6 @@ const Text: React.AbstractComponent< + isHighlighted={isHighlighted} + isPressable={isPressable} + nativeID={id ?? nativeID} +- numberOfLines={numberOfLines} + ref={forwardedRef} + selectable={_selectable} + selectionColor={selectionColor} +@@ -252,6 +275,7 @@ const Text: React.AbstractComponent< + + #import ++#import ++#import + + @implementation RCTMultilineTextInputViewManager + +@@ -17,8 +19,21 @@ - (UIView *)view + return [[RCTMultilineTextInputView alloc] initWithBridge:self.bridge]; + } + ++- (RCTShadowView *)shadowView ++{ ++ RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; ++ ++ shadowView.maximumNumberOfLines = 0; ++ shadowView.exactNumberOfLines = 0; ++ ++ return shadowView; ++} ++ + #pragma mark - Multiline (aka TextView) specific properties + + RCT_REMAP_VIEW_PROPERTY(dataDetectorTypes, backedTextInputView.dataDetectorTypes, UIDataDetectorTypes) + ++RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger) ++RCT_REMAP_SHADOW_PROPERTY(numberOfLines, exactNumberOfLines, NSInteger) ++ + @end +diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h +index 8f4cf7e..6238ebc 100644 +--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h ++++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.h +@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN + @property (nonatomic, copy, nullable) NSString *text; + @property (nonatomic, copy, nullable) NSString *placeholder; + @property (nonatomic, assign) NSInteger maximumNumberOfLines; ++@property (nonatomic, assign) NSInteger exactNumberOfLines; + @property (nonatomic, copy, nullable) RCTDirectEventBlock onContentSizeChange; + + - (void)uiManagerWillPerformMounting; +diff --git a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m +index 04d2446..9d77743 100644 +--- a/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m ++++ b/node_modules/react-native/Libraries/Text/TextInput/RCTBaseTextInputShadowView.m +@@ -218,7 +218,22 @@ - (NSAttributedString *)measurableAttributedText + + - (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize + { +- NSAttributedString *attributedText = [self measurableAttributedText]; ++ NSMutableAttributedString *attributedText = [[self measurableAttributedText] mutableCopy]; ++ ++ /* ++ * The block below is responsible for setting the exact height of the view in lines ++ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines ++ * prop and then add random lines at the front. However, they are only used for layout ++ * so they are not visible on the screen. ++ */ ++ if (self.exactNumberOfLines) { ++ NSMutableString *newLines = [NSMutableString stringWithCapacity:self.exactNumberOfLines]; ++ for (NSUInteger i = 0UL; i < self.exactNumberOfLines; ++i) { ++ [newLines appendString:@"\n"]; ++ } ++ [attributedText insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:self.textAttributes.effectiveTextAttributes] atIndex:0]; ++ _maximumNumberOfLines = self.exactNumberOfLines; ++ } + + if (!_textStorage) { + _textContainer = [NSTextContainer new]; +diff --git a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m +index 413ac42..56d039c 100644 +--- a/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m ++++ b/node_modules/react-native/Libraries/Text/TextInput/Singleline/RCTSinglelineTextInputViewManager.m +@@ -19,6 +19,7 @@ - (RCTShadowView *)shadowView + RCTBaseTextInputShadowView *shadowView = (RCTBaseTextInputShadowView *)[super shadowView]; + + shadowView.maximumNumberOfLines = 1; ++ shadowView.exactNumberOfLines = 0; + + return shadowView; + } +diff --git a/node_modules/react-native/Libraries/Text/TextNativeComponent.js b/node_modules/react-native/Libraries/Text/TextNativeComponent.js +index 0d59904..3216e43 100644 +--- a/node_modules/react-native/Libraries/Text/TextNativeComponent.js ++++ b/node_modules/react-native/Libraries/Text/TextNativeComponent.js +@@ -9,6 +9,7 @@ + */ + + import {createViewConfig} from '../NativeComponent/ViewConfig'; ++import getNativeComponentAttributes from '../ReactNative/getNativeComponentAttributes'; + import UIManager from '../ReactNative/UIManager'; + import createReactNativeComponentClass from '../Renderer/shims/createReactNativeComponentClass'; + import {type HostComponent} from '../Renderer/shims/ReactNativeTypes'; +@@ -18,6 +19,7 @@ import {type TextProps} from './TextProps'; + + type NativeTextProps = $ReadOnly<{ + ...TextProps, ++ maximumNumberOfLines?: ?number, + isHighlighted?: ?boolean, + selectionColor?: ?ProcessedColorValue, + onClick?: ?(event: PressEvent) => mixed, +@@ -31,7 +33,7 @@ const textViewConfig = { + validAttributes: { + isHighlighted: true, + isPressable: true, +- numberOfLines: true, ++ maximumNumberOfLines: true, + ellipsizeMode: true, + allowFontScaling: true, + dynamicTypeRamp: true, +@@ -73,6 +75,12 @@ export const NativeText: HostComponent = + createViewConfig(textViewConfig), + ): any); + ++const jestIsDefined = typeof jest !== 'undefined'; ++export const CONTAINS_MAX_NUMBER_OF_LINES_RENAME: boolean = jestIsDefined ++ ? true ++ : getNativeComponentAttributes('RCTText')?.NativeProps ++ ?.maximumNumberOfLines === 'number'; ++ + export const NativeVirtualText: HostComponent = + !global.RN$Bridgeless && !UIManager.hasViewManagerConfig('RCTVirtualText') + ? NativeText +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java +index 8cab407..ad5fa96 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewDefaults.java +@@ -12,5 +12,6 @@ public class ViewDefaults { + + public static final float FONT_SIZE_SP = 14.0f; + public static final int LINE_HEIGHT = 0; +- public static final int NUMBER_OF_LINES = Integer.MAX_VALUE; ++ public static final int NUMBER_OF_LINES = -1; ++ public static final int MAXIMUM_NUMBER_OF_LINES = Integer.MAX_VALUE; + } +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +index 3f76fa7..7a5d096 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewProps.java +@@ -96,6 +96,7 @@ public class ViewProps { + public static final String LETTER_SPACING = "letterSpacing"; + public static final String NEEDS_OFFSCREEN_ALPHA_COMPOSITING = "needsOffscreenAlphaCompositing"; + public static final String NUMBER_OF_LINES = "numberOfLines"; ++ public static final String MAXIMUM_NUMBER_OF_LINES = "maximumNumberOfLines"; + public static final String ELLIPSIZE_MODE = "ellipsizeMode"; + public static final String ADJUSTS_FONT_SIZE_TO_FIT = "adjustsFontSizeToFit"; + public static final String MINIMUM_FONT_SCALE = "minimumFontScale"; +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +index b5811c7..96eef96 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java +@@ -303,6 +303,7 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { + protected boolean mIsAccessibilityLink = false; + + protected int mNumberOfLines = UNSET; ++ protected int mMaxNumberOfLines = UNSET; + protected int mTextAlign = Gravity.NO_GRAVITY; + protected int mTextBreakStrategy = + (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) ? 0 : Layout.BREAK_STRATEGY_HIGH_QUALITY; +@@ -387,6 +388,12 @@ public abstract class ReactBaseTextShadowNode extends LayoutShadowNode { + markUpdated(); + } + ++ @ReactProp(name = ViewProps.MAXIMUM_NUMBER_OF_LINES, defaultInt = UNSET) ++ public void setMaxNumberOfLines(int numberOfLines) { ++ mMaxNumberOfLines = numberOfLines == 0 ? UNSET : numberOfLines; ++ markUpdated(); ++ } ++ + @ReactProp(name = ViewProps.LINE_HEIGHT, defaultFloat = Float.NaN) + public void setLineHeight(float lineHeight) { + mTextAttributes.setLineHeight(lineHeight); +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java +index 7b5d0c1..c3032eb 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java +@@ -49,8 +49,8 @@ public abstract class ReactTextAnchorViewManager minimumFontSize +- && (mNumberOfLines != UNSET && layout.getLineCount() > mNumberOfLines ++ && (mMaxNumberOfLines != UNSET && layout.getLineCount() > mMaxNumberOfLines + || heightMode != YogaMeasureMode.UNDEFINED && layout.getHeight() > height)) { + // TODO: We could probably use a smarter algorithm here. This will require 0(n) + // measurements +@@ -124,9 +124,9 @@ public class ReactTextShadowNode extends ReactBaseTextShadowNode { + } + + final int lineCount = +- mNumberOfLines == UNSET ++ mMaxNumberOfLines == UNSET + ? layout.getLineCount() +- : Math.min(mNumberOfLines, layout.getLineCount()); ++ : Math.min(mMaxNumberOfLines, layout.getLineCount()); + + // Instead of using `layout.getWidth()` (which may yield a significantly larger width for + // text that is wrapping), compute width using the longest line. +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +index 190bc27..c2bcdc1 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +@@ -87,7 +87,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie + + mReactBackgroundManager = new ReactViewBackgroundManager(this); + +- mNumberOfLines = ViewDefaults.NUMBER_OF_LINES; ++ mNumberOfLines = ViewDefaults.MAXIMUM_NUMBER_OF_LINES; + mAdjustsFontSizeToFit = false; + mLinkifyMaskType = 0; + mNotifyOnInlineViewLayout = false; +@@ -576,7 +576,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie + } + + public void setNumberOfLines(int numberOfLines) { +- mNumberOfLines = numberOfLines == 0 ? ViewDefaults.NUMBER_OF_LINES : numberOfLines; ++ mNumberOfLines = numberOfLines == 0 ? ViewDefaults.MAXIMUM_NUMBER_OF_LINES : numberOfLines; + setSingleLine(mNumberOfLines == 1); + setMaxLines(mNumberOfLines); + } +@@ -596,7 +596,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie + public void updateView() { + @Nullable + TextUtils.TruncateAt ellipsizeLocation = +- mNumberOfLines == ViewDefaults.NUMBER_OF_LINES || mAdjustsFontSizeToFit ++ mNumberOfLines == ViewDefaults.MAXIMUM_NUMBER_OF_LINES || mAdjustsFontSizeToFit + ? null + : mEllipsizeLocation; + setEllipsize(ellipsizeLocation); +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +index 561a2d0..9409cfc 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.java +@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; + import android.text.Spanned; + import android.text.StaticLayout; + import android.text.TextPaint; ++import android.text.TextUtils; + import android.util.LayoutDirection; + import android.util.LruCache; + import android.view.View; +@@ -65,6 +66,7 @@ public class TextLayoutManager { + private static final String TEXT_BREAK_STRATEGY_KEY = "textBreakStrategy"; + private static final String HYPHENATION_FREQUENCY_KEY = "android_hyphenationFrequency"; + private static final String MAXIMUM_NUMBER_OF_LINES_KEY = "maximumNumberOfLines"; ++ private static final String NUMBER_OF_LINES_KEY = "numberOfLines"; + private static final LruCache sSpannableCache = + new LruCache<>(spannableCacheSize); + private static final ConcurrentHashMap sTagToSpannableCache = +@@ -385,6 +387,48 @@ public class TextLayoutManager { + ? paragraphAttributes.getInt(MAXIMUM_NUMBER_OF_LINES_KEY) + : UNSET; + ++ int numberOfLines = ++ paragraphAttributes.hasKey(NUMBER_OF_LINES_KEY) ++ ? paragraphAttributes.getInt(NUMBER_OF_LINES_KEY) ++ : UNSET; ++ ++ int lines = layout.getLineCount(); ++ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines >= lines && text.length() > 0) { ++ int numberOfEmptyLines = numberOfLines - lines; ++ SpannableStringBuilder ssb = new SpannableStringBuilder(); ++ ++ // for some reason a newline on end causes issues with computing height so we add a character ++ if (text.toString().endsWith("\n")) { ++ ssb.append("A"); ++ } ++ ++ for (int i = 0; i < numberOfEmptyLines; ++i) { ++ ssb.append("\nA"); ++ } ++ ++ Object[] spans = text.getSpans(0, 0, Object.class); ++ for (Object span : spans) { // It's possible we need to set exl-exl ++ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); ++ }; ++ ++ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); ++ boring = null; ++ layout = createLayout( ++ text, ++ boring, ++ width, ++ widthYogaMeasureMode, ++ includeFontPadding, ++ textBreakStrategy, ++ hyphenationFrequency); ++ } ++ ++ ++ if (numberOfLines != UNSET && numberOfLines != 0) { ++ maximumNumberOfLines = numberOfLines; ++ } ++ ++ + int calculatedLineCount = + maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 + ? layout.getLineCount() +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +index 0d118f0..0ae44b7 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java +@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder; + import android.text.Spanned; + import android.text.StaticLayout; + import android.text.TextPaint; ++import android.text.TextUtils; + import android.util.LayoutDirection; + import android.util.LruCache; + import android.view.View; +@@ -61,6 +62,7 @@ public class TextLayoutManagerMapBuffer { + public static final short PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; + public static final short PA_KEY_INCLUDE_FONT_PADDING = 4; + public static final short PA_KEY_HYPHENATION_FREQUENCY = 5; ++ public static final short PA_KEY_NUMBER_OF_LINES = 6; + + private static final boolean ENABLE_MEASURE_LOGGING = ReactBuildConfig.DEBUG && false; + +@@ -399,6 +401,47 @@ public class TextLayoutManagerMapBuffer { + ? paragraphAttributes.getInt(PA_KEY_MAX_NUMBER_OF_LINES) + : UNSET; + ++ int numberOfLines = ++ paragraphAttributes.contains(PA_KEY_NUMBER_OF_LINES) ++ ? paragraphAttributes.getInt(PA_KEY_NUMBER_OF_LINES) ++ : UNSET; ++ ++ int lines = layout.getLineCount(); ++ if (numberOfLines != UNSET && numberOfLines != 0 && numberOfLines > lines && text.length() > 0) { ++ int numberOfEmptyLines = numberOfLines - lines; ++ SpannableStringBuilder ssb = new SpannableStringBuilder(); ++ ++ // for some reason a newline on end causes issues with computing height so we add a character ++ if (text.toString().endsWith("\n")) { ++ ssb.append("A"); ++ } ++ ++ for (int i = 0; i < numberOfEmptyLines; ++i) { ++ ssb.append("\nA"); ++ } ++ ++ Object[] spans = text.getSpans(0, 0, Object.class); ++ for (Object span : spans) { // It's possible we need to set exl-exl ++ ssb.setSpan(span, 0, ssb.length(), text.getSpanFlags(span)); ++ }; ++ ++ text = new SpannableStringBuilder(TextUtils.concat(text, ssb)); ++ boring = null; ++ layout = createLayout( ++ text, ++ boring, ++ width, ++ widthYogaMeasureMode, ++ includeFontPadding, ++ textBreakStrategy, ++ hyphenationFrequency); ++ } ++ ++ if (numberOfLines != UNSET && numberOfLines != 0) { ++ maximumNumberOfLines = numberOfLines; ++ } ++ ++ + int calculatedLineCount = + maximumNumberOfLines == UNSET || maximumNumberOfLines == 0 + ? layout.getLineCount() +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +index ced37be..ef2f321 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +@@ -548,7 +548,13 @@ public class ReactEditText extends AppCompatEditText + * href='https://android.googlesource.com/platform/frameworks/base/+/jb-release/core/java/android/widget/TextView.java'>TextView.java} + */ + if (isMultiline()) { ++ // we save max lines as setSingleLines overwrites it ++ // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/TextView.java#10671 ++ int maxLines = getMaxLines(); + setSingleLine(false); ++ if (maxLines != -1) { ++ setMaxLines(maxLines); ++ } + } + + // We override the KeyListener so that all keys on the soft input keyboard as well as hardware +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java +index a850510..c59be1d 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java +@@ -41,9 +41,9 @@ public final class ReactTextInputLocalData { + public void apply(EditText editText) { + editText.setText(mText); + editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); ++ editText.setInputType(mInputType); + editText.setMinLines(mMinLines); + editText.setMaxLines(mMaxLines); +- editText.setInputType(mInputType); + editText.setHint(mPlaceholder); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + editText.setBreakStrategy(mBreakStrategy); +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +index b27ace4..c6a2d63 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +@@ -737,9 +737,18 @@ public class ReactTextInputManager extends BaseViewManager= Build.VERSION_CODES.M +diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp +index 2994aca..fff0d5e 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp ++++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.cpp +@@ -16,6 +16,7 @@ namespace facebook::react { + + bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { + return std::tie( ++ numberOfLines, + maximumNumberOfLines, + ellipsizeMode, + textBreakStrategy, +@@ -23,6 +24,7 @@ bool ParagraphAttributes::operator==(const ParagraphAttributes &rhs) const { + includeFontPadding, + android_hyphenationFrequency) == + std::tie( ++ rhs.numberOfLines, + rhs.maximumNumberOfLines, + rhs.ellipsizeMode, + rhs.textBreakStrategy, +@@ -42,6 +44,7 @@ bool ParagraphAttributes::operator!=(const ParagraphAttributes &rhs) const { + #if RN_DEBUG_STRING_CONVERTIBLE + SharedDebugStringConvertibleList ParagraphAttributes::getDebugProps() const { + return { ++ debugStringConvertibleItem("numberOfLines", numberOfLines), + debugStringConvertibleItem("maximumNumberOfLines", maximumNumberOfLines), + debugStringConvertibleItem("ellipsizeMode", ellipsizeMode), + debugStringConvertibleItem("textBreakStrategy", textBreakStrategy), +diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h +index f5f87c6..b7d1e90 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/ParagraphAttributes.h +@@ -30,6 +30,11 @@ class ParagraphAttributes : public DebugStringConvertible { + public: + #pragma mark - Fields + ++ /* ++ * Number of lines which paragraph takes. ++ */ ++ int numberOfLines{}; ++ + /* + * Maximum number of lines which paragraph can take. + * Zero value represents "no limit". +@@ -92,6 +97,7 @@ struct hash { + const facebook::react::ParagraphAttributes &attributes) const { + return folly::hash::hash_combine( + 0, ++ attributes.numberOfLines, + attributes.maximumNumberOfLines, + attributes.ellipsizeMode, + attributes.textBreakStrategy, +diff --git a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +index 8687b89..eab75f4 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/attributedstring/conversions.h +@@ -835,10 +835,16 @@ inline ParagraphAttributes convertRawProp( + ParagraphAttributes const &defaultParagraphAttributes) { + auto paragraphAttributes = ParagraphAttributes{}; + +- paragraphAttributes.maximumNumberOfLines = convertRawProp( ++ paragraphAttributes.numberOfLines = convertRawProp( + context, + rawProps, + "numberOfLines", ++ sourceParagraphAttributes.numberOfLines, ++ defaultParagraphAttributes.numberOfLines); ++ paragraphAttributes.maximumNumberOfLines = convertRawProp( ++ context, ++ rawProps, ++ "maximumNumberOfLines", + sourceParagraphAttributes.maximumNumberOfLines, + defaultParagraphAttributes.maximumNumberOfLines); + paragraphAttributes.ellipsizeMode = convertRawProp( +@@ -913,6 +919,7 @@ inline std::string toString(AttributedString::Range const &range) { + inline folly::dynamic toDynamic( + const ParagraphAttributes ¶graphAttributes) { + auto values = folly::dynamic::object(); ++ values("numberOfLines", paragraphAttributes.numberOfLines); + values("maximumNumberOfLines", paragraphAttributes.maximumNumberOfLines); + values("ellipsizeMode", toString(paragraphAttributes.ellipsizeMode)); + values("textBreakStrategy", toString(paragraphAttributes.textBreakStrategy)); +@@ -1118,6 +1125,7 @@ constexpr static MapBuffer::Key PA_KEY_TEXT_BREAK_STRATEGY = 2; + constexpr static MapBuffer::Key PA_KEY_ADJUST_FONT_SIZE_TO_FIT = 3; + constexpr static MapBuffer::Key PA_KEY_INCLUDE_FONT_PADDING = 4; + constexpr static MapBuffer::Key PA_KEY_HYPHENATION_FREQUENCY = 5; ++constexpr static MapBuffer::Key PA_KEY_NUMBER_OF_LINES = 6; + + inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { + auto builder = MapBufferBuilder(); +@@ -1135,6 +1143,8 @@ inline MapBuffer toMapBuffer(const ParagraphAttributes ¶graphAttributes) { + builder.putString( + PA_KEY_HYPHENATION_FREQUENCY, + toString(paragraphAttributes.android_hyphenationFrequency)); ++ builder.putInt( ++ PA_KEY_NUMBER_OF_LINES, paragraphAttributes.numberOfLines); + + return builder.build(); + } +diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp +index 9953e22..98eb3da 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp ++++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp +@@ -56,6 +56,10 @@ AndroidTextInputProps::AndroidTextInputProps( + "numberOfLines", + sourceProps.numberOfLines, + {0})), ++ maximumNumberOfLines(CoreFeatures::enablePropIteratorSetter? sourceProps.maximumNumberOfLines : convertRawProp(context, rawProps, ++ "maximumNumberOfLines", ++ sourceProps.maximumNumberOfLines, ++ {0})), + disableFullscreenUI(CoreFeatures::enablePropIteratorSetter? sourceProps.disableFullscreenUI : convertRawProp(context, rawProps, + "disableFullscreenUI", + sourceProps.disableFullscreenUI, +@@ -281,6 +285,12 @@ void AndroidTextInputProps::setProp( + value, + paragraphAttributes, + maximumNumberOfLines, ++ "maximumNumberOfLines"); ++ REBUILD_FIELD_SWITCH_CASE( ++ paDefaults, ++ value, ++ paragraphAttributes, ++ numberOfLines, + "numberOfLines"); + REBUILD_FIELD_SWITCH_CASE( + paDefaults, value, paragraphAttributes, ellipsizeMode, "ellipsizeMode"); +@@ -323,6 +333,7 @@ void AndroidTextInputProps::setProp( + } + + switch (hash) { ++ RAW_SET_PROP_SWITCH_CASE_BASIC(maximumNumberOfLines); + RAW_SET_PROP_SWITCH_CASE_BASIC(autoComplete); + RAW_SET_PROP_SWITCH_CASE_BASIC(returnKeyLabel); + RAW_SET_PROP_SWITCH_CASE_BASIC(numberOfLines); +@@ -422,6 +433,7 @@ void AndroidTextInputProps::setProp( + // TODO T53300085: support this in codegen; this was hand-written + folly::dynamic AndroidTextInputProps::getDynamic() const { + folly::dynamic props = folly::dynamic::object(); ++ props["maximumNumberOfLines"] = maximumNumberOfLines; + props["autoComplete"] = autoComplete; + props["returnKeyLabel"] = returnKeyLabel; + props["numberOfLines"] = numberOfLines; +diff --git a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h +index ba39ebb..ead28e3 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h ++++ b/node_modules/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h +@@ -84,6 +84,7 @@ class AndroidTextInputProps final : public ViewProps, public BaseTextProps { + std::string autoComplete{}; + std::string returnKeyLabel{}; + int numberOfLines{0}; ++ int maximumNumberOfLines{0}; + bool disableFullscreenUI{false}; + std::string textBreakStrategy{}; + SharedColor underlineColorAndroid{}; +diff --git a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +index 368c334..a1bb33e 100644 +--- a/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm ++++ b/node_modules/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTTextLayoutManager.mm +@@ -244,26 +244,51 @@ - (void)getRectWithAttributedString:(AttributedString)attributedString + + #pragma mark - Private + +-- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)attributedString +++- (NSTextStorage *)_textStorageForNSAttributesString:(NSAttributedString *)inputAttributedString + paragraphAttributes:(ParagraphAttributes)paragraphAttributes + size:(CGSize)size + { +- NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size]; ++ NSMutableAttributedString *attributedString = [ inputAttributedString mutableCopy]; ++ ++ /* ++ * The block below is responsible for setting the exact height of the view in lines ++ * Unfortunatelly, iOS doesn't export any easy way to do it. So we set maximumNumberOfLines ++ * prop and then add random lines at the front. However, they are only used for layout ++ * so they are not visible on the screen. This method is used for drawing only for Paragraph component ++ * but we set exact height in lines only on TextInput that doesn't use it. ++ */ ++ if (paragraphAttributes.numberOfLines) { ++ paragraphAttributes.maximumNumberOfLines = paragraphAttributes.numberOfLines; ++ NSMutableString *newLines = [NSMutableString stringWithCapacity: paragraphAttributes.numberOfLines]; ++ for (NSUInteger i = 0UL; i < paragraphAttributes.numberOfLines; ++i) { ++ // K is added on purpose. New line seems to be not enough for NTtextContainer ++ [newLines appendString:@"K\n"]; ++ } ++ NSDictionary * attributesOfFirstCharacter = [inputAttributedString attributesAtIndex:0 effectiveRange:NULL]; + +- textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. +- textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 +- ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) +- : NSLineBreakByClipping; +- textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; ++ [attributedString insertAttributedString:[[NSAttributedString alloc] initWithString:newLines attributes:attributesOfFirstCharacter] atIndex:0]; ++ } ++ ++ NSTextContainer *textContainer = [NSTextContainer new]; + + NSLayoutManager *layoutManager = [NSLayoutManager new]; + layoutManager.usesFontLeading = NO; + [layoutManager addTextContainer:textContainer]; + +- NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; ++ NSTextStorage *textStorage = [NSTextStorage new]; + + [textStorage addLayoutManager:layoutManager]; + ++ textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5. ++ textContainer.lineBreakMode = paragraphAttributes.maximumNumberOfLines > 0 ++ ? RCTNSLineBreakModeFromEllipsizeMode(paragraphAttributes.ellipsizeMode) ++ : NSLineBreakByClipping; ++ textContainer.size = size; ++ textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines; ++ ++ [textStorage replaceCharactersInRange:(NSRange){0, textStorage.length} withAttributedString:attributedString]; ++ ++ + if (paragraphAttributes.adjustsFontSizeToFit) { + CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0; + CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0; From 9027d7a54435a34b8f6b990990e8cb3c842193b3 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 28 Nov 2023 13:57:33 +0100 Subject: [PATCH 30/57] change styles --- src/styles/styles.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/styles/styles.ts b/src/styles/styles.ts index e26d79979cac..86ef7f951bc0 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -21,7 +21,7 @@ import pointerEventsAuto from './pointerEventsAuto'; import pointerEventsBoxNone from './pointerEventsBoxNone'; import pointerEventsNone from './pointerEventsNone'; import defaultTheme from './themes/default'; -import {StatusBarAndScrollbarTheme, ThemeColors} from './themes/types'; +import {ThemeColors} from './themes/types'; import borders from './utilities/borders'; import cursor from './utilities/cursor'; import display from './utilities/display'; @@ -40,6 +40,8 @@ import wordBreak from './utilities/wordBreak'; import writingDirection from './utilities/writingDirection'; import variables from './variables'; +type ColorScheme = 'light' | 'dark'; + type AnchorPosition = { horizontal: number; vertical: number; @@ -3980,11 +3982,11 @@ const styles = (theme: ThemeColors) => borderColor: theme.icon, }, - colorSchemeStyle: (colorScheme: StatusBarAndScrollbarTheme | undefined) => ({colorScheme}), + colorSchemeStyle: (colorScheme: ColorScheme) => ({colorScheme}), } satisfies Styles); const stylesGenerator = styles; const defaultStyles = styles(defaultTheme); export default defaultStyles; -export {stylesGenerator, type Styles}; +export {stylesGenerator, type Styles, type ColorScheme}; From 94c11e05c74dfca43829cde0b3ef5ea88eb02247 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 28 Nov 2023 14:05:22 +0100 Subject: [PATCH 31/57] update styles and CONST --- src/CONST.ts | 4 ++-- src/components/CustomStatusBar/index.tsx | 2 +- src/styles/styles.ts | 2 +- src/styles/themes/default.ts | 5 +++-- src/styles/themes/light.ts | 5 +++-- src/styles/themes/types.ts | 8 ++++---- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d596f9029b44..71670447e921 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -697,11 +697,11 @@ const CONST = { DARK: 'dark', SYSTEM: 'system', }, - STATUS_BAR_AND_SCROLLBAR_THEME: { + COLOR_SCHEME: { LIGHT: 'light', DARK: 'dark', }, - STATUS_BAR_THEME: { + STATUS_BAR_CONTENT_STYLE: { LIGHT_CONTENT: 'light-content', DARK_CONTENT: 'dark-content', }, diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index b9ff18a083c6..ddb33efd440c 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -13,7 +13,7 @@ function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactE const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarContext); const theme = useTheme(); const statusBarContentTheme = useMemo( - () => (theme.statusBarContentTheme === CONST.STATUS_BAR_AND_SCROLLBAR_THEME.LIGHT ? CONST.STATUS_BAR_THEME.LIGHT_CONTENT : CONST.STATUS_BAR_THEME.DARK_CONTENT), + () => (theme.statusBarContentTheme === CONST.COLOR_SCHEME.LIGHT ? CONST.STATUS_BAR_CONTENT_STYLE.LIGHT_CONTENT : CONST.STATUS_BAR_CONTENT_STYLE.DARK_CONTENT), [theme.statusBarContentTheme], ); diff --git a/src/styles/styles.ts b/src/styles/styles.ts index 86ef7f951bc0..cab10cae2345 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -40,7 +40,7 @@ import wordBreak from './utilities/wordBreak'; import writingDirection from './utilities/writingDirection'; import variables from './variables'; -type ColorScheme = 'light' | 'dark'; +type ColorScheme = (typeof CONST.COLOR_SCHEME)[keyof typeof CONST.COLOR_SCHEME]; type AnchorPosition = { horizontal: number; diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index ea98fdf37fa7..7f39dc034488 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -1,4 +1,5 @@ import colors from '@styles/colors'; +import CONST from '@src/CONST'; import SCREENS from '@src/SCREENS'; import {ThemeColors} from './types'; @@ -100,8 +101,8 @@ const darkTheme = { [SCREENS.SETTINGS.ROOT]: colors.darkHighlightBackground, }, - statusBarContentTheme: 'light', - scrollBarTheme: 'dark', + statusBarContentTheme: CONST.COLOR_SCHEME.LIGHT, + scrollBarTheme: CONST.COLOR_SCHEME.DARK, } satisfies ThemeColors; export default darkTheme; diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index 8640249d147c..b74c694980cb 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -1,4 +1,5 @@ import colors from '@styles/colors'; +import CONST from '@src/CONST'; import SCREENS from '@src/SCREENS'; import {ThemeColors} from './types'; @@ -100,8 +101,8 @@ const lightTheme = { [SCREENS.SETTINGS.ROOT]: colors.lightHighlightBackground, }, - statusBarContentTheme: 'dark', - scrollBarTheme: 'light', + statusBarContentTheme: CONST.COLOR_SCHEME.DARK, + scrollBarTheme: CONST.COLOR_SCHEME.LIGHT, } satisfies ThemeColors; export default lightTheme; diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index eecd4acecfab..93af9cab60a3 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -1,6 +1,6 @@ +import {ColorScheme} from '@styles/styles'; import CONST from '@src/CONST'; -type StatusBarAndScrollbarTheme = 'light' | 'dark'; type Color = string; type ThemePreference = (typeof CONST.THEME)[keyof typeof CONST.THEME]; @@ -94,8 +94,8 @@ type ThemeColors = { // Status bar and scroll bars need to adapt their theme based on the active user theme for good contrast // Therefore, we need to define specific themes for these elements // e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme - statusBarContentTheme: StatusBarAndScrollbarTheme; - scrollBarTheme: StatusBarAndScrollbarTheme; + statusBarContentTheme: ColorScheme; + scrollBarTheme: ColorScheme; }; -export {type ThemePreference, type ThemePreferenceWithoutSystem, type ThemeColors, type Color, type StatusBarAndScrollbarTheme}; +export {type ThemePreference, type ThemePreferenceWithoutSystem, type ThemeColors, type Color}; From 2ffb572ce986854a114d6c4bf251179c927f6ce7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 28 Nov 2023 14:54:47 +0100 Subject: [PATCH 32/57] make navigation container theme dynamic --- src/libs/Navigation/NavigationRoot.js | 69 +++++++-------------------- 1 file changed, 16 insertions(+), 53 deletions(-) diff --git a/src/libs/Navigation/NavigationRoot.js b/src/libs/Navigation/NavigationRoot.js index 2373066cf4bd..1a31f09fd0c8 100644 --- a/src/libs/Navigation/NavigationRoot.js +++ b/src/libs/Navigation/NavigationRoot.js @@ -1,26 +1,15 @@ import {DefaultTheme, getPathFromState, NavigationContainer} from '@react-navigation/native'; import PropTypes from 'prop-types'; -import React, {useEffect, useRef} from 'react'; -import {Easing, interpolateColor, runOnJS, useAnimatedReaction, useSharedValue, withDelay, withTiming} from 'react-native-reanimated'; +import React, {useEffect, useMemo, useRef} from 'react'; import useCurrentReportID from '@hooks/useCurrentReportID'; import useFlipper from '@hooks/useFlipper'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Log from '@libs/Log'; -import StatusBar from '@libs/StatusBar'; -import themeColors from '@styles/themes/default'; +import useTheme from '@styles/themes/useTheme'; import AppNavigator from './AppNavigator'; import linkingConfig from './linkingConfig'; import Navigation, {navigationRef} from './Navigation'; -// https://reactnavigation.org/docs/themes -const navigationTheme = { - ...DefaultTheme, - colors: { - ...DefaultTheme.colors, - background: themeColors.appBG, - }, -}; - const propTypes = { /** Whether the current user is logged in with an authToken */ authenticated: PropTypes.bool.isRequired, @@ -53,6 +42,7 @@ function parseAndLogRoute(state) { function NavigationRoot(props) { useFlipper(navigationRef); const firstRenderRef = useRef(true); + const theme = useTheme(); const {updateCurrentReportID} = useCurrentReportID(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -79,57 +69,30 @@ function NavigationRoot(props) { navigationRef.resetRoot(navigationRef.getRootState()); }, [isSmallScreenWidth, props.authenticated]); - const prevStatusBarBackgroundColor = useRef(themeColors.appBG); - const statusBarBackgroundColor = useRef(themeColors.appBG); - const statusBarAnimation = useSharedValue(0); - - const updateStatusBarBackgroundColor = (color) => StatusBar.setBackgroundColor(color); - useAnimatedReaction( - () => statusBarAnimation.value, - (current, previous) => { - // Do not run if either of the animated value is null - // or previous animated value is greater than or equal to the current one - if ([current, previous].includes(null) || current <= previous) { - return; - } - const color = interpolateColor(statusBarAnimation.value, [0, 1], [prevStatusBarBackgroundColor.current, statusBarBackgroundColor.current]); - runOnJS(updateStatusBarBackgroundColor)(color); - }, - ); - - const animateStatusBarBackgroundColor = () => { - const currentRoute = navigationRef.getCurrentRoute(); - const currentScreenBackgroundColor = (currentRoute.params && currentRoute.params.backgroundColor) || themeColors.PAGE_BACKGROUND_COLORS[currentRoute.name] || themeColors.appBG; - - prevStatusBarBackgroundColor.current = statusBarBackgroundColor.current; - statusBarBackgroundColor.current = currentScreenBackgroundColor; - - if (currentScreenBackgroundColor === themeColors.appBG && prevStatusBarBackgroundColor.current === themeColors.appBG) { - return; - } - - statusBarAnimation.value = 0; - statusBarAnimation.value = withDelay( - 300, - withTiming(1, { - duration: 300, - easing: Easing.in, - }), - ); - }; - const handleStateChange = (state) => { if (!state) { return; } + // Performance optimization to avoid context consumers to delay first render setTimeout(() => { updateCurrentReportID(state); }, 0); parseAndLogRoute(state); - animateStatusBarBackgroundColor(); }; + // https://reactnavigation.org/docs/themes + const navigationTheme = useMemo( + () => ({ + ...DefaultTheme, + colors: { + ...DefaultTheme.colors, + background: theme.appBG, + }, + }), + [theme.appBG], + ); + return ( Date: Tue, 28 Nov 2023 14:55:04 +0100 Subject: [PATCH 33/57] add status bar for android --- src/components/CustomStatusBar/index.android.tsx | 10 ---------- src/components/CustomStatusBar/index.tsx | 5 +++-- src/libs/StatusBar/index.android.ts | 14 +++++++++----- 3 files changed, 12 insertions(+), 17 deletions(-) delete mode 100644 src/components/CustomStatusBar/index.android.tsx diff --git a/src/components/CustomStatusBar/index.android.tsx b/src/components/CustomStatusBar/index.android.tsx deleted file mode 100644 index a7bf509114e6..000000000000 --- a/src/components/CustomStatusBar/index.android.tsx +++ /dev/null @@ -1,10 +0,0 @@ -/** - * On Android we setup the status bar in native code. - */ - -export default function CustomStatusBar() { - // Prefer to not render the StatusBar component in Android as it can cause - // issues with edge to edge display. We setup the status bar appearance in - // MainActivity.java and styles.xml. - return null; -} diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index ddb33efd440c..73dfa94dc84f 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -42,8 +42,9 @@ function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactE if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; } - StatusBar.setBarStyle(statusBarContentTheme, true); - StatusBar.setBackgroundColor(currentScreenBackgroundColor); + + StatusBar.setBarStyle(statusBarContentTheme, false); + StatusBar.setBackgroundColor(currentScreenBackgroundColor, false); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG]); diff --git a/src/libs/StatusBar/index.android.ts b/src/libs/StatusBar/index.android.ts index c928f0949665..6cbf1a762a0b 100644 --- a/src/libs/StatusBar/index.android.ts +++ b/src/libs/StatusBar/index.android.ts @@ -1,10 +1,14 @@ import StatusBar from './types'; -// Only has custom web implementation -StatusBar.getBackgroundColor = () => null; +const setBackgroundColor = StatusBar.setBackgroundColor; -// We override this because it's not used – on Android our app display edge-to-edge. -// Also because Reanimated's interpolateColor gives Android native colors instead of hex strings, causing this to display a warning. -StatusBar.setBackgroundColor = () => null; +let statusBarColor: string | null = null; + +StatusBar.getBackgroundColor = () => statusBarColor; + +StatusBar.setBackgroundColor = (color, animated = false) => { + statusBarColor = color as string; + setBackgroundColor(color, animated); +}; export default StatusBar; From 2eba4b72df75c202e3b46e6d7f69d3044da93c59 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 28 Nov 2023 15:10:52 +0100 Subject: [PATCH 34/57] simplify status bar and scrollbar components and add support for different color screens --- src/CONST.ts | 2 +- .../CustomScrollbarWrapperProps.ts | 7 --- .../CustomScrollbarWrapper/index.native.tsx | 4 +- .../CustomScrollbarWrapper/index.tsx | 14 ++---- src/components/CustomStatusBar/index.tsx | 22 ++++---- src/pages/TeachersUnite/SaveTheWorldPage.js | 2 +- src/pages/settings/InitialSettingsPage.js | 2 +- .../settings/Preferences/PreferencesPage.js | 2 +- .../Profile/CustomStatus/StatusPage.js | 2 +- .../settings/Security/SecuritySettingsPage.js | 2 +- .../Wallet/ActivatePhysicalCardPage.js | 2 +- src/pages/settings/Wallet/WalletEmptyState.js | 2 +- src/pages/signin/SignInPage.js | 2 +- src/pages/workspace/WorkspacesListPage.js | 2 +- src/styles/styles.ts | 3 +- src/styles/themes/default.ts | 50 +++++++++++++++---- src/styles/themes/light.ts | 50 +++++++++++++++---- src/styles/themes/types.ts | 8 +-- 18 files changed, 110 insertions(+), 68 deletions(-) delete mode 100644 src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts diff --git a/src/CONST.ts b/src/CONST.ts index 71670447e921..9652773a8d14 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -701,7 +701,7 @@ const CONST = { LIGHT: 'light', DARK: 'dark', }, - STATUS_BAR_CONTENT_STYLE: { + STATUS_BAR_STYLE: { LIGHT_CONTENT: 'light-content', DARK_CONTENT: 'dark-content', }, diff --git a/src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts b/src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts deleted file mode 100644 index ced7d72070c8..000000000000 --- a/src/components/CustomScrollbarWrapper/CustomScrollbarWrapperProps.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {ThemePreferenceWithoutSystem} from '@styles/themes/types'; - -type CustomScrollbarWrapperProps = React.PropsWithChildren & { - theme?: ThemePreferenceWithoutSystem; -}; - -export default CustomScrollbarWrapperProps; diff --git a/src/components/CustomScrollbarWrapper/index.native.tsx b/src/components/CustomScrollbarWrapper/index.native.tsx index c055bc5f8d1c..425bca4dd843 100644 --- a/src/components/CustomScrollbarWrapper/index.native.tsx +++ b/src/components/CustomScrollbarWrapper/index.native.tsx @@ -1,6 +1,4 @@ -import CustomScrollbarWrapperProps from './CustomScrollbarWrapperProps'; - -function CustomScrollbarWrapper({children}: CustomScrollbarWrapperProps) { +function CustomScrollbarWrapper({children}: React.PropsWithChildren) { return children; } diff --git a/src/components/CustomScrollbarWrapper/index.tsx b/src/components/CustomScrollbarWrapper/index.tsx index d8c97ad5b7a6..1c9c17d9064f 100644 --- a/src/components/CustomScrollbarWrapper/index.tsx +++ b/src/components/CustomScrollbarWrapper/index.tsx @@ -1,17 +1,13 @@ -import React, {useMemo} from 'react'; +import React from 'react'; import {View} from 'react-native'; -import Themes from '@styles/themes/Themes'; -import useThemePreferenceWithStaticOverride from '@styles/themes/useThemePreferenceWithStaticOverride'; +import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; -import CustomScrollbarWrapperProps from './CustomScrollbarWrapperProps'; -function CustomScrollbarWrapper({children, theme: staticThemePreference}: CustomScrollbarWrapperProps): React.ReactElement { +function CustomScrollbarWrapper({children}: React.PropsWithChildren): React.ReactElement { + const theme = useTheme(); const themeStyles = useThemeStyles(); - const preferredTheme = useThemePreferenceWithStaticOverride(staticThemePreference); - const scrollbarTheme = useMemo(() => Themes[preferredTheme].scrollBarTheme, [preferredTheme]); - - return {children}; + return {children}; } export default CustomScrollbarWrapper; diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index 73dfa94dc84f..cf2874a4782b 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -1,8 +1,7 @@ -import React, {useContext, useEffect, useMemo} from 'react'; +import React, {useContext, useEffect} from 'react'; import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; import useTheme from '@styles/themes/useTheme'; -import CONST from '@src/CONST'; import CustomStatusBarContext from './CustomStatusBarContext'; type CustomStatusBarProps = { @@ -12,10 +11,6 @@ type CustomStatusBarProps = { function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactElement | null { const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarContext); const theme = useTheme(); - const statusBarContentTheme = useMemo( - () => (theme.statusBarContentTheme === CONST.COLOR_SCHEME.LIGHT ? CONST.STATUS_BAR_CONTENT_STYLE.LIGHT_CONTENT : CONST.STATUS_BAR_CONTENT_STYLE.DARK_CONTENT), - [theme.statusBarContentTheme], - ); const isDisabled = !isNested && isRootStatusBarDisabled; @@ -39,19 +34,22 @@ function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactE // appBG color. const currentRoute = navigationRef.getCurrentRoute(); let currentScreenBackgroundColor = theme.appBG; - if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_BACKGROUND_COLORS) { - currentScreenBackgroundColor = theme.PAGE_BACKGROUND_COLORS[currentRoute.name]; + let statusBarStyle = theme.statusBarStyle; + if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { + const screenTheme = theme.PAGE_THEMES[currentRoute.name]; + currentScreenBackgroundColor = screenTheme.backgroundColor; + statusBarStyle = screenTheme.statusBarStyle; } - StatusBar.setBarStyle(statusBarContentTheme, false); + StatusBar.setBarStyle(statusBarStyle, false); StatusBar.setBackgroundColor(currentScreenBackgroundColor, false); }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [theme.PAGE_BACKGROUND_COLORS, theme.appBG]); + }, [theme.PAGE_THEMES, theme.appBG]); useEffect(() => { - StatusBar.setBarStyle(statusBarContentTheme, true); - }, [statusBarContentTheme]); + StatusBar.setBarStyle(theme.statusBarStyle, true); + }, [theme.statusBarStyle]); if (isDisabled) { return null; diff --git a/src/pages/TeachersUnite/SaveTheWorldPage.js b/src/pages/TeachersUnite/SaveTheWorldPage.js index 6d448b6b08b1..d179b7d1db95 100644 --- a/src/pages/TeachersUnite/SaveTheWorldPage.js +++ b/src/pages/TeachersUnite/SaveTheWorldPage.js @@ -38,7 +38,7 @@ function SaveTheWorldPage(props) { Navigation.goBack(ROUTES.HOME)} illustration={LottieAnimations.SaveTheWorld} > diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 1bd57bcab32b..77a4a458dac6 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -381,7 +381,7 @@ function InitialSettingsPage(props) { title={translate('common.settings')} headerContent={headerContent} headerContainerStyles={[styles.staticHeaderImage, styles.justifyContentCenter]} - backgroundColor={theme.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.ROOT]} + backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.ROOT].backgroundColor} > {getMenuItems} diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js index 4dbc5fda9198..b010c3790056 100755 --- a/src/pages/settings/Preferences/PreferencesPage.js +++ b/src/pages/settings/Preferences/PreferencesPage.js @@ -46,7 +46,7 @@ function PreferencesPage(props) { Navigation.goBack(ROUTES.SETTINGS)} - backgroundColor={theme.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.PREFERENCES]} + backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.PREFERENCES].backgroundColor} illustration={LottieAnimations.PreferencesDJ} > diff --git a/src/pages/settings/Profile/CustomStatus/StatusPage.js b/src/pages/settings/Profile/CustomStatus/StatusPage.js index 285a3072f859..6850684cfda2 100644 --- a/src/pages/settings/Profile/CustomStatus/StatusPage.js +++ b/src/pages/settings/Profile/CustomStatus/StatusPage.js @@ -91,7 +91,7 @@ function StatusPage({draftStatus, currentUserPersonalDetails}) { /> } headerContainerStyles={[styles.staticHeaderImage]} - backgroundColor={theme.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.STATUS]} + backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.STATUS].backgroundColor} footer={footerComponent} > diff --git a/src/pages/settings/Security/SecuritySettingsPage.js b/src/pages/settings/Security/SecuritySettingsPage.js index db2be7f5b681..4fbc0c58d6f5 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.js +++ b/src/pages/settings/Security/SecuritySettingsPage.js @@ -69,7 +69,7 @@ function SecuritySettingsPage(props) { onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS)} shouldShowBackButton illustration={LottieAnimations.Safe} - backgroundColor={theme.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.SECURITY]} + backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.SECURITY].backgroundColor} > diff --git a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js index e20721b5db4a..4a00fdcfe064 100644 --- a/src/pages/settings/Wallet/ActivatePhysicalCardPage.js +++ b/src/pages/settings/Wallet/ActivatePhysicalCardPage.js @@ -134,7 +134,7 @@ function ActivatePhysicalCardPage({ Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAINCARD.getRoute(domain))} - backgroundColor={theme.PAGE_BACKGROUND_COLORS[SCREENS.SETTINGS.PREFERENCES]} + backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.PREFERENCES].backgroundColor} illustration={LottieAnimations.Magician} scrollViewContainerStyles={[styles.mnh100]} childrenContainerStyles={[styles.flex1]} diff --git a/src/pages/settings/Wallet/WalletEmptyState.js b/src/pages/settings/Wallet/WalletEmptyState.js index 8eacd322d695..f2235dfafef2 100644 --- a/src/pages/settings/Wallet/WalletEmptyState.js +++ b/src/pages/settings/Wallet/WalletEmptyState.js @@ -36,7 +36,7 @@ function WalletEmptyState({onAddPaymentMethod}) { const {translate} = useLocalize(); return ( Navigation.goBack(ROUTES.SETTINGS)} title={translate('common.wallet')} diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 2e2ad64f08f4..333a4ebdcd55 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -281,7 +281,7 @@ function SignInPage(props) { return ( - + Navigation.goBack(ROUTES.SETTINGS)} title={translate('common.workspaces')} diff --git a/src/styles/styles.ts b/src/styles/styles.ts index cab10cae2345..aa4d33eff44a 100644 --- a/src/styles/styles.ts +++ b/src/styles/styles.ts @@ -41,6 +41,7 @@ import writingDirection from './utilities/writingDirection'; import variables from './variables'; type ColorScheme = (typeof CONST.COLOR_SCHEME)[keyof typeof CONST.COLOR_SCHEME]; +type StatusBarStyle = (typeof CONST.STATUS_BAR_STYLE)[keyof typeof CONST.STATUS_BAR_STYLE]; type AnchorPosition = { horizontal: number; @@ -3989,4 +3990,4 @@ const stylesGenerator = styles; const defaultStyles = styles(defaultTheme); export default defaultStyles; -export {stylesGenerator, type Styles, type ColorScheme}; +export {stylesGenerator, type Styles, type StatusBarStyle, type ColorScheme}; diff --git a/src/styles/themes/default.ts b/src/styles/themes/default.ts index 7f39dc034488..519e818054e0 100644 --- a/src/styles/themes/default.ts +++ b/src/styles/themes/default.ts @@ -90,19 +90,47 @@ const darkTheme = { // Note that it needs to be a screen name, not a route url. // The route urls from ROUTES.ts are only used for deep linking and configuring URLs on web. // The screen name (see SCREENS.ts) is the name of the screen as far as react-navigation is concerned, and the linkingConfig maps screen names to URLs - PAGE_BACKGROUND_COLORS: { - [SCREENS.HOME]: colors.darkHighlightBackground, - [SCREENS.SAVE_THE_WORLD.ROOT]: colors.tangerine800, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, - [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, - [SCREENS.SETTINGS.WALLET]: colors.darkAppBackground, - [SCREENS.SETTINGS.SECURITY]: colors.ice500, - [SCREENS.SETTINGS.STATUS]: colors.green700, - [SCREENS.SETTINGS.ROOT]: colors.darkHighlightBackground, + PAGE_THEMES: { + [SCREENS.HOME]: { + backgroundColor: colors.darkHighlightBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.REPORT]: { + backgroundColor: colors.darkAppBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SAVE_THE_WORLD.ROOT]: { + backgroundColor: colors.tangerine800, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.PREFERENCES]: { + backgroundColor: colors.blue500, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.WORKSPACES]: { + backgroundColor: colors.pink800, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.WALLET]: { + backgroundColor: colors.darkAppBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.SECURITY]: { + backgroundColor: colors.ice500, + statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, + }, + [SCREENS.SETTINGS.STATUS]: { + backgroundColor: colors.green700, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.ROOT]: { + backgroundColor: colors.darkHighlightBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, }, - statusBarContentTheme: CONST.COLOR_SCHEME.LIGHT, - scrollBarTheme: CONST.COLOR_SCHEME.DARK, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + colorScheme: CONST.COLOR_SCHEME.DARK, } satisfies ThemeColors; export default darkTheme; diff --git a/src/styles/themes/light.ts b/src/styles/themes/light.ts index b74c694980cb..2f6eda2f70dc 100644 --- a/src/styles/themes/light.ts +++ b/src/styles/themes/light.ts @@ -90,19 +90,47 @@ const lightTheme = { // Note that it needs to be a screen name, not a route url. // The route urls from ROUTES.ts are only used for deep linking and configuring URLs on web. // The screen name (see SCREENS.ts) is the name of the screen as far as react-navigation is concerned, and the linkingConfig maps screen names to URLs - PAGE_BACKGROUND_COLORS: { - [SCREENS.HOME]: colors.lightHighlightBackground, - [SCREENS.SAVE_THE_WORLD.ROOT]: colors.tangerine800, - [SCREENS.SETTINGS.PREFERENCES]: colors.blue500, - [SCREENS.SETTINGS.WORKSPACES]: colors.pink800, - [SCREENS.SETTINGS.WALLET]: colors.darkAppBackground, - [SCREENS.SETTINGS.SECURITY]: colors.ice500, - [SCREENS.SETTINGS.STATUS]: colors.green700, - [SCREENS.SETTINGS.ROOT]: colors.lightHighlightBackground, + PAGE_THEMES: { + [SCREENS.HOME]: { + backgroundColor: colors.lightHighlightBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, + }, + [SCREENS.REPORT]: { + backgroundColor: colors.lightAppBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, + }, + [SCREENS.SAVE_THE_WORLD.ROOT]: { + backgroundColor: colors.tangerine800, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.PREFERENCES]: { + backgroundColor: colors.blue500, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.WORKSPACES]: { + backgroundColor: colors.pink800, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.WALLET]: { + backgroundColor: colors.darkAppBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.SECURITY]: { + backgroundColor: colors.ice500, + statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, + }, + [SCREENS.SETTINGS.STATUS]: { + backgroundColor: colors.green700, + statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT, + }, + [SCREENS.SETTINGS.ROOT]: { + backgroundColor: colors.lightHighlightBackground, + statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, + }, }, - statusBarContentTheme: CONST.COLOR_SCHEME.DARK, - scrollBarTheme: CONST.COLOR_SCHEME.LIGHT, + statusBarStyle: CONST.STATUS_BAR_STYLE.DARK_CONTENT, + colorScheme: CONST.COLOR_SCHEME.LIGHT, } satisfies ThemeColors; export default lightTheme; diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index 93af9cab60a3..05306b4f7565 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -1,4 +1,4 @@ -import {ColorScheme} from '@styles/styles'; +import {ColorScheme, StatusBarStyle} from '@styles/styles'; import CONST from '@src/CONST'; type Color = string; @@ -89,13 +89,13 @@ type ThemeColors = { mapAttributionText: Color; white: Color; - PAGE_BACKGROUND_COLORS: Record; + PAGE_THEMES: Record; // Status bar and scroll bars need to adapt their theme based on the active user theme for good contrast // Therefore, we need to define specific themes for these elements // e.g. the StatusBar displays either "light-content" or "dark-content" based on the theme - statusBarContentTheme: ColorScheme; - scrollBarTheme: ColorScheme; + statusBarStyle: StatusBarStyle; + colorScheme: ColorScheme; }; export {type ThemePreference, type ThemePreferenceWithoutSystem, type ThemeColors, type Color}; From 4d833de34685d020b77a3cf9bffd9290e798b6cb Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 28 Nov 2023 15:16:34 +0100 Subject: [PATCH 35/57] improve code --- src/components/CustomStatusBar/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index cf2874a4782b..bfff277c7c75 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -34,18 +34,19 @@ function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactE // appBG color. const currentRoute = navigationRef.getCurrentRoute(); let currentScreenBackgroundColor = theme.appBG; - let statusBarStyle = theme.statusBarStyle; + let statusBarStyle; if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { const screenTheme = theme.PAGE_THEMES[currentRoute.name]; currentScreenBackgroundColor = screenTheme.backgroundColor; statusBarStyle = screenTheme.statusBarStyle; } - StatusBar.setBarStyle(statusBarStyle, false); StatusBar.setBackgroundColor(currentScreenBackgroundColor, false); + if (statusBarStyle != null) { + StatusBar.setBarStyle(statusBarStyle, false); + } }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [theme.PAGE_THEMES, theme.appBG]); + }, [isDisabled, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle]); useEffect(() => { StatusBar.setBarStyle(theme.statusBarStyle, true); From 941f4dc3f0b34a2834bf1be648dce9266a4d47ba Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 28 Nov 2023 15:29:19 +0100 Subject: [PATCH 36/57] implement route change listener --- src/components/CustomStatusBar/index.tsx | 52 ++++++++++++++---------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index bfff277c7c75..c06a26297289 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -1,5 +1,6 @@ -import React, {useContext, useEffect} from 'react'; -import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; +import {EventListenerCallback, NavigationContainerEventMap} from '@react-navigation/native'; +import React, {useCallback, useContext, useEffect} from 'react'; +import {navigationRef} from '@libs/Navigation/Navigation'; import StatusBar from '@libs/StatusBar'; import useTheme from '@styles/themes/useTheme'; import CustomStatusBarContext from './CustomStatusBarContext'; @@ -23,30 +24,37 @@ function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactE // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - useEffect(() => { + const navigationStateListener = useCallback>(() => { if (isDisabled) { return; } - Navigation.isNavigationReady().then(() => { - // Set the status bar colour depending on the current route. - // If we don't have any colour defined for a route, fall back to - // appBG color. - const currentRoute = navigationRef.getCurrentRoute(); - let currentScreenBackgroundColor = theme.appBG; - let statusBarStyle; - if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { - const screenTheme = theme.PAGE_THEMES[currentRoute.name]; - currentScreenBackgroundColor = screenTheme.backgroundColor; - statusBarStyle = screenTheme.statusBarStyle; - } - - StatusBar.setBackgroundColor(currentScreenBackgroundColor, false); - if (statusBarStyle != null) { - StatusBar.setBarStyle(statusBarStyle, false); - } - }); - }, [isDisabled, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle]); + // Set the status bar colour depending on the current route. + // If we don't have any colour defined for a route, fall back to + // appBG color. + const currentRoute = navigationRef.getCurrentRoute(); + + console.log({currentRoute}); + + let currentScreenBackgroundColor = theme.appBG; + let statusBarStyle; + if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { + const screenTheme = theme.PAGE_THEMES[currentRoute.name]; + currentScreenBackgroundColor = screenTheme.backgroundColor; + statusBarStyle = screenTheme.statusBarStyle; + } + + StatusBar.setBackgroundColor(currentScreenBackgroundColor, true); + if (statusBarStyle != null) { + StatusBar.setBarStyle(statusBarStyle, true); + } + }, [isDisabled, theme.PAGE_THEMES, theme.appBG]); + + useEffect(() => { + navigationRef.addListener('state', navigationStateListener); + + return () => navigationRef.removeListener('state', navigationStateListener); + }, [navigationStateListener]); useEffect(() => { StatusBar.setBarStyle(theme.statusBarStyle, true); From 497e98eef6276313de7abd6170d25a73fcda1f88 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Tue, 28 Nov 2023 16:53:47 +0100 Subject: [PATCH 37/57] remove log --- src/components/CustomStatusBar/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index c06a26297289..ac323e6adf56 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -34,8 +34,6 @@ function CustomStatusBar({isNested = false}: CustomStatusBarProps): React.ReactE // appBG color. const currentRoute = navigationRef.getCurrentRoute(); - console.log({currentRoute}); - let currentScreenBackgroundColor = theme.appBG; let statusBarStyle; if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { From 0feb15519163fe60b0da7bf7517efab3a3cc6d1d Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 29 Nov 2023 09:06:42 +0100 Subject: [PATCH 38/57] add file to .imgbotconfig --- .imgbotconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.imgbotconfig b/.imgbotconfig index ff5c3345cc4d..c60d296e668f 100644 --- a/.imgbotconfig +++ b/.imgbotconfig @@ -1,6 +1,7 @@ { "ignoredFiles": [ - "assets/images/empty-state_background-fade.png" // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 + "assets/images/empty-state_background-fade-dark.png" // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 + "assets/images/empty-state_background-fade-light.png" ], "aggressiveCompression": "false" } From cb28261a935549f0d4d4b1918bf785a3791db6c4 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Wed, 29 Nov 2023 09:31:57 +0100 Subject: [PATCH 39/57] fix: lint --- .imgbotconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.imgbotconfig b/.imgbotconfig index c60d296e668f..43d1b77166cc 100644 --- a/.imgbotconfig +++ b/.imgbotconfig @@ -1,6 +1,6 @@ { "ignoredFiles": [ - "assets/images/empty-state_background-fade-dark.png" // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 + "assets/images/empty-state_background-fade-dark.png", // Caused an issue with colour gradients, https://github.com/Expensify/App/issues/30499 "assets/images/empty-state_background-fade-light.png" ], "aggressiveCompression": "false" From 915c38e1478840fca33f16f606f6a237386e2fa3 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 10:58:32 +0100 Subject: [PATCH 40/57] rename scrollbar wrapper --- src/App.js | 6 +++--- src/components/ColorSchemeWrapper/index.native.tsx | 5 +++++ .../index.tsx | 4 ++-- src/components/CustomScrollbarWrapper/index.native.tsx | 5 ----- src/pages/signin/SignInPage.js | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 src/components/ColorSchemeWrapper/index.native.tsx rename src/components/{CustomScrollbarWrapper => ColorSchemeWrapper}/index.tsx (73%) delete mode 100644 src/components/CustomScrollbarWrapper/index.native.tsx diff --git a/src/App.js b/src/App.js index f3f342d3df09..2caa6b9ffc29 100644 --- a/src/App.js +++ b/src/App.js @@ -6,8 +6,8 @@ import Onyx from 'react-native-onyx'; import {PickerStateProvider} from 'react-native-picker-select'; import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; +import ColorSchemeWrapper from './components/ColorSchemeWrapper'; import ComposeProviders from './components/ComposeProviders'; -import CustomScrollbarWrapper from './components/CustomScrollbarWrapper'; import CustomStatusBar from './components/CustomStatusBar'; import CustomStatusBarContextProvider from './components/CustomStatusBar/CustomStatusBarContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; @@ -73,9 +73,9 @@ function App() { > - + - + diff --git a/src/components/ColorSchemeWrapper/index.native.tsx b/src/components/ColorSchemeWrapper/index.native.tsx new file mode 100644 index 000000000000..9f68ee97d330 --- /dev/null +++ b/src/components/ColorSchemeWrapper/index.native.tsx @@ -0,0 +1,5 @@ +function ColorSchemeWrapper({children}: React.PropsWithChildren) { + return children; +} + +export default ColorSchemeWrapper; diff --git a/src/components/CustomScrollbarWrapper/index.tsx b/src/components/ColorSchemeWrapper/index.tsx similarity index 73% rename from src/components/CustomScrollbarWrapper/index.tsx rename to src/components/ColorSchemeWrapper/index.tsx index 1c9c17d9064f..577ccf9f3794 100644 --- a/src/components/CustomScrollbarWrapper/index.tsx +++ b/src/components/ColorSchemeWrapper/index.tsx @@ -3,11 +3,11 @@ import {View} from 'react-native'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; -function CustomScrollbarWrapper({children}: React.PropsWithChildren): React.ReactElement { +function ColorSchemeWrapper({children}: React.PropsWithChildren): React.ReactElement { const theme = useTheme(); const themeStyles = useThemeStyles(); return {children}; } -export default CustomScrollbarWrapper; +export default ColorSchemeWrapper; diff --git a/src/components/CustomScrollbarWrapper/index.native.tsx b/src/components/CustomScrollbarWrapper/index.native.tsx deleted file mode 100644 index 425bca4dd843..000000000000 --- a/src/components/CustomScrollbarWrapper/index.native.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function CustomScrollbarWrapper({children}: React.PropsWithChildren) { - return children; -} - -export default CustomScrollbarWrapper; diff --git a/src/pages/signin/SignInPage.js b/src/pages/signin/SignInPage.js index 333a4ebdcd55..e7499e2aef01 100644 --- a/src/pages/signin/SignInPage.js +++ b/src/pages/signin/SignInPage.js @@ -5,7 +5,7 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import _ from 'underscore'; -import CustomScrollbarWrapper from '@components/CustomScrollbarWrapper'; +import ColorSchemeWrapper from '@components/ColorSchemeWrapper'; import CustomStatusBar from '@components/CustomStatusBar'; import useLocalize from '@hooks/useLocalize'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -281,13 +281,13 @@ function SignInPage(props) { return ( - + - + ); From bf2fd7205d4fcd48df4e2dba004a878388b95480 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 11:59:05 +0100 Subject: [PATCH 41/57] fix: CustomStatusBar component --- src/components/CustomStatusBar/index.tsx | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index 11745aae7c77..68ebf9a59437 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -32,9 +32,13 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { disableRootStatusBar(true); } - return () => disableRootStatusBar(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + return () => { + if (!isNested) { + return; + } + disableRootStatusBar(false); + }; + }, [disableRootStatusBar, isNested]); const navigationStateListener = useCallback>(() => { if (isDisabled) { @@ -47,7 +51,7 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { const currentRoute = navigationRef.getCurrentRoute(); let currentScreenBackgroundColor = theme.appBG; - let statusBarStyle; + let statusBarStyle = theme.statusBarStyle; if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { const screenTheme = theme.PAGE_THEMES[currentRoute.name]; currentScreenBackgroundColor = screenTheme.backgroundColor; @@ -55,10 +59,8 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { } StatusBar.setBackgroundColor(currentScreenBackgroundColor, true); - if (statusBarStyle != null) { - StatusBar.setBarStyle(statusBarStyle, true); - } - }, [isDisabled, theme.PAGE_THEMES, theme.appBG]); + StatusBar.setBarStyle(statusBarStyle, true); + }, [isDisabled, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle]); useEffect(() => { navigationRef.addListener('state', navigationStateListener); @@ -67,8 +69,12 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { }, [navigationStateListener]); useEffect(() => { + if (isDisabled) { + return; + } + StatusBar.setBarStyle(theme.statusBarStyle, true); - }, [theme.statusBarStyle]); + }, [isDisabled, theme.statusBarStyle]); if (isDisabled) { return null; From 331b4182f09be2f701e768f1f3e995e6165a21f7 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 12:15:42 +0100 Subject: [PATCH 42/57] add comment --- src/components/CustomStatusBar/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index 68ebf9a59437..888aad7ec275 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -11,6 +11,8 @@ type CustomStatusBarProps = { }; const propTypes = { + /** Whether the CustomStatusBar is nested within another CustomStatusBar. + * A nested CustomStatusBar will disable the "root" CustomStatusBar. */ isNested: PropTypes.bool, }; From 268f6839ff84b00a6759549bfc4526930c720849 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 14:40:50 +0100 Subject: [PATCH 43/57] Update src/components/CustomStatusBar/index.tsx Co-authored-by: Fedi Rajhi --- src/components/CustomStatusBar/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index 888aad7ec275..b9d47cacd65a 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -42,7 +42,7 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { }; }, [disableRootStatusBar, isNested]); - const navigationStateListener = useCallback>(() => { + const updateStatusBarStyle = useCallback>(() => { if (isDisabled) { return; } From 3ca7fc7e1a25a9db245871cbc8313b4c14e097e6 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 30 Nov 2023 12:35:55 +0000 Subject: [PATCH 44/57] Update version to 1.4.6-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 549ee7fd79d5..4a32609ce517 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001040507 - versionName "1.4.5-7" + versionCode 1001040600 + versionName "1.4.6-0" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 22c893c828aa..c4105db05e68 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.5.7 + 1.4.6.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 35deae453a67..d53f0db8cde9 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.5 + 1.4.6 CFBundleSignature ???? CFBundleVersion - 1.4.5.7 + 1.4.6.0 diff --git a/package-lock.json b/package-lock.json index abd1110e5cce..e4eadbab7afa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.5-7", + "version": "1.4.6-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.5-7", + "version": "1.4.6-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index b8173bd8ba1b..4cd5d52ae2b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.5-7", + "version": "1.4.6-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From cde5ba88f122b482f4d9d6f843b2b963d6cd1dcd Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 30 Nov 2023 10:57:25 +0100 Subject: [PATCH 45/57] fix: amend missed imports --- src/components/Button/index.tsx | 7 +++---- src/components/Icon/index.tsx | 14 ++++++++------ src/components/MapView/MapView.tsx | 3 ++- src/components/MapView/MapView.web.tsx | 9 ++++++--- src/components/ShowMoreButton/index.js | 8 +++++--- src/components/withTheme.tsx | 2 +- src/pages/home/report/ReportActionItem.js | 8 +++++--- src/pages/home/report/ReportActionItemSingle.js | 7 ++++--- 8 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 71bce9777174..8d5833a121ca 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -9,7 +9,6 @@ import Text from '@components/Text'; import withNavigationFallback from '@components/withNavigationFallback'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import HapticFeedback from '@libs/HapticFeedback'; -import themeColors from '@styles/themes/default'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; @@ -118,7 +117,7 @@ function Button( allowBubble = false, iconRight = Expensicons.ArrowRight, - iconFill = themeColors.textLight, + iconFill, iconStyles = [], iconRightStyles = [], @@ -214,7 +213,7 @@ function Button( @@ -225,7 +224,7 @@ function Button( diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 82a5045b7ad4..1c21d1ae9426 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -1,8 +1,9 @@ import React, {PureComponent} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import withThemeStyles, {ThemeStylesProps} from '@components/withThemeStyles'; +import withTheme, {ThemeProps} from "@components/withTheme"; +import compose from '@libs/compose'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; import variables from '@styles/variables'; import IconWrapperStyles from './IconWrapperStyles'; @@ -41,7 +42,7 @@ type IconProps = { /** Additional styles to add to the Icon */ additionalStyles?: StyleProp; -} & ThemeStylesProps; +} & ThemeStylesProps & ThemeProps; // We must use a class component to create an animatable component with the Animated API // eslint-disable-next-line react/prefer-stateless-function @@ -50,7 +51,7 @@ class Icon extends PureComponent { public static defaultProps = { width: variables.iconSizeNormal, height: variables.iconSizeNormal, - fill: themeColors.icon, + fill: undefined, small: false, inline: false, additionalStyles: [], @@ -62,6 +63,7 @@ class Icon extends PureComponent { const width = this.props.small ? variables.iconSizeSmall : this.props.width; const height = this.props.small ? variables.iconSizeSmall : this.props.height; const iconStyles = [StyleUtils.getWidthAndHeightStyle(width ?? 0, height), IconWrapperStyles, this.props.themeStyles.pAbsolute, this.props.additionalStyles]; + const fill = this.props.fill ?? this.props.theme.icon; if (this.props.inline) { return ( @@ -73,7 +75,7 @@ class Icon extends PureComponent { @@ -90,7 +92,7 @@ class Icon extends PureComponent { @@ -99,4 +101,4 @@ class Icon extends PureComponent { } } -export default withThemeStyles(Icon); +export default compose(withTheme, withThemeStyles)(Icon); diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index db3e076eacca..eaafd7fc9f7e 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -6,11 +6,11 @@ import {withOnyx} from 'react-native-onyx'; import setUserLocation from '@libs/actions/UserLocation'; import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; -import styles from '@styles/styles'; import CONST from '@src/CONST'; import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; import ONYXKEYS from '@src/ONYXKEYS'; +import useThemeStyles from "@styles/useThemeStyles"; import Direction from './Direction'; import {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; @@ -23,6 +23,7 @@ const MapView = forwardRef( const navigation = useNavigation(); const {isOffline} = useNetwork(); const {translate} = useLocalize(); + const styles = useThemeStyles(); const cameraRef = useRef(null); const [isIdle, setIsIdle] = useState(false); diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx index 1880049b3542..87ec8a25093e 100644 --- a/src/components/MapView/MapView.web.tsx +++ b/src/components/MapView/MapView.web.tsx @@ -10,14 +10,14 @@ import Map, {MapRef, Marker} from 'react-map-gl'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; import setUserLocation from '@userActions/UserLocation'; import CONST from '@src/CONST'; import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; import getCurrentPosition from '@src/libs/getCurrentPosition'; import ONYXKEYS from '@src/ONYXKEYS'; -import styles from '@src/styles/styles'; +import useTheme from "@styles/themes/useTheme"; +import useThemeStyles from "@styles/useThemeStyles"; import Direction from './Direction'; import './mapbox.css'; import {MapViewHandle} from './MapViewTypes'; @@ -43,6 +43,9 @@ const MapView = forwardRef( const {isOffline} = useNetwork(); const {translate} = useLocalize(); + const theme = useTheme(); + const styles = useThemeStyles(); + const [mapRef, setMapRef] = useState(null); const [currentPosition, setCurrentPosition] = useState(cachedUserLocation); const [userInteractedWithMap, setUserInteractedWithMap] = useState(false); @@ -179,7 +182,7 @@ const MapView = forwardRef( latitude: currentPosition?.latitude, zoom: initialState.zoom, }} - style={StyleUtils.getTextColorStyle(themeColors.mapAttributionText) as React.CSSProperties} + style={StyleUtils.getTextColorStyle(theme.mapAttributionText) as React.CSSProperties} mapStyle={styleURL} > {waypoints?.map(({coordinate, markerComponent, id}) => { diff --git a/src/components/ShowMoreButton/index.js b/src/components/ShowMoreButton/index.js index f983a468cc1c..b47b8bed1932 100644 --- a/src/components/ShowMoreButton/index.js +++ b/src/components/ShowMoreButton/index.js @@ -7,8 +7,8 @@ import * as Expensicons from '@components/Icon/Expensicons'; import useLocalize from '@hooks/useLocalize'; import * as NumberFormatUtils from '@libs/NumberFormatUtils'; import stylePropTypes from '@styles/stylePropTypes'; -import styles from '@styles/styles'; -import themeColors from '@styles/themes/default'; +import useTheme from "@styles/themes/useTheme"; +import useThemeStyles from "@styles/useThemeStyles"; const propTypes = { /** Additional styles for container */ @@ -32,6 +32,8 @@ const defaultProps = { function ShowMoreButton({containerStyle, currentCount, totalCount, onPress}) { const {translate, preferredLocale} = useLocalize(); + const theme = useTheme(); + const styles = useThemeStyles(); const shouldShowCounter = _.isNumber(currentCount) && _.isNumber(totalCount); @@ -51,7 +53,7 @@ function ShowMoreButton({containerStyle, currentCount, totalCount, onPress}) { style={styles.mh0} small shouldShowRightIcon - iconFill={themeColors.icon} + iconFill={theme.icon} iconRight={Expensicons.DownArrow} text={translate('common.showMore')} accessibilityLabel={translate('common.showMore')} diff --git a/src/components/withTheme.tsx b/src/components/withTheme.tsx index d78742b7036b..451292f1a66f 100644 --- a/src/components/withTheme.tsx +++ b/src/components/withTheme.tsx @@ -29,4 +29,4 @@ export default function withTheme( return forwardRef(WithTheme); } -export {withThemePropTypes}; +export {withThemePropTypes, type ThemeProps}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 828a793a8565..5ce7683fb6bf 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -45,9 +45,7 @@ import SelectionScraper from '@libs/SelectionScraper'; import userWalletPropTypes from '@pages/EnablePayments/userWalletPropTypes'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import reportPropTypes from '@pages/reportPropTypes'; -import styles from '@styles/styles'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; import * as BankAccounts from '@userActions/BankAccounts'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as store from '@userActions/ReimbursementAccount/store'; @@ -58,6 +56,8 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import useThemeStyles from "@styles/useThemeStyles"; +import useTheme from "@styles/themes/useTheme"; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; @@ -142,13 +142,15 @@ function ReportActionItem(props) { const {updateHiddenAttachments} = useContext(ReportAttachmentsContext); const textInputRef = useRef(); const popoverAnchorRef = useRef(); + const styles = useThemeStyles(); + const theme = useTheme(); const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(props.draftMessage); const originalReportID = ReportUtils.getOriginalReportID(props.report.reportID, props.action); const originalReport = props.report.reportID === originalReportID ? props.report : ReportUtils.getReport(originalReportID); const isReportActionLinked = props.linkedReportActionID === props.action.reportActionID; - const highlightedBackgroundColorIfNeeded = useMemo(() => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(themeColors.highlightBG) : {}), [isReportActionLinked]); + const highlightedBackgroundColorIfNeeded = useMemo(() => (isReportActionLinked ? StyleUtils.getBackgroundColorStyle(theme.highlightBG) : {}), [isReportActionLinked, theme.highlightBG]); const originalMessage = lodashGet(props.action, 'originalMessage', {}); // IOUDetails only exists when we are sending money diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 9c641d879de3..911a5e0aa69a 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -21,8 +21,8 @@ import * as UserUtils from '@libs/UserUtils'; import reportPropTypes from '@pages/reportPropTypes'; import stylePropTypes from '@styles/stylePropTypes'; import * as StyleUtils from '@styles/StyleUtils'; -import themeColors from '@styles/themes/default'; import useThemeStyles from '@styles/useThemeStyles'; +import useTheme from "@styles/themes/useTheme"; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import ReportActionItemDate from './ReportActionItemDate'; @@ -80,6 +80,7 @@ const showWorkspaceDetails = (reportID) => { function ReportActionItemSingle(props) { const styles = useThemeStyles(); + const theme = useTheme(); const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT; const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID; let displayName = ReportUtils.getDisplayNameForParticipant(actorAccountID); @@ -167,8 +168,8 @@ function ReportActionItemSingle(props) { isInReportAction shouldShowTooltip secondAvatarStyle={[ - StyleUtils.getBackgroundAndBorderStyle(themeColors.appBG), - props.isHovered ? StyleUtils.getBackgroundAndBorderStyle(themeColors.highlightBG) : undefined, + StyleUtils.getBackgroundAndBorderStyle(theme.appBG), + props.isHovered ? StyleUtils.getBackgroundAndBorderStyle(theme.highlightBG) : undefined, ]} /> ); From 20d54db74d8abf3eb1d69594cf2be2b1080bfc91 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 30 Nov 2023 11:05:28 +0100 Subject: [PATCH 46/57] fix: run lint --- src/components/Icon/index.tsx | 5 +++-- src/components/MapView/MapView.tsx | 2 +- src/components/MapView/MapView.web.tsx | 4 ++-- src/components/ShowMoreButton/index.js | 4 ++-- src/pages/home/report/ReportActionItem.js | 4 ++-- src/pages/home/report/ReportActionItemSingle.js | 7 ++----- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 1c21d1ae9426..7b3249798046 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -1,7 +1,7 @@ import React, {PureComponent} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; +import withTheme, {ThemeProps} from '@components/withTheme'; import withThemeStyles, {ThemeStylesProps} from '@components/withThemeStyles'; -import withTheme, {ThemeProps} from "@components/withTheme"; import compose from '@libs/compose'; import * as StyleUtils from '@styles/StyleUtils'; import variables from '@styles/variables'; @@ -42,7 +42,8 @@ type IconProps = { /** Additional styles to add to the Icon */ additionalStyles?: StyleProp; -} & ThemeStylesProps & ThemeProps; +} & ThemeStylesProps & + ThemeProps; // We must use a class component to create an animatable component with the Animated API // eslint-disable-next-line react/prefer-stateless-function diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index eaafd7fc9f7e..91f9d9930079 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -6,11 +6,11 @@ import {withOnyx} from 'react-native-onyx'; import setUserLocation from '@libs/actions/UserLocation'; import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; +import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; import ONYXKEYS from '@src/ONYXKEYS'; -import useThemeStyles from "@styles/useThemeStyles"; import Direction from './Direction'; import {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; diff --git a/src/components/MapView/MapView.web.tsx b/src/components/MapView/MapView.web.tsx index 87ec8a25093e..f32413cbc15d 100644 --- a/src/components/MapView/MapView.web.tsx +++ b/src/components/MapView/MapView.web.tsx @@ -10,14 +10,14 @@ import Map, {MapRef, Marker} from 'react-map-gl'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import * as StyleUtils from '@styles/StyleUtils'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import setUserLocation from '@userActions/UserLocation'; import CONST from '@src/CONST'; import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; import getCurrentPosition from '@src/libs/getCurrentPosition'; import ONYXKEYS from '@src/ONYXKEYS'; -import useTheme from "@styles/themes/useTheme"; -import useThemeStyles from "@styles/useThemeStyles"; import Direction from './Direction'; import './mapbox.css'; import {MapViewHandle} from './MapViewTypes'; diff --git a/src/components/ShowMoreButton/index.js b/src/components/ShowMoreButton/index.js index b47b8bed1932..5f1620ac7c88 100644 --- a/src/components/ShowMoreButton/index.js +++ b/src/components/ShowMoreButton/index.js @@ -7,8 +7,8 @@ import * as Expensicons from '@components/Icon/Expensicons'; import useLocalize from '@hooks/useLocalize'; import * as NumberFormatUtils from '@libs/NumberFormatUtils'; import stylePropTypes from '@styles/stylePropTypes'; -import useTheme from "@styles/themes/useTheme"; -import useThemeStyles from "@styles/useThemeStyles"; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** Additional styles for container */ diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 5ce7683fb6bf..ae07503b6e9c 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -46,6 +46,8 @@ import userWalletPropTypes from '@pages/EnablePayments/userWalletPropTypes'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import reportPropTypes from '@pages/reportPropTypes'; import * as StyleUtils from '@styles/StyleUtils'; +import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from '@styles/useThemeStyles'; import * as BankAccounts from '@userActions/BankAccounts'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as store from '@userActions/ReimbursementAccount/store'; @@ -56,8 +58,6 @@ import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import useThemeStyles from "@styles/useThemeStyles"; -import useTheme from "@styles/themes/useTheme"; import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground'; import * as ContextMenuActions from './ContextMenu/ContextMenuActions'; import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu'; diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 911a5e0aa69a..c50f868cae99 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -21,8 +21,8 @@ import * as UserUtils from '@libs/UserUtils'; import reportPropTypes from '@pages/reportPropTypes'; import stylePropTypes from '@styles/stylePropTypes'; import * as StyleUtils from '@styles/StyleUtils'; +import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; -import useTheme from "@styles/themes/useTheme"; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import ReportActionItemDate from './ReportActionItemDate'; @@ -167,10 +167,7 @@ function ReportActionItemSingle(props) { icons={[icon, secondaryAvatar]} isInReportAction shouldShowTooltip - secondAvatarStyle={[ - StyleUtils.getBackgroundAndBorderStyle(theme.appBG), - props.isHovered ? StyleUtils.getBackgroundAndBorderStyle(theme.highlightBG) : undefined, - ]} + secondAvatarStyle={[StyleUtils.getBackgroundAndBorderStyle(theme.appBG), props.isHovered ? StyleUtils.getBackgroundAndBorderStyle(theme.highlightBG) : undefined]} /> ); } From b343ef17df6ce8993800edf088f594bf7d51afa0 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Thu, 30 Nov 2023 11:54:06 +0100 Subject: [PATCH 47/57] fix: export fix --- src/components/Icon/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 7b3249798046..98449c838b67 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -2,7 +2,6 @@ import React, {PureComponent} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import withTheme, {ThemeProps} from '@components/withTheme'; import withThemeStyles, {ThemeStylesProps} from '@components/withThemeStyles'; -import compose from '@libs/compose'; import * as StyleUtils from '@styles/StyleUtils'; import variables from '@styles/variables'; import IconWrapperStyles from './IconWrapperStyles'; @@ -102,4 +101,4 @@ class Icon extends PureComponent { } } -export default compose(withTheme, withThemeStyles)(Icon); +export default withTheme(withThemeStyles(Icon)); From 198e4ec7ab7ef4b1ad69e87c23cd7a7267753c73 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 30 Nov 2023 11:37:17 +0100 Subject: [PATCH 48/57] migrate getModalStyles --- src/components/Modal/BaseModal.tsx | 3 ++- src/components/PopoverWithoutOverlay/index.js | 3 +++ src/styles/getModalStyles.ts | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 95a7f3adc279..4945b4b62ad6 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -139,11 +139,12 @@ function BaseModal( windowHeight, isSmallScreenWidth, }, + theme, popoverAnchorPosition, innerContainerStyle, outerStyle, ), - [innerContainerStyle, isSmallScreenWidth, outerStyle, popoverAnchorPosition, type, windowHeight, windowWidth], + [innerContainerStyle, isSmallScreenWidth, outerStyle, popoverAnchorPosition, theme, type, windowHeight, windowWidth], ); const { diff --git a/src/components/PopoverWithoutOverlay/index.js b/src/components/PopoverWithoutOverlay/index.js index 8b9dd4ac7a61..2a5d9265144a 100644 --- a/src/components/PopoverWithoutOverlay/index.js +++ b/src/components/PopoverWithoutOverlay/index.js @@ -6,10 +6,12 @@ import {PopoverContext} from '@components/PopoverProvider'; import withWindowDimensions from '@components/withWindowDimensions'; import getModalStyles from '@styles/getModalStyles'; import * as StyleUtils from '@styles/StyleUtils'; +import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import * as Modal from '@userActions/Modal'; function Popover(props) { + const theme = useTheme(); const styles = useThemeStyles(); const {onOpen, close} = React.useContext(PopoverContext); const {modalStyle, modalContainerStyle, shouldAddTopSafeAreaMargin, shouldAddBottomSafeAreaMargin, shouldAddTopSafeAreaPadding, shouldAddBottomSafeAreaPadding} = getModalStyles( @@ -19,6 +21,7 @@ function Popover(props) { windowHeight: props.windowHeight, isSmallScreenWidth: false, }, + theme, props.anchorPosition, props.innerContainerStyle, props.outerStyle, diff --git a/src/styles/getModalStyles.ts b/src/styles/getModalStyles.ts index c250bdf9498d..6402a16a97eb 100644 --- a/src/styles/getModalStyles.ts +++ b/src/styles/getModalStyles.ts @@ -3,7 +3,7 @@ import {ModalProps} from 'react-native-modal'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import styles from './styles'; -import themeColors from './themes/default'; +import {ThemeColors} from './themes/types'; import variables from './variables'; function getCenteredModalStyles(windowWidth: number, isSmallScreenWidth: boolean, isFullScreenWhenSmall = false): ViewStyle { @@ -39,6 +39,7 @@ type GetModalStyles = { export default function getModalStyles( type: ModalType | undefined, windowDimensions: WindowDimensions, + themeColors: ThemeColors, popoverAnchorPosition: ViewStyle = {}, innerContainerStyle: ViewStyle = {}, outerStyle: ViewStyle = {}, From 9bc522f445a245dfa8fa2c53ddd838a5c9a0619c Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 30 Nov 2023 11:46:04 +0100 Subject: [PATCH 49/57] migrate getReportActionContextMenuStyles --- .../BaseReportActionContextMenu.js | 4 ++- .../getReportActionContextMenuStyles.ts | 26 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js index 18351f86a400..4044ea2396db 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js @@ -13,6 +13,7 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useNetwork from '@hooks/useNetwork'; import compose from '@libs/compose'; import getReportActionContextMenuStyles from '@styles/getReportActionContextMenuStyles'; +import useTheme from '@styles/themes/useTheme'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -49,9 +50,10 @@ const defaultProps = { ...GenericReportActionContextMenuDefaultProps, }; function BaseReportActionContextMenu(props) { + const theme = useTheme(); const menuItemRefs = useRef({}); const [shouldKeepOpen, setShouldKeepOpen] = useState(false); - const wrapperStyle = getReportActionContextMenuStyles(props.isMini, props.isSmallScreenWidth); + const wrapperStyle = getReportActionContextMenuStyles(props.isMini, props.isSmallScreenWidth, theme); const {isOffline} = useNetwork(); const reportAction = useMemo(() => { diff --git a/src/styles/getReportActionContextMenuStyles.ts b/src/styles/getReportActionContextMenuStyles.ts index cd3843169a43..e26f8de94919 100644 --- a/src/styles/getReportActionContextMenuStyles.ts +++ b/src/styles/getReportActionContextMenuStyles.ts @@ -1,42 +1,40 @@ import {ViewStyle} from 'react-native'; import styles from './styles'; -import themeColors from './themes/default'; +import {ThemeColors} from './themes/types'; import variables from './variables'; -const defaultWrapperStyle: ViewStyle = { - backgroundColor: themeColors.componentBG, -}; +const getDefaultWrapperStyle = (theme: ThemeColors): ViewStyle => ({ + backgroundColor: theme.componentBG, +}); -const miniWrapperStyle: ViewStyle[] = [ +const getMiniWrapperStyle = (theme: ThemeColors): ViewStyle[] => [ styles.flexRow, - defaultWrapperStyle, + getDefaultWrapperStyle(theme), { borderRadius: variables.buttonBorderRadius, borderWidth: 1, - borderColor: themeColors.border, + borderColor: theme.border, // In Safari, when welcome messages use a code block (triple backticks), they would overlap the context menu below when there is no scrollbar without the transform style. // NOTE: asserting "transform" to a valid type, because it isn't possible to augment "transform". transform: 'translateZ(0)' as unknown as ViewStyle['transform'], }, ]; -const bigWrapperStyle: ViewStyle[] = [styles.flexColumn, defaultWrapperStyle]; - /** * Generate the wrapper styles for the ReportActionContextMenu. * * @param isMini * @param isSmallScreenWidth + * @param theme */ -function getReportActionContextMenuStyles(isMini: boolean, isSmallScreenWidth: boolean): ViewStyle[] { +function getReportActionContextMenuStyles(isMini: boolean, isSmallScreenWidth: boolean, theme: ThemeColors): ViewStyle[] { if (isMini) { - return miniWrapperStyle; + return getMiniWrapperStyle(theme); } - // TODO: Remove this "eslint-disable-next" once the theme switching migration is done and styles are fully typed (GH Issue: https://github.com/Expensify/App/issues/27337) - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return [ - ...bigWrapperStyle, + styles.flexColumn, + getDefaultWrapperStyle(theme), // Small screens use a bottom-docked modal that already has vertical padding. isSmallScreenWidth ? {} : styles.pv3, From 97d00819f8e4667cf8aeb3e8f761288c6be1d552 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Thu, 30 Nov 2023 11:46:30 +0100 Subject: [PATCH 50/57] rename argument --- src/styles/getModalStyles.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/getModalStyles.ts b/src/styles/getModalStyles.ts index 6402a16a97eb..eb87cc005f8b 100644 --- a/src/styles/getModalStyles.ts +++ b/src/styles/getModalStyles.ts @@ -39,7 +39,7 @@ type GetModalStyles = { export default function getModalStyles( type: ModalType | undefined, windowDimensions: WindowDimensions, - themeColors: ThemeColors, + theme: ThemeColors, popoverAnchorPosition: ViewStyle = {}, innerContainerStyle: ViewStyle = {}, outerStyle: ViewStyle = {}, @@ -197,7 +197,7 @@ export default function getModalStyles( modalContainerStyle = { borderRadius: 12, borderWidth: 1, - borderColor: themeColors.border, + borderColor: theme.border, justifyContent: 'center', overflow: 'hidden', boxShadow: variables.popoverMenuShadow, From 91f299de90d03565b6c1df3a847c145f1574691e Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 30 Nov 2023 12:44:35 +0000 Subject: [PATCH 51/57] Update version to 1.4.6-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 4a32609ce517..4db8a0836477 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001040600 - versionName "1.4.6-0" + versionCode 1001040601 + versionName "1.4.6-1" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index c4105db05e68..5ce1b7f51147 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.6.0 + 1.4.6.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d53f0db8cde9..f9675bc7cc27 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.4.6.0 + 1.4.6.1 diff --git a/package-lock.json b/package-lock.json index e4eadbab7afa..289c13c27e27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.6-0", + "version": "1.4.6-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.6-0", + "version": "1.4.6-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 4cd5d52ae2b0..de4be2ace0f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.6-0", + "version": "1.4.6-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 80f13fc1af1e3929b995158ce14a623adbc75865 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 16 Nov 2023 17:33:45 +0100 Subject: [PATCH 52/57] Fix green line being displayed chaotically in chat --- src/pages/home/report/ReportActionsList.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index e1230d7219db..96b3fe3be67a 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -338,8 +338,9 @@ function ReportActionsList({ const nextMessage = sortedReportActions[index + 1]; const isCurrentMessageUnread = isMessageUnread(reportAction, lastReadTimeRef.current); shouldDisplay = isCurrentMessageUnread && (!nextMessage || !isMessageUnread(nextMessage, lastReadTimeRef.current)); - if (!messageManuallyMarkedUnread) { - shouldDisplay = shouldDisplay && reportAction.actorAccountID !== Report.getCurrentUserAccountID(); + if (shouldDisplay && !messageManuallyMarkedUnread) { + const isWithinVisibleThreshold = scrollingVerticalOffset.current < MSG_VISIBLE_THRESHOLD ? reportAction.created < userActiveSince.current : true; + shouldDisplay = reportAction.actorAccountID !== Report.getCurrentUserAccountID() && isWithinVisibleThreshold; } if (shouldDisplay) { cacheUnreadMarkers.set(report.reportID, reportAction.reportActionID); From 51ef33f47d928a7e9a79507a353fe547f88a9868 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 14:24:27 +0100 Subject: [PATCH 53/57] fix: remove invalid param --- src/components/TabSelector/TabSelector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TabSelector/TabSelector.js b/src/components/TabSelector/TabSelector.js index cdec2a7e91e1..83601dbc27d8 100644 --- a/src/components/TabSelector/TabSelector.js +++ b/src/components/TabSelector/TabSelector.js @@ -102,7 +102,7 @@ function TabSelector({state, navigation, onTabPress, position}) { {_.map(state.routes, (route, index) => { const activeOpacity = getOpacity(position, state.routes.length, index, true, affectedAnimatedTabs); const inactiveOpacity = getOpacity(position, state.routes.length, index, false, affectedAnimatedTabs); - const backgroundColor = getBackgroundColor(position, state.routes.length, index, affectedAnimatedTabs); + const backgroundColor = getBackgroundColor(state.routes.length, index, affectedAnimatedTabs); const isFocused = index === state.index; const {icon, title} = getIconAndTitle(route.name, translate); From f08d0a18ed99e811827c7494b8d7a76346219d3a Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 15:35:45 +0100 Subject: [PATCH 54/57] revert changes --- android/app/build.gradle | 2 +- ios/NewExpensify/Info.plist | 234 +++++++++++++++---------------- ios/NewExpensifyTests/Info.plist | 40 +++--- 3 files changed, 138 insertions(+), 138 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 89e4bd2dfb56..28c983297c8b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -151,7 +151,7 @@ android { signingConfig null // buildTypes take precedence over productFlavors when it comes to the signing configuration, // thus we need to manually set the signing config, so that the e2e uses the debug config again. - // In other words, the signingConfig setting above will be ignored when we build the flavor in release mode. + // In other words, the signingConfig setting above will be ignored when we build the flavor in release mode. productFlavors.all { flavor -> // All release builds should be signed with the release config ... flavor.signingConfig signingConfigs.release diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 1125f62243d3..c05fe7fbfeba 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -1,128 +1,128 @@ + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + $(PRODUCT_NAME) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.4.6 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + new-expensify + + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3 + + + + CFBundleVersion + 1.4.6.2 + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + venmo + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + NSAppTransportSecurity - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - en - CFBundleDisplayName - $(PRODUCT_NAME) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.4.6 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleURLSchemes - - new-expensify - - + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost - CFBundleTypeRole - Editor - CFBundleURLSchemes - - com.googleusercontent.apps.921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3 - + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + - - CFBundleVersion - 1.4.6.2 - ITSAppUsesNonExemptEncryption - - LSApplicationQueriesSchemes - - venmo - - LSRequiresIPhoneOS - - LSSupportsOpeningDocumentsInPlace - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSExceptionDomains + www.expensify.com.dev - localhost - - NSExceptionAllowsInsecureHTTPLoads - - NSIncludesSubdomains - - - www.expensify.com.dev - - NSExceptionAllowsInsecureHTTPLoads - - NSIncludesSubdomains - - + NSExceptionAllowsInsecureHTTPLoads + + NSIncludesSubdomains + - NSCameraUsageDescription - Your camera is used to create chat attachments, documents, and facial capture. - NSLocationAlwaysAndWhenInUseUsageDescription - Your location is used to determine your default currency and timezone. - NSLocationWhenInUseUsageDescription - Your location is used to determine your default currency and timezone. - NSMicrophoneUsageDescription - Required for video capture - NSPhotoLibraryAddUsageDescription - Your camera roll is used to store chat attachments. - NSPhotoLibraryUsageDescription - Your photos are used to create chat attachments. - UIAppFonts - - ExpensifyNewKansas-Medium.otf - ExpensifyNewKansas-MediumItalic.otf - ExpensifyMono-Bold.otf - ExpensifyMono-Regular.otf - ExpensifyNeue-Bold.otf - ExpensifyNeue-BoldItalic.otf - ExpensifyNeue-Italic.otf - ExpensifyNeue-Regular.otf - - UIBackgroundModes - - remote-notification - - UIFileSharingEnabled - - UILaunchStoryboardName - BootSplash - UIRequiredDeviceCapabilities - - armv7 - - UIRequiresFullScreen - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UIUserInterfaceStyle - Dark - UIViewControllerBasedStatusBarAppearance - + NSCameraUsageDescription + Your camera is used to create chat attachments, documents, and facial capture. + NSLocationAlwaysAndWhenInUseUsageDescription + Your location is used to determine your default currency and timezone. + NSLocationWhenInUseUsageDescription + Your location is used to determine your default currency and timezone. + NSMicrophoneUsageDescription + Required for video capture + NSPhotoLibraryAddUsageDescription + Your camera roll is used to store chat attachments. + NSPhotoLibraryUsageDescription + Your photos are used to create chat attachments. + UIAppFonts + + ExpensifyNewKansas-Medium.otf + ExpensifyNewKansas-MediumItalic.otf + ExpensifyMono-Bold.otf + ExpensifyMono-Regular.otf + ExpensifyNeue-Bold.otf + ExpensifyNeue-BoldItalic.otf + ExpensifyNeue-Italic.otf + ExpensifyNeue-Regular.otf + + UIBackgroundModes + + remote-notification + + UIFileSharingEnabled + + UILaunchStoryboardName + BootSplash + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UIUserInterfaceStyle + Dark + UIViewControllerBasedStatusBarAppearance + + diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index f67483e2be59..4c875f6d7ba2 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -1,24 +1,24 @@ - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - 1.4.6 - CFBundleSignature - ???? - CFBundleVersion - 1.4.6.2 - + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.4.6 + CFBundleSignature + ???? + CFBundleVersion + 1.4.6.2 + From c0dbdc4a03b677abe9add976a4780421d9adb582 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 15:49:44 +0100 Subject: [PATCH 55/57] Update src/components/CustomStatusBar/index.tsx Co-authored-by: Fedi Rajhi --- src/components/CustomStatusBar/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index b9d47cacd65a..2a8b15642307 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -65,7 +65,7 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { }, [isDisabled, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle]); useEffect(() => { - navigationRef.addListener('state', navigationStateListener); + navigationRef.addListener('state', updateStatusBarStyle); return () => navigationRef.removeListener('state', navigationStateListener); }, [navigationStateListener]); From 3b0ebeba2e337bfe292d029602d65cd538bc3e01 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 15:49:53 +0100 Subject: [PATCH 56/57] Update src/components/CustomStatusBar/index.tsx Co-authored-by: Fedi Rajhi --- src/components/CustomStatusBar/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index 2a8b15642307..6c6a38119aa9 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -68,7 +68,7 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { navigationRef.addListener('state', updateStatusBarStyle); return () => navigationRef.removeListener('state', navigationStateListener); - }, [navigationStateListener]); + }, [updateStatusBarStyle]); useEffect(() => { if (isDisabled) { From f7df8adf79a494f89e888902d2346431c1617a98 Mon Sep 17 00:00:00 2001 From: Christoph Pader Date: Thu, 30 Nov 2023 15:51:21 +0100 Subject: [PATCH 57/57] fix: lint errors --- src/components/CustomStatusBar/index.tsx | 2 +- src/styles/themes/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx index 6c6a38119aa9..2e4994378264 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -67,7 +67,7 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { useEffect(() => { navigationRef.addListener('state', updateStatusBarStyle); - return () => navigationRef.removeListener('state', navigationStateListener); + return () => navigationRef.removeListener('state', updateStatusBarStyle); }, [updateStatusBarStyle]); useEffect(() => { diff --git a/src/styles/themes/types.ts b/src/styles/themes/types.ts index 05306b4f7565..c674b6057d0f 100644 --- a/src/styles/themes/types.ts +++ b/src/styles/themes/types.ts @@ -1,4 +1,4 @@ -import {ColorScheme, StatusBarStyle} from '@styles/styles'; +import {type ColorScheme, type StatusBarStyle} from '@styles/styles'; import CONST from '@src/CONST'; type Color = string;