diff --git a/packages/pluggableWidgets/bottom-sheet-native/package.json b/packages/pluggableWidgets/bottom-sheet-native/package.json index 1c0ddb020..540d47d02 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/package.json +++ b/packages/pluggableWidgets/bottom-sheet-native/package.json @@ -23,7 +23,6 @@ "@mendix/piw-native-utils-internal": "*", "@mendix/piw-utils-internal": "*", "@shopify/flash-list": "2.3.1", - "react-native-device-info": "15.0.2", "react-native-gesture-handler": "2.31.2", "react-native-reanimated": "4.3.1", "react-native-worklets": "0.8.3" diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/BottomSheet.spec.tsx b/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/BottomSheet.spec.tsx index 2e00f7a5f..a6108bbf8 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/BottomSheet.spec.tsx +++ b/packages/pluggableWidgets/bottom-sheet-native/src/__tests__/BottomSheet.spec.tsx @@ -17,10 +17,6 @@ jest.mock("react-native-reanimated", () => { return Reanimated; }); -jest.mock("react-native-device-info", () => ({ - getDeviceId: () => "iPhone10,6" -})); - jest.mock("react-native/Libraries/Utilities/Platform", () => { const Platform = jest.requireActual("react-native/Libraries/Utilities/Platform"); Platform.OS = "ios"; diff --git a/packages/pluggableWidgets/bottom-sheet-native/src/ui/Styles.ts b/packages/pluggableWidgets/bottom-sheet-native/src/ui/Styles.ts index 1b1db34b6..8ea337d9f 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/src/ui/Styles.ts +++ b/packages/pluggableWidgets/bottom-sheet-native/src/ui/Styles.ts @@ -1,5 +1,4 @@ import { TextStyle, ViewStyle } from "react-native"; -import DeviceInfo from "react-native-device-info"; export interface ModalItemsStyle { container?: ModalItemContainerStyle; @@ -19,26 +18,3 @@ export interface BottomSheetStyle { modal: ViewStyle; modalItems: ModalItemsStyle; } - -const isiPhoneModelWithNotch = (): boolean => { - const model = DeviceInfo.getDeviceId(); - if (model.indexOf("iPhone") !== -1) { - switch (model) { - case "iPhone10,6": // iPhone X GSM - case "iPhone11,2": // iPhone XS - case "iPhone11,4": // iPhone XS Max - case "iPhone11,6": // iPhone XS Max Global - case "iPhone11,8": // Iphone XR - case "iPhone12,1": // Iphone 11 - case "iPhone12,3": // Iphone 11 Pro - case "iPhone12,5": // Iphone 11 Pro Max - return true; - default: - return false; - } - } - return false; -}; - -export const defaultPaddings = { paddingBottom: isiPhoneModelWithNotch() ? 24 : 0 }; -export const defaultMargins = { marginBottom: isiPhoneModelWithNotch() ? 24 : 0 }; diff --git a/packages/pluggableWidgets/gallery-native/package.json b/packages/pluggableWidgets/gallery-native/package.json index 282da9eb2..bad0c7099 100644 --- a/packages/pluggableWidgets/gallery-native/package.json +++ b/packages/pluggableWidgets/gallery-native/package.json @@ -22,9 +22,9 @@ "test": "pluggable-widgets-tools test:unit:native" }, "dependencies": { + "@mendix/piw-native-utils-internal": "*", "@mendix/piw-utils-internal": "*", - "@shopify/flash-list": "2.3.1", - "react-native-device-info": "15.0.2" + "@shopify/flash-list": "2.3.1" }, "devDependencies": { "@mendix/pluggable-widgets-tools": "*" diff --git a/packages/pluggableWidgets/gallery-native/src/components/Gallery.tsx b/packages/pluggableWidgets/gallery-native/src/components/Gallery.tsx index a26be77a6..08f15959b 100644 --- a/packages/pluggableWidgets/gallery-native/src/components/Gallery.tsx +++ b/packages/pluggableWidgets/gallery-native/src/components/Gallery.tsx @@ -10,7 +10,7 @@ import { useWindowDimensions } from "react-native"; import { ObjectItem, DynamicValue } from "mendix"; -import DeviceInfo from "react-native-device-info"; +import { useDeviceMetrics } from "@mendix/piw-native-utils-internal"; import { GalleryStyle } from "../ui/Styles"; import { HorizontalItemSizingEnum, PaginationEnum, ScrollDirectionEnum } from "../../typings/GalleryProps"; import { isAvailable } from "@mendix/piw-utils-internal"; @@ -39,8 +39,9 @@ export interface GalleryProps { } export const Gallery = (props: GalleryProps): ReactElement => { + const { isTablet } = useDeviceMetrics(); const isScrollDirectionVertical = props.scrollDirection === "vertical"; - const numColumns = DeviceInfo.isTablet() ? props.tabletColumns : props.phoneColumns; + const numColumns = isTablet ? props.tabletColumns : props.phoneColumns; const firstItemId = props.items?.[0]?.id; const lastItemId = props.items?.[props.items.length - 1]?.id; const { name, style, itemRenderer } = props; diff --git a/packages/pluggableWidgets/gallery-native/src/components/__tests__/Gallery.spec.tsx b/packages/pluggableWidgets/gallery-native/src/components/__tests__/Gallery.spec.tsx index 59901208f..ea4615c1a 100644 --- a/packages/pluggableWidgets/gallery-native/src/components/__tests__/Gallery.spec.tsx +++ b/packages/pluggableWidgets/gallery-native/src/components/__tests__/Gallery.spec.tsx @@ -3,7 +3,9 @@ import { ObjectItem, GUID, DynamicValue, ValueStatus } from "mendix"; import { render, fireEvent, act } from "@testing-library/react-native"; import { Gallery, GalleryProps } from "../Gallery"; -jest.mock("react-native-device-info", () => ({ isTablet: jest.fn().mockReturnValue(false) })); +jest.mock("@mendix/piw-native-utils-internal", () => ({ + useDeviceMetrics: () => ({ isTablet: false }) +})); jest.mock("@shopify/flash-list"); // Mocking FlashList API because it causes issues with the test renderer const itemWrapperFunction = diff --git a/packages/pluggableWidgets/intro-screen-native/package.json b/packages/pluggableWidgets/intro-screen-native/package.json index a9ee26879..0796dcc2f 100644 --- a/packages/pluggableWidgets/intro-screen-native/package.json +++ b/packages/pluggableWidgets/intro-screen-native/package.json @@ -22,8 +22,7 @@ "@mendix/piw-native-utils-internal": "*", "@mendix/piw-utils-internal": "*", "@react-native-async-storage/async-storage": "2.2.0", - "@shopify/flash-list": "2.3.1", - "react-native-device-info": "15.0.2" + "@shopify/flash-list": "2.3.1" }, "devDependencies": { "@mendix/pluggable-widgets-tools": "*" diff --git a/packages/pluggableWidgets/intro-screen-native/src/IntroScreen.tsx b/packages/pluggableWidgets/intro-screen-native/src/IntroScreen.tsx index e861673a3..6cb16760e 100644 --- a/packages/pluggableWidgets/intro-screen-native/src/IntroScreen.tsx +++ b/packages/pluggableWidgets/intro-screen-native/src/IntroScreen.tsx @@ -7,14 +7,14 @@ import { SwipeableContainer } from "./SwipeableContainer"; import AsyncStorage from "@react-native-async-storage/async-storage"; import deepmerge from "deepmerge"; import { executeAction } from "@mendix/piw-utils-internal"; +import { useDeviceMetrics } from "@mendix/piw-native-utils-internal"; export function IntroScreen(props: IntroScreenProps): JSX.Element { const [visible, setVisible] = useState(false); + const { hasNotch } = useDeviceMetrics(); + const notchStyle = hasNotch ? { paginationContainer: { bottom: 22 } } : {}; const customStyles = props.style ? props.style.filter(o => o != null) : []; - const styles = - customStyles.length > 0 - ? deepmerge.all([defaultWelcomeScreenStyle, ...customStyles]) - : defaultWelcomeScreenStyle; + const styles = deepmerge.all([defaultWelcomeScreenStyle, notchStyle, ...customStyles]); useEffect(() => { let isMounted = true; diff --git a/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.notch.spec.tsx b/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.notch.spec.tsx index 4c10dee4d..5486cfdbd 100644 --- a/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.notch.spec.tsx +++ b/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.notch.spec.tsx @@ -6,9 +6,8 @@ import { View } from "react-native"; import { EditableValueBuilder } from "@mendix/piw-utils-internal"; import { Big } from "big.js"; -jest.mock("react-native-device-info", () => ({ - hasNotch: jest.fn().mockReturnValue(true), - getDeviceId: jest.fn().mockReturnValue("iPhone10,6") +jest.mock("@mendix/piw-native-utils-internal", () => ({ + useDeviceMetrics: () => ({ hasNotch: true }) })); jest.mock("@react-native-async-storage/async-storage", () => ({ diff --git a/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.spec.tsx b/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.spec.tsx index 643f80d43..28e05a08b 100644 --- a/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.spec.tsx +++ b/packages/pluggableWidgets/intro-screen-native/src/__tests__/IntroScreen.spec.tsx @@ -6,9 +6,8 @@ import { View } from "react-native"; import { EditableValueBuilder } from "@mendix/piw-utils-internal"; import { Big } from "big.js"; -jest.mock("react-native-device-info", () => ({ - hasNotch: jest.fn(), - getDeviceId: jest.fn().mockReturnValue("") +jest.mock("@mendix/piw-native-utils-internal", () => ({ + useDeviceMetrics: () => ({ hasNotch: false }) })); jest.mock("@react-native-async-storage/async-storage", () => ({ @@ -42,9 +41,8 @@ describe("Intro Screen", () => { identifier: "" }; - jest.mock("react-native-device-info", () => ({ - hasNotch: jest.fn(), - getDeviceId: jest.fn().mockReturnValue("iPhone") + jest.mock("@mendix/piw-native-utils-internal", () => ({ + useDeviceMetrics: () => ({ hasNotch: false }) })); }); diff --git a/packages/pluggableWidgets/intro-screen-native/src/ui/Styles.ts b/packages/pluggableWidgets/intro-screen-native/src/ui/Styles.ts index 4f81457f7..6581d3d41 100644 --- a/packages/pluggableWidgets/intro-screen-native/src/ui/Styles.ts +++ b/packages/pluggableWidgets/intro-screen-native/src/ui/Styles.ts @@ -1,30 +1,8 @@ import { Style } from "@mendix/piw-native-utils-internal"; import { I18nManager, Platform, StyleSheet, TextStyle, ViewStyle } from "react-native"; import absoluteFillObject = StyleSheet.absoluteFillObject; -import DeviceInfo from "react-native-device-info"; - -const isiPhoneModelWithNotch = (): boolean => { - const model = DeviceInfo.getDeviceId(); - if (model.indexOf("iPhone") !== -1) { - switch (model) { - case "iPhone10,6": // iPhone X GSM - case "iPhone11,2": // iPhone XS - case "iPhone11,4": // iPhone XS Max - case "iPhone11,6": // iPhone XS Max Global - case "iPhone11,8": // Iphone XR - case "iPhone12,1": // Iphone 11 - case "iPhone12,3": // Iphone 11 Pro - case "iPhone12,5": // Iphone 11 Pro Max - return true; - default: - return false; - } - } - return false; -}; const isAndroidRTL = I18nManager.isRTL && Platform.OS === "android"; -const isIphoneWithNotch = DeviceInfo.hasNotch() || (Platform.OS === "ios" && isiPhoneModelWithNotch()); const defaultButtonBetweenContainer: ViewStyle = { flexDirection: "row", @@ -91,7 +69,7 @@ export const defaultWelcomeScreenStyle: IntroScreenStyle = { }, paginationContainer: { position: "absolute", - bottom: isIphoneWithNotch ? 22 : 0, + bottom: 0, left: 0, right: 0, justifyContent: "space-between", diff --git a/packages/tools/piw-native-utils-internal/package.json b/packages/tools/piw-native-utils-internal/package.json index a8b4636aa..01450a0d7 100644 --- a/packages/tools/piw-native-utils-internal/package.json +++ b/packages/tools/piw-native-utils-internal/package.json @@ -24,6 +24,9 @@ "prepare": "pnpm build", "postinstall": "pnpm build" }, + "dependencies": { + "react-native-device-info": "15.0.2" + }, "devDependencies": { "@mendix/pluggable-widgets-tools": "*", "rimraf": "^6.1.2" diff --git a/packages/tools/piw-native-utils-internal/src/deviceMetrics.ts b/packages/tools/piw-native-utils-internal/src/deviceMetrics.ts new file mode 100644 index 000000000..fe9804e2c --- /dev/null +++ b/packages/tools/piw-native-utils-internal/src/deviceMetrics.ts @@ -0,0 +1,15 @@ +export interface DeviceMetrics { + hasNotch: boolean; + isTablet: boolean; +} + +export function useDeviceMetrics(): DeviceMetrics { + // Lazy import - only loads react-native-device-info when this function is called + // This prevents the module from being bundled during widget build + const DeviceInfo = require("react-native-device-info").default; + + return { + hasNotch: DeviceInfo.hasNotch(), + isTablet: DeviceInfo.isTablet() + }; +} diff --git a/packages/tools/piw-native-utils-internal/src/index.ts b/packages/tools/piw-native-utils-internal/src/index.ts index e8e614b80..5493037df 100644 --- a/packages/tools/piw-native-utils-internal/src/index.ts +++ b/packages/tools/piw-native-utils-internal/src/index.ts @@ -4,3 +4,4 @@ export { available, toNumber, unavailable } from "./properties"; export { Slider, SliderProps, RangeSlider, RangeSliderProps, RangeSliderValue } from "./components/Slider"; export { Bar, BarProps } from "./components/Bar"; export { Arc, ArcProps, makeArcPath } from "./components/Arc"; +export { useDeviceMetrics, DeviceMetrics } from "./deviceMetrics"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5068430d2..f4bcb5eed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -373,9 +373,6 @@ importers: '@shopify/flash-list': specifier: 2.3.1 version: 2.3.1(@babel/runtime@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - react-native-device-info: - specifier: 15.0.2 - version: 15.0.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) react-native-gesture-handler: specifier: 2.31.2 version: 2.31.2(patch_hash=5be6c2cce8be6bd8afdbbf6c289d8b210686eafce0b41bf8cae2c29388aaa3b8)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) @@ -484,15 +481,15 @@ importers: packages/pluggableWidgets/gallery-native: dependencies: + '@mendix/piw-native-utils-internal': + specifier: '*' + version: link:../../tools/piw-native-utils-internal '@mendix/piw-utils-internal': specifier: '*' version: link:../../tools/piw-utils-internal '@shopify/flash-list': specifier: 2.3.1 version: 2.3.1(@babel/runtime@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - react-native-device-info: - specifier: 15.0.2 - version: 15.0.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) devDependencies: '@mendix/pluggable-widgets-tools': specifier: 11.12.0 @@ -553,9 +550,6 @@ importers: '@shopify/flash-list': specifier: 2.3.1 version: 2.3.1(@babel/runtime@7.29.7)(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) - react-native-device-info: - specifier: 15.0.2 - version: 15.0.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) devDependencies: '@mendix/pluggable-widgets-tools': specifier: 11.12.0 @@ -894,6 +888,10 @@ importers: version: 11.12.0(patch_hash=c3d32a9026cacb93268037356ae25d0e56d1b9b3ecb22aea09cbf1c69542670e)(@jest/transform@30.3.0)(@jest/types@30.4.1)(@types/babel__core@7.20.5)(@types/node@20.19.43)(jest-util@30.4.1)(jiti@2.6.1)(picomatch@4.0.5)(react-dom@19.2.3(react@19.2.3))(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(tslib@2.8.1) packages/tools/piw-native-utils-internal: + dependencies: + react-native-device-info: + specifier: 15.0.2 + version: 15.0.2(react-native@0.84.1(@babel/core@7.29.7)(@react-native/metro-config@0.86.1(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) devDependencies: '@mendix/pluggable-widgets-tools': specifier: 11.12.0