import type { ScaledSize } from "react-native"; import { Dimensions, NativeModules, PixelRatio, Platform, StatusBar } from "react-native"; import { createStore } from "zustand"; import { subscribeWithSelector } from "zustand/middleware"; import type { Mutate, StoreApi } from "zustand/vanilla"; import { inRange, isString, isValueNumber, round } from "../library/lodash"; import type { SafeAreaInsetType, ResponsiveActionUnion, ResponsiveState, ScreenUtilInitilizeParams, setStateResultType } from "../type"; import { OrientationType } from "../type"; import "../responsive/extension"; const initializeState = { safeAreaInset: { top: 0, bottom: 0, left: 0, right: 0 }, screenSize: { width: 0, height: 0, scale: 0, fontScale: 0 }, mainUI: { width: 0, height: 0 }, scaleHeight: 0, scaleWidth: 0, debug: true, minTextSize: true, safeArea: true, scaleByHeight: false, splitScreenMode: false, uiWidth: 360, uiHeight: 690, font: 0 }; // const enum DpiEnum { // ldpi = 36, // mdpi = 48, // hdpi = 72, // xhdpi = 96, // xxhdpi = 144, // xxxhdpi = 192 // } // const Dpi: Record = { // [DpiEnum.ldpi]: [0.75, 0.99], // [DpiEnum.mdpi]: [1, 1.499], // [DpiEnum.hdpi]: [1.5, 2], // [DpiEnum.xhdpi]: [2, 3], // [DpiEnum.xxhdpi]: [3, 4], // [DpiEnum.xxxhdpi]: [4, 10] // }; export type ResponsiveUnion = ResponsiveState & ResponsiveActionUnion; export type ResponsiveStore = ReturnType; export function createScreenResponsiveStore(): Mutate< StoreApi, [["zustand/subscribeWithSelector", never]] > { return createStore()( subscribeWithSelector( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore (set, get) => ({ screenUtilInitialize: false, orientation: false, ...initializeState, getOrientation(): OrientationType { const currentState = get(); if (!currentState.screenSize) { return OrientationType.NONE; } if (isNaN(currentState.screenSize.width) || isNaN(currentState.screenSize.height)) { return OrientationType.NONE; } const orientationState = currentState.screenSize.width < currentState.screenSize.height; if (orientationState) { return OrientationType.POTTRAIT; } return OrientationType.LANDSCAPE; }, getInitialize: async (): Promise => { let promiseCheckTimer = -1; return new Promise((resolve): void => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore promiseCheckTimer = setInterval(() => { if (!get().screenUtilInitialize) { return; } resolve(true); clearInterval(promiseCheckTimer); }, 500); }); }, getSpacing(value: number): number { const currentState = get(); if (!currentState.scaleWidth) { return value; } if (!currentState.scaleHeight) { return value; } return Math.min(currentState.scaleWidth, currentState.scaleHeight) * value; }, getSafeArea(): SafeAreaInsetType | undefined { const safeAreaInset = get().safeAreaInset; if (!get().safeAreaInset) { return undefined; } return safeAreaInset; }, getDefaultStyle(): ScreenUtilInitilizeParams { return { height: 0, width: 0, mainUI: { width: 0, height: 0 }, safeAreaInset: { top: 0, bottom: 0, left: 0, right: 0, fontScale: 0, realWidth: 0, realHeight: 0, screenScale: 0 }, screenSize: Dimensions.get("window"), scaleHeight: 0, scaleWidth: 0, debug: true, minTextSize: true, safeArea: true, scaleByHeight: false, splitScreenMode: false, uiWidth: 360, uiHeight: 690, font: 0 } as ScreenUtilInitilizeParams; }, checkIfValueIsNull(value: T): boolean { // if(!get().screenUtilInitialize) return false; switch (typeof value) { case "string": return value !== ""; case "boolean": return value; case "number": return numberValueCheckIsNull(value); case "object": return objectValueCheckIsNull(get(), value); case "bigint": case "function": case "symbol": case "undefined": default: return false; } }, checkNumberIsAllowRange(value: number): boolean { if (!get().screenUtilInitialize) return false; return isValueNumber(value) && inRange(value, 1, 10000); }, async setScreenResponsiveInitialize(option?: ScreenUtilInitilizeParams): Promise { if (!NativeModules.ScreenUtill) { return { error: true, message: "not load Native Module.... insetUtil" }; } try { if (!option) { option = get().getDefaultStyle(); } await get().setScreenSafeInset(); try { const width = option.width; const height = option.height; set({ screenSize: option.screenSize, safeArea: option.safeArea, debug: option.debug, uiWidth: width, uiHeight: height, minTextSize: option.minTextSize, scaleByHeight: option.scaleByHeight, splitScreenMode: option.splitScreenMode }); const setScreenSizeRatioResult = get().setScreenSizeRatio(); if (setScreenSizeRatioResult.error) { return setScreenSizeRatioResult; } } catch (e) { return makeErrorResult(e); } set({ screenUtilInitialize: true }); return { error: false, message: "success" }; } catch (e: any) { return makeErrorResult(e); } }, setScreenReScreeenSizeRatio(width, height): setStateResultType { try { const currentState = get(); currentState.screenSize.width = width; currentState.screenSize.height = height; const screenSize: ScaledSize = { ...currentState.screenSize } as ScaledSize; screenSize.width = width; screenSize.height = height; const defaultScale = { ...screenSize } as ScaledSize; const fontScale = { ...screenSize } as ScaledSize; const uiSize = { ...{ uiWidth: currentState.uiWidth, uiHeight: currentState.uiHeight } }; defaultScale.width = round(defaultScale.width / uiSize.uiWidth, 3); defaultScale.height = round(defaultScale.height / uiSize.uiHeight, 3); let fontWidth = round(fontScale.width / uiSize.uiWidth, 3); let fontHeight = round(fontScale.height / uiSize.uiHeight, 3); if (!fontWidth) { fontWidth = defaultScale.width; } if (!fontHeight) { fontHeight = defaultScale.height; } set({ scaleWidth: defaultScale.width, scaleHeight: defaultScale.height, font: currentState.minTextSize ? Math.min(fontWidth, fontHeight) : fontWidth }); return { error: false, message: "success" }; } catch (e) { return makeErrorResult(e); } }, setScreenSizeRatio(): setStateResultType { try { const currentState = get(); if (!get().checkIfValueIsNull(currentState.screenSize)) { return { error: true, message: "dimension value is null" }; } let insetHeight = StatusBar.currentHeight; if (!insetHeight) { insetHeight = 0; } const inset = currentState.safeAreaInset; if (inset) { if (currentState.safeArea) { if (Platform.OS === "ios") { insetHeight = inset.top + inset.bottom; } else { const scale = PixelRatio.get(); insetHeight = inset.top / scale; } } } const screenSize: ScaledSize = { ...currentState.screenSize } as ScaledSize; screenSize.width = currentState.screenSize.width; screenSize.height = currentState.screenSize.height - insetHeight; const defaultScale = { ...screenSize } as ScaledSize; const fontScale = { ...screenSize } as ScaledSize; const uiSize = { ...{ uiWidth: currentState.uiWidth, uiHeight: currentState.uiHeight } }; defaultScale.width = round(defaultScale.width / uiSize.uiWidth, 3); defaultScale.height = round(defaultScale.height / uiSize.uiHeight, 3); let fontWidth = round(fontScale.width / uiSize.uiWidth, 3); let fontHeight = round(fontScale.height / uiSize.uiHeight, 3); if (!fontWidth) { fontWidth = defaultScale.width; } if (!fontHeight) { fontHeight = defaultScale.height; } set({ scaleWidth: defaultScale.width, scaleHeight: defaultScale.height, uiWidth: currentState.uiWidth, uiHeight: currentState.uiHeight, font: currentState.minTextSize ? Math.min(fontWidth, fontHeight) : fontWidth }); return { error: false, message: "success" }; } catch (e) { return makeErrorResult(e); } }, setScreenSafeInset(): Promise { return new Promise((resolve) => { try { NativeModules.ScreenUtill.getSafeAreaInsets((v: SafeAreaInsetType) => { try { const keyList = Object.keys(v); const setSafeAreaInset = { top: 0, bottom: 0, left: 0, right: 0, fontScale: 0, realHeight: 0, realWidth: 0, screenScale: 0 } as SafeAreaInsetType; for (let i = 0; i < keyList.length; i++) { const key = keyList[i] as keyof typeof v; setSafeAreaInset[key] = v[key]; } set({ safeAreaInset: setSafeAreaInset }); resolve({ error: false, message: "success" }); } catch (e) { return resolve(makeErrorResult(e)); } }); } catch (e) { return resolve(makeErrorResult(e)); } }); } }) ) ); } function makeErrorResult(e: any): setStateResultType { let message = "error"; if (isString(e)) { e = message; } message = e.message; return { error: false, message: message }; } export function numberValueCheckIsNull(value: number): boolean { return isValueNumber(value) && inRange(value, 1, 10000); } function objectValueCheckIsNull(currentState: ResponsiveUnion, value: T): boolean { if (!value) { return false; } const freezeKey = Object.freeze(value); const freezeValues = Object.values(freezeKey); const result: boolean[] = []; for (let i = 0; i < freezeValues.length; i++) { result.push(currentState.checkIfValueIsNull(freezeValues[i])); } return result.includes(true); }