Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a ios native module to calculate inset top and bottom #511

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
23 changes: 22 additions & 1 deletion docs/quick-start.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
# Quick start

## Install
## Install React Native Project

```sh
yarn add react-native-toast-message
# or
npm install --save react-native-toast-message
cd ios && pod install # for iOS
```

## Install Expo Project
```sh
npx expo install react-native-toast-message
```

## Rebuild the project
Rebuild the project
```sh
# expo projects
npx expo run:android
npx expo run:ios

# non-expo projects
npx react-native run-android
npx react-native run-ios
```

## Expo
- ❌ This library can't be used in the "Expo Go" app because it [requires custom native code](https://docs.expo.dev/workflow/customizing/).

## Usage

Render the `Toast` component in your app's entry file, as the **LAST CHILD** in the `View` hierarchy (along with any other components that might be rendered there):
Expand Down
5 changes: 5 additions & 0 deletions ios/SafeAreaModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <React/RCTBridgeModule.h>

@interface RNSafeAreaModule : NSObject <RCTBridgeModule>

@end
27 changes: 27 additions & 0 deletions ios/SafeAreaModule.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface SafeAreaModule : NSObject <RCTBridgeModule>
@end

@implementation SafeAreaModule
RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(getSafeAreaInsets:(RCTResponseSenderBlock)callback) {
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
UIEdgeInsets safeAreaInsets = UIEdgeInsetsZero;

if (@available(iOS 11.0, *)) {
safeAreaInsets = rootViewController.view.safeAreaInsets;
}

NSDictionary *result = @{
@"top": @(safeAreaInsets.top),
@"bottom": @(safeAreaInsets.bottom),
@"left": @(safeAreaInsets.left),
@"right": @(safeAreaInsets.right)
};

callback(@[[NSNull null], result]);
}

@end
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"files": [
"/lib"
"/lib",
"ios",
"*.podspec"
],
"repository": {
"type": "git",
Expand Down
41 changes: 41 additions & 0 deletions react-native-toast-message.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'

Pod::Spec.new do |s|
s.name = "react-native-toast-message"
s.version = package["version"]
s.summary = package["description"]
s.homepage = "https://github.com/calintamas/react-native-toast-message"
s.license = package["license"]
s.authors = package["author"]

s.platforms = { :ios => "9.0" }
s.source = { :git => "https://github.com/calintamas/react-native-toast-message.git", :tag => "#{s.version}" }

s.source_files = "ios/**/*.{h,m,mm,swift}"

# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
if respond_to?(:install_modules_dependencies, true)
install_modules_dependencies(s)
else
s.dependency "React-Core"

# Don't install the dependencies when we run `pod install` in the old architecture.
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
s.pod_target_xcconfig = {
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
}
s.dependency "React-Codegen"
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
end
end
end
4 changes: 2 additions & 2 deletions src/__tests__/useToast.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ describe('test useToast hook', () => {
position: 'bottom',
autoHide: false,
visibilityTime: 20,
topOffset: 120,
bottomOffset: 130,
topOffset: 40,
bottomOffset: 40,
keyboardOffset: 5,
onShow: jest.fn(),
onHide: jest.fn(),
Expand Down
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './useSlideAnimation';
export * from './useTimeout';
export * from './usePanResponder';
export * from './useKeyboard';
export * from './useSafeArea';
42 changes: 42 additions & 0 deletions src/hooks/useSafeArea.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {NativeModules, StatusBar} from 'react-native'
import {useEffect, useState} from "react";
import {isIOS} from "../utils/platform";
import {SCREEN_HEIGHT, WINDOW_HEIGHT} from "../utils/dimension";

type TSafeArea = {
top: number
bottom: number
left: number
right: number
}

type TNativeModulesSafeArea = {
SafeAreaInsetsModule: {
getSafeAreaInsets(p: (error: Error, result: TSafeArea) => void): Promise<{
top: number;
bottom: number;
left: number;
right: number
}>;
};
}
export const useSafeArea = () => {
const [safeAreaInsets, setSafeAreaInsets] = useState<TSafeArea | null>(null);
useEffect(() => {
if (isIOS()) {
NativeModules?.SafeAreaModule?.getSafeAreaInsets((error: Error, result: TSafeArea) => {
if (error) {
console.error(error);
} else {
setSafeAreaInsets(result)
}
});
} else {
const statusBarHeight = StatusBar.currentHeight ?? 0
const bottomInset = SCREEN_HEIGHT - WINDOW_HEIGHT - statusBarHeight;
setSafeAreaInsets({top: statusBarHeight, bottom: bottomInset, left: 0, right: 0})
}
}, [])

return {safeAreaInsets}
}
seyedmostafahasani marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 4 additions & 3 deletions src/useToast.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';

import { useLogger } from './contexts';
import { useTimeout } from './hooks';
import { useSafeArea, useTimeout } from './hooks';
import { ToastData, ToastOptions, ToastProps, ToastShowParams } from './types';
import { noop } from './utils/func';
import { mergeIfDefined } from './utils/obj';
Expand Down Expand Up @@ -34,6 +34,7 @@ export function useToast({ defaultOptions }: UseToastParams) {

const [isVisible, setIsVisible] = React.useState(false);
const [data, setData] = React.useState<ToastData>(DEFAULT_DATA);
const {safeAreaInsets} = useSafeArea()

const initialOptions = mergeIfDefined(
DEFAULT_OPTIONS,
Expand Down Expand Up @@ -62,15 +63,15 @@ export function useToast({ defaultOptions }: UseToastParams) {
const show = React.useCallback(
(params: ToastShowParams) => {
log(`Showing with params: ${JSON.stringify(params)}`);
const topOffset = safeAreaInsets && safeAreaInsets.top > 0 ? safeAreaInsets.top : initialOptions.topOffset
const bottomOffset = safeAreaInsets && safeAreaInsets.bottom > 0 ? safeAreaInsets.bottom : initialOptions.bottomOffset
const {
text1 = DEFAULT_DATA.text1,
text2 = DEFAULT_DATA.text2,
type = initialOptions.type,
position = initialOptions.position,
autoHide = initialOptions.autoHide,
visibilityTime = initialOptions.visibilityTime,
topOffset = initialOptions.topOffset,
bottomOffset = initialOptions.bottomOffset,
keyboardOffset = initialOptions.keyboardOffset,
onShow = initialOptions.onShow,
onHide = initialOptions.onHide,
Expand Down
4 changes: 4 additions & 0 deletions src/utils/dimension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {Dimensions} from "react-native";

export const {height: WINDOW_HEIGHT} = Dimensions.get('window')
export const {height: SCREEN_HEIGHT} = Dimensions.get('screen')
Loading