├── .expo-shared
└── assets.json
├── .gitignore
├── .svgrrc
├── App.tsx
├── LICENSE
├── README.md
├── app.json
├── assets
├── adaptive-icon.png
├── favicon.png
├── icon.png
└── splash.png
├── babel.config.aliases.js
├── babel.config.js
├── declarations.d.ts
├── metro.config.js
├── package.json
├── src
├── assets
│ ├── img
│ │ ├── app-icons
│ │ │ ├── AppStore.png
│ │ │ ├── Calculator.png
│ │ │ ├── Calendar.png
│ │ │ ├── Camera.png
│ │ │ ├── Clock.png
│ │ │ ├── Compass.png
│ │ │ ├── Facetime.png
│ │ │ ├── Find My.png
│ │ │ ├── FindMy.png
│ │ │ ├── GarageBand.png
│ │ │ ├── Home.png
│ │ │ ├── Measure.png
│ │ │ ├── Messages.png
│ │ │ ├── News.png
│ │ │ ├── Notes.png
│ │ │ ├── Phone.png
│ │ │ ├── Photos.png
│ │ │ ├── Safari.png
│ │ │ ├── Settings.png
│ │ │ ├── TestFlight.png
│ │ │ ├── Wallet.png
│ │ │ ├── Weather.png
│ │ │ └── iTunes.png
│ │ └── wallpaper.jpg
│ └── svg
│ │ ├── chevron-right.svg
│ │ ├── e-letter.svg
│ │ ├── github.svg
│ │ ├── link.svg
│ │ ├── message.svg
│ │ ├── michrophone.svg
│ │ └── search.svg
├── components
│ ├── AnimatedInput
│ │ ├── AnimatedInput.hooks.ts
│ │ ├── AnimatedInput.styles.ts
│ │ ├── AnimatedIntput.tsx
│ │ ├── CancelButton.tsx
│ │ └── index.ts
│ ├── AppItem
│ │ ├── AppItem.constants.ts
│ │ ├── AppItem.styles.ts
│ │ ├── AppItem.tsx
│ │ └── index.ts
│ ├── Footer
│ │ ├── Footer.tsx
│ │ └── index.ts
│ ├── SwipeableProvider
│ │ ├── SwipeableProvider.styles.ts
│ │ ├── SwipeableProvider.tsx
│ │ └── index.ts
│ ├── Text.tsx
│ ├── Wallpaper.tsx
│ └── WidgetItem
│ │ ├── WidgetItem.constants.ts
│ │ ├── WidgetItem.styles.ts
│ │ ├── WidgetItem.tsx
│ │ └── index.ts
├── configs
│ ├── horizontalGestureCalculations.ts
│ └── verticalGestureCalculations.ts
├── constants
│ ├── animation.ts
│ ├── apps.ts
│ ├── theme.ts
│ └── ui.ts
├── hooks
│ ├── useGestureHandler.ts
│ └── useSwipeableProvider.ts
├── screens
│ ├── Home
│ │ ├── Home.tsx
│ │ └── index.ts
│ └── Search
│ │ ├── AnimatedProvider.tsx
│ │ ├── LeftSearch.tsx
│ │ ├── RightSearch.tsx
│ │ ├── Search.styles.ts
│ │ ├── Search.tsx
│ │ ├── SearchContent.tsx
│ │ ├── components
│ │ ├── LeftSearchContent.styles.tsx
│ │ └── LeftSearchContent.tsx
│ │ └── index.ts
└── utils
│ └── swipeableGestureHandlers.ts
├── tsconfig.json
└── yarn.lock
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/.svgrrc:
--------------------------------------------------------------------------------
1 | {
2 | "memo": true,
3 | "native": true,
4 | "replaceAttrValues": {
5 | "fill": "{props.fill}",
6 | "stroke": "{props.stroke}"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | import { StatusBar } from "expo-status-bar";
4 | import { SafeAreaProvider } from "react-native-safe-area-context";
5 |
6 | import Wallpaper from "@react-native-ios/components/Wallpaper";
7 | import Footer from "@react-native-ios/components/Footer/Footer";
8 | import SwipeableProvider from "@react-native-ios/components/SwipeableProvider";
9 | import { GestureHandlerRootView } from "react-native-gesture-handler";
10 | import { Page1, Page2 } from "@react-native-ios/screens/Home";
11 |
12 | export default function App() {
13 | return (
14 |
15 |
16 |
17 |
18 | , ]} />
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | const styles = StyleSheet.create({
26 | gestureHandler: {
27 | flex: 1,
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Enes Ozturk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
React Native iOS
6 |
7 |
8 | This is a fun project. I have always been impressed with iOS's UI gestures & animations. Previously I developed [React Native Hold Menu](https://github.com/enesozturk/react-native-hold-menu) which I inspired by the iOS messages, applications hold to open menus. This time, I tried to do the whole iOS itself for fun, and the result is awesome!
9 |
10 | ## Stack
11 |
12 | - [Expo](https://expo.dev/) (SDK 44)
13 | - TypeScript
14 | - [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)
15 | - [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/)
16 | - [Expo Blur](https://github.com/expo/expo/tree/master/packages/expo-blur) for animatable blur component
17 |
18 | ## Want to try?
19 |
20 | If you want to try this on your device, you can install and run the app in a few seconds with the following commands;
21 |
22 | Install the packages:
23 |
24 | ```
25 | yarn install
26 | ```
27 |
28 | Start the server
29 |
30 | ```bash
31 | yarn start
32 | ```
33 |
34 | You will see a QR code on the terminal, you can scan this QR code with the device which you are on the same network. Or you can start the simulator by pressing `i`, on the terminal.
35 |
36 | > Currently app is not working the same with iOS on Android due to some issues with blur view animations and horizontal gesture animations. I may fix it later.
37 |
38 | That's it, enjoy 🤞🏽
39 |
40 | ## License
41 |
42 | The source code is made available under the [MIT license](./LICENSE).
43 |
44 | ## Show Your Support
45 |
46 | If you like this project, please give a star and follow me on Github for more 🤩
47 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "react-native-ios",
4 | "slug": "react-native-ios",
5 | "privacy": "public",
6 | "platforms": ["ios"],
7 | "sdkVersion": "44.0.0",
8 | "githubUrl": "https://github.com/enesozturk/react-native-ios",
9 | "version": "1.0.2",
10 | "orientation": "portrait",
11 | "owner": "enesozturk",
12 | "icon": "./assets/icon.png",
13 | "splash": {
14 | "image": "./assets/splash.png",
15 | "resizeMode": "cover",
16 | "backgroundColor": "#000000"
17 | },
18 | "updates": {
19 | "fallbackToCacheTimeout": 0
20 | },
21 | "assetBundlePatterns": ["**/*"],
22 | "ios": {
23 | "supportsTablet": true,
24 | "bundleIdentifier": "com.enesozturk.react-native-ios"
25 | },
26 | "android": {
27 | "adaptiveIcon": {
28 | "foregroundImage": "./assets/adaptive-icon.png",
29 | "backgroundColor": "#FFFFFF"
30 | }
31 | },
32 | "web": {
33 | "favicon": "./assets/favicon.png"
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/assets/favicon.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.aliases.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "@react-native-ios/api": "./src/api",
3 | "@react-native-ios/assets": "./src/assets",
4 | "@react-native-ios/components": "./src/components",
5 | "@react-native-ios/configs": "./src/configs",
6 | "@react-native-ios/constants": "./src/constants",
7 | "@react-native-ios/helpers": "./src/helpers",
8 | "@react-native-ios/hooks": "./src/hooks",
9 | "@react-native-ios/navigators": "./src/navigators",
10 | "@react-native-ios/providers": "./src/providers",
11 | "@react-native-ios/screens": "./src/screens",
12 | "@react-native-ios/store": "./src/store",
13 | "@react-native-ios/storybook": "./src/storybook",
14 | "@react-native-ios/translations": "./src/translations",
15 | "@react-native-ios/utils": "./src/utils",
16 | };
17 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const aliases = require("./babel.config.aliases");
2 |
3 | module.exports = function (api) {
4 | api.cache(true);
5 | return {
6 | presets: ["babel-preset-expo"],
7 | plugins: [
8 | ["module-resolver", { root: ["./"], alias: aliases }],
9 | "react-native-reanimated/plugin",
10 | ],
11 | };
12 | };
13 |
--------------------------------------------------------------------------------
/declarations.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | import React from "react";
3 | import { SvgProps } from "react-native-svg";
4 | const content: React.FC;
5 | export default content;
6 | }
7 |
8 | declare module "*.jpg";
9 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | const { getDefaultConfig } = require("expo/metro-config");
2 |
3 | module.exports = (async () => {
4 | const {
5 | resolver: { sourceExts, assetExts },
6 | } = await getDefaultConfig(__dirname);
7 | return {
8 | ...getDefaultConfig,
9 | transformer: {
10 | babelTransformerPath: require.resolve("react-native-svg-transformer"),
11 | getTransformOptions: async () => ({
12 | transform: {
13 | inlineRequires: true,
14 | },
15 | }),
16 | },
17 | resolver: {
18 | assetExts: assetExts.filter((ext) => ext !== "svg"),
19 | sourceExts: [...sourceExts, "svg"],
20 | },
21 | };
22 | })();
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-ios",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web",
10 | "eject": "expo eject"
11 | },
12 | "dependencies": {
13 | "expo": "~44.0.0",
14 | "expo-blur": "~11.0.0",
15 | "expo-status-bar": "~1.2.0",
16 | "expo-web-browser": "~10.1.0",
17 | "react": "17.0.1",
18 | "react-dom": "17.0.1",
19 | "react-native": "0.64.3",
20 | "react-native-gesture-handler": "~2.1.0",
21 | "react-native-reanimated": "~2.3.1",
22 | "react-native-safe-area-context": "3.3.2",
23 | "react-native-svg": "^12.1.1",
24 | "react-native-web": "0.17.1"
25 | },
26 | "devDependencies": {
27 | "@babel/core": "^7.12.9",
28 | "@svgr/cli": "^6.2.0",
29 | "@types/react": "~17.0.21",
30 | "@types/react-native": "~0.64.12",
31 | "babel-plugin-module-resolver": "^4.1.0",
32 | "react-native-svg-transformer": "^1.0.0",
33 | "typescript": "~4.3.5"
34 | },
35 | "private": true
36 | }
37 |
--------------------------------------------------------------------------------
/src/assets/img/app-icons/AppStore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/AppStore.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Calculator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Calculator.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Calendar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Calendar.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Camera.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Clock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Clock.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Compass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Compass.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Facetime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Facetime.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Find My.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Find My.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/FindMy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/FindMy.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/GarageBand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/GarageBand.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Home.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Measure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Measure.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Messages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Messages.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/News.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/News.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Notes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Notes.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Phone.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Photos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Photos.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Safari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Safari.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Settings.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/TestFlight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/TestFlight.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Wallet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Wallet.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/Weather.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/Weather.png
--------------------------------------------------------------------------------
/src/assets/img/app-icons/iTunes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/app-icons/iTunes.png
--------------------------------------------------------------------------------
/src/assets/img/wallpaper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/enesozturk/react-native-ios/89f73c56a514804af925e76f86f0df5f017a2d8d/src/assets/img/wallpaper.jpg
--------------------------------------------------------------------------------
/src/assets/svg/chevron-right.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/svg/e-letter.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/svg/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/svg/link.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/assets/svg/message.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/svg/michrophone.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/svg/search.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/components/AnimatedInput/AnimatedInput.hooks.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Extrapolate,
3 | interpolate,
4 | useAnimatedStyle,
5 | useSharedValue,
6 | } from "react-native-reanimated";
7 |
8 | import theme from "@react-native-ios/constants/theme";
9 | import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
10 |
11 | const INPUT_WIDTH = SCREEN_WIDTH - 54;
12 |
13 | const useAnimatedInput = () => {
14 | const containerWidth = useSharedValue(0);
15 | const cancelTextWidth = useSharedValue(0);
16 | const active = useSharedValue(0);
17 |
18 | const animatedContainerStyles = useAnimatedStyle(
19 | () => ({
20 | width: "100%",
21 | }),
22 | []
23 | );
24 |
25 | const animatedContentContainerStyles = useAnimatedStyle(
26 | () => ({
27 | height: interpolate(
28 | active.value,
29 | [0, 1],
30 | [22 + 26, 22 + 12], // 48 - 24 24/2 14, 38-20 18/2 7
31 | Extrapolate.CLAMP
32 | ),
33 | width: interpolate(
34 | active.value,
35 | [0, 1],
36 | [INPUT_WIDTH, INPUT_WIDTH - cancelTextWidth.value],
37 | Extrapolate.CLAMP
38 | ),
39 | borderRadius: interpolate(
40 | active.value,
41 | [0, 1],
42 | [theme.spacing.md, 12],
43 | Extrapolate.CLAMP
44 | ),
45 | }),
46 | [cancelTextWidth]
47 | );
48 |
49 | const animatedInputContainerStyles = useAnimatedStyle(
50 | () => ({
51 | position: "absolute",
52 | left: interpolate(
53 | active.value,
54 | [0, 1],
55 | [containerWidth.value / 2, 0],
56 | Extrapolate.CLAMP
57 | ),
58 | }),
59 | []
60 | );
61 |
62 | const animatedInputStyles = useAnimatedStyle(
63 | () => ({
64 | marginLeft: interpolate(
65 | active.value,
66 | [0, 1],
67 | [INPUT_WIDTH / 2 - 30, (theme.spacing.sm + theme.spacing.xs) * 2 - 2],
68 | Extrapolate.CLAMP
69 | ),
70 | marginBottom: interpolate(
71 | active.value,
72 | [0, 1],
73 | [4, 2],
74 | Extrapolate.CLAMP
75 | ),
76 | transform: [
77 | {
78 | scale: interpolate(
79 | active.value,
80 | [0, 1],
81 | [1, 0.84],
82 | Extrapolate.CLAMP
83 | ),
84 | },
85 | ],
86 | }),
87 | []
88 | );
89 |
90 | const animatedCancelTextStyles = useAnimatedStyle(
91 | () => ({
92 | opacity: active.value,
93 | }),
94 | []
95 | );
96 |
97 | const animatedSearchIconStyles = useAnimatedStyle(
98 | () => ({
99 | opacity: 1,
100 | top: interpolate(active.value, [0, 1], [12, 8], Extrapolate.CLAMP),
101 | left: interpolate(
102 | active.value,
103 | [0, 1],
104 | [INPUT_WIDTH / 2 - 30 - 32, 8],
105 | Extrapolate.CLAMP
106 | ),
107 | }),
108 | []
109 | );
110 |
111 | const animatedSearchIconProps = useAnimatedStyle(
112 | () => ({
113 | width: interpolate(active.value, [0, 1], [20, 18], Extrapolate.CLAMP),
114 | height: interpolate(active.value, [0, 1], [20, 18], Extrapolate.CLAMP),
115 | }),
116 | []
117 | );
118 |
119 | const animatedMicIconStyles = useAnimatedStyle(
120 | () => ({
121 | opacity: active.value,
122 | top: interpolate(active.value, [0, 1], [14, 8], Extrapolate.CLAMP),
123 | }),
124 | []
125 | );
126 |
127 | return {
128 | active,
129 | cancelTextWidth,
130 | containerWidth,
131 | animatedSearchIconStyles,
132 | animatedSearchIconProps,
133 | animatedMicIconStyles,
134 | animatedContainerStyles,
135 | animatedContentContainerStyles,
136 | animatedInputContainerStyles,
137 | animatedInputStyles,
138 | animatedCancelTextStyles,
139 | };
140 | };
141 |
142 | export default useAnimatedInput;
143 |
--------------------------------------------------------------------------------
/src/components/AnimatedInput/AnimatedInput.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | import theme from "@react-native-ios/constants/theme";
4 | import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
5 |
6 | export default StyleSheet.create({
7 | container: {
8 | display: "flex",
9 | flexDirection: "row",
10 | justifyContent: "flex-start",
11 | alignItems: "center",
12 | marginBottom: theme.spacing.lg,
13 | paddingHorizontal: 56 / 2 - 8,
14 | },
15 | contentContainer: {
16 | width: SCREEN_WIDTH - theme.spacing.lg,
17 | display: "flex",
18 | flexDirection: "row",
19 | position: "relative",
20 | backgroundColor: theme.colors.white.white25,
21 | },
22 | input: {
23 | paddingHorizontal: theme.spacing.sm,
24 | paddingRight: 36,
25 | color: theme.colors.white.white75,
26 | ...theme.font.title2,
27 | },
28 | searchIconContainer: {
29 | position: "absolute",
30 | },
31 | michrophoneIconContainer: {
32 | position: "absolute",
33 | right: 0,
34 | },
35 | cancelText: {
36 | ...theme.font.body,
37 | textAlignVertical: "center",
38 | color: theme.colors.white.white50,
39 | height: "100%",
40 | paddingVertical: theme.spacing.sm13,
41 | },
42 | });
43 |
--------------------------------------------------------------------------------
/src/components/AnimatedInput/AnimatedIntput.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { TextInput, View } from "react-native";
3 |
4 | import Animated, { withTiming } from "react-native-reanimated";
5 | import { TapGestureHandler } from "react-native-gesture-handler";
6 |
7 | import theme from "@react-native-ios/constants/theme";
8 | import SearchSVG from "@react-native-ios/assets/svg/search.svg";
9 | import MichrophoneSVG from "@react-native-ios/assets/svg/michrophone.svg";
10 |
11 | import styles from "./AnimatedInput.styles";
12 | import useAnimatedInput from "./AnimatedInput.hooks";
13 | import { useRef } from "react";
14 | import CancelButton from "./CancelButton";
15 |
16 | const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
17 | const AnimatedSearchIcon = Animated.createAnimatedComponent(SearchSVG);
18 |
19 | export default function AnimatedIntput() {
20 | const {
21 | active,
22 | cancelTextWidth,
23 | containerWidth,
24 | animatedSearchIconStyles,
25 | animatedMicIconStyles,
26 | animatedSearchIconProps,
27 | animatedContainerStyles,
28 | animatedContentContainerStyles,
29 | animatedInputStyles,
30 | animatedCancelTextStyles,
31 | } = useAnimatedInput();
32 | const inputRef = useRef(null);
33 |
34 | return (
35 | {}}>
36 |
37 | {
39 | inputRef?.current?.focus();
40 | active.value = withTiming(1, { duration: 300 });
41 | }}
42 | >
43 | {
45 | containerWidth.value = e.nativeEvent.layout.width;
46 | }}
47 | style={[styles.contentContainer, animatedContentContainerStyles]}
48 | >
49 |
52 |
56 |
57 |
60 |
61 |
62 | {
71 | active.value = withTiming(1, { duration: 300 });
72 | }}
73 | onBlur={() => {
74 | active.value = withTiming(0, { duration: 300 });
75 | }}
76 | />
77 |
78 |
79 | {
83 | inputRef?.current?.blur();
84 | }}
85 | cancelTextWidth={cancelTextWidth}
86 | />
87 |
88 |
89 | );
90 | }
91 |
--------------------------------------------------------------------------------
/src/components/AnimatedInput/CancelButton.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { StyleSheet, View } from "react-native";
3 |
4 | import { TapGestureHandler } from "react-native-gesture-handler";
5 | import Animated, { SharedValue, withSpring } from "react-native-reanimated";
6 |
7 | import { SPRING_CONFIG } from "@react-native-ios/constants/animation";
8 | import theme from "@react-native-ios/constants/theme";
9 |
10 | type CancelButtonProps = {
11 | active: SharedValue;
12 | cancelTextWidth: SharedValue;
13 | animatedCancelTextStyles: any;
14 | onClose: () => void;
15 | };
16 |
17 | export default function CancelButton({
18 | active,
19 | cancelTextWidth,
20 | animatedCancelTextStyles,
21 | onClose,
22 | }: CancelButtonProps) {
23 | return (
24 | {
27 | cancelTextWidth.value = e.nativeEvent.layout.width;
28 | }}
29 | >
30 | {
32 | onClose?.();
33 | active.value = withSpring(0, SPRING_CONFIG);
34 | }}
35 | >
36 |
37 | Cancel
38 |
39 |
40 |
41 | );
42 | }
43 |
44 | const styles = StyleSheet.create({
45 | container: {
46 | paddingVertical: theme.spacing.sm13,
47 | display: "flex",
48 | flexDirection: "row",
49 | },
50 | cancelText: {
51 | ...theme.font.body,
52 | color: theme.colors.white.white50,
53 | height: "100%",
54 | marginLeft: 4,
55 | },
56 | });
57 |
--------------------------------------------------------------------------------
/src/components/AnimatedInput/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./AnimatedIntput";
2 |
--------------------------------------------------------------------------------
/src/components/AppItem/AppItem.constants.ts:
--------------------------------------------------------------------------------
1 | import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
2 |
3 | export const APP_ICON_WIDTH_RATIO = 828 / 128;
4 | export const APP_ICON_SIZE = SCREEN_WIDTH / APP_ICON_WIDTH_RATIO;
5 |
--------------------------------------------------------------------------------
/src/components/AppItem/AppItem.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | import theme from "@react-native-ios/constants/theme";
4 |
5 | import { APP_ICON_SIZE } from "./AppItem.constants";
6 |
7 | export default StyleSheet.create({
8 | container: {},
9 | image: {
10 | width: "100%",
11 | height: "100%",
12 | borderRadius: 17,
13 | },
14 | title: {
15 | position: "absolute",
16 | bottom: -20,
17 | left: -8,
18 | textAlign: "center",
19 | color: "white",
20 | width: APP_ICON_SIZE + 16,
21 | ...theme.font.caption1,
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/src/components/AppItem/AppItem.tsx:
--------------------------------------------------------------------------------
1 | import { Image, Text, View } from "react-native";
2 | import { APP_ICON_SIZE } from "./AppItem.constants";
3 |
4 | import styles from "./AppItem.styles";
5 |
6 | type AppItemProps = {
7 | size?: "small" | undefined;
8 | noTitle?: boolean;
9 | title?: string;
10 | icon: any;
11 | };
12 |
13 | export default function AppItem({ icon, title, size, noTitle }: AppItemProps) {
14 | return (
15 |
25 |
26 | {noTitle || size === "small" ? null : (
27 |
32 | {title ? title : "Square"}
33 |
34 | )}
35 |
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/src/components/AppItem/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./AppItem";
2 |
--------------------------------------------------------------------------------
/src/components/Footer/Footer.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, View } from "react-native";
2 |
3 | import { BlurView } from "expo-blur";
4 |
5 | import AppItem from "@react-native-ios/components/AppItem";
6 | import apps from "@react-native-ios/constants/apps";
7 | import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
8 |
9 | export default function Footer() {
10 | return (
11 |
12 |
13 | {Object.keys(apps.footer).map((item, index) => {
14 | return (
15 |
20 | );
21 | })}
22 |
23 |
24 | );
25 | }
26 |
27 | const styles = StyleSheet.create({
28 | container: {
29 | width: "100%",
30 | display: "flex",
31 | flexDirection: "column",
32 | alignItems: "center",
33 | },
34 | contentContainer: {
35 | display: "flex",
36 | flexDirection: "row",
37 | justifyContent: "space-between",
38 | paddingVertical: 20,
39 | paddingHorizontal: 20,
40 | position: "absolute",
41 | bottom: 24,
42 | width: SCREEN_WIDTH - 24,
43 | borderRadius: 36,
44 | overflow: "hidden",
45 | },
46 | });
47 |
--------------------------------------------------------------------------------
/src/components/Footer/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Footer";
2 |
--------------------------------------------------------------------------------
/src/components/SwipeableProvider/SwipeableProvider.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | import { SCREEN_HEIGHT, SCREEN_WIDTH } from "@react-native-ios/constants/ui";
4 |
5 | export default StyleSheet.create({
6 | pagesContainer: {
7 | width: SCREEN_WIDTH * 2,
8 | height: SCREEN_HEIGHT,
9 | display: "flex",
10 | flexDirection: "row",
11 | },
12 | pageContainer: {
13 | width: SCREEN_WIDTH,
14 | height: SCREEN_HEIGHT,
15 | display: "flex",
16 | },
17 | container: {
18 | width: SCREEN_WIDTH,
19 | height: SCREEN_HEIGHT,
20 | },
21 | searchContainer: {
22 | width: SCREEN_WIDTH,
23 | height: SCREEN_HEIGHT,
24 | ...StyleSheet.absoluteFillObject,
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/src/components/SwipeableProvider/SwipeableProvider.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { GestureDetector } from "react-native-gesture-handler";
4 | import Animated from "react-native-reanimated";
5 |
6 | import Search from "@react-native-ios/screens/Search";
7 | import RightSearch from "@react-native-ios/screens/Search/RightSearch";
8 | import LeftSearch from "@react-native-ios/screens/Search/LeftSearch";
9 | import useSwipeableProvider from "@react-native-ios/hooks/useSwipeableProvider";
10 |
11 | import styles from "./SwipeableProvider.styles";
12 |
13 | type SwipeableProviderProps = {
14 | pages: React.ReactNode[];
15 | };
16 |
17 | export default function SwipeableProvider({ pages }: SwipeableProviderProps) {
18 | const {
19 | offsetY,
20 | offsetX,
21 | startX,
22 | animatedStyles,
23 | animatedPageContainerStyles,
24 | animatedPagesContainerStyles,
25 | swipeableProviderGesture,
26 | } = useSwipeableProvider();
27 |
28 | return (
29 | <>
30 |
31 |
32 |
33 |
34 |
35 |
38 | {pages.map((item) => (
39 |
42 | {item}
43 |
44 | ))}
45 |
46 |
47 |
48 | >
49 | );
50 | }
51 |
--------------------------------------------------------------------------------
/src/components/SwipeableProvider/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./SwipeableProvider";
2 |
--------------------------------------------------------------------------------
/src/components/Text.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { Text as RNText, TextProps } from "react-native";
4 |
5 | export default function Text(props: TextProps) {
6 | return (
7 |
8 | {props.children}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/Wallpaper.tsx:
--------------------------------------------------------------------------------
1 | import { useLayoutEffect, useState } from "react";
2 | import { Image, StyleSheet, View } from "react-native";
3 | import { SCREEN_WIDTH } from "../constants/ui";
4 |
5 | import WalpaperImage from "../assets/img/wallpaper.jpg";
6 |
7 | export default function Wallpaper() {
8 | const [imageWidth, setImageWidth] = useState(0);
9 | const [imageHeight, setImageHeight] = useState(0);
10 |
11 | useLayoutEffect(() => {
12 | const { width, height } = Image.resolveAssetSource(WalpaperImage);
13 | setImageWidth(SCREEN_WIDTH);
14 | setImageHeight((SCREEN_WIDTH * height) / width);
15 | }, []);
16 |
17 | return (
18 |
24 |
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/WidgetItem/WidgetItem.constants.ts:
--------------------------------------------------------------------------------
1 | import theme from "@react-native-ios/constants/theme";
2 | import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
3 |
4 | export const WIDGET_SQUARE_SIZE =
5 | (SCREEN_WIDTH - (theme.spacing.lg + 4) * 2 - 21) / 2;
6 | export const WIDGET_WIDE_SIZE = SCREEN_WIDTH - theme.spacing.lg * 2;
7 |
--------------------------------------------------------------------------------
/src/components/WidgetItem/WidgetItem.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | import theme from "@react-native-ios/constants/theme";
4 |
5 | import { WIDGET_SQUARE_SIZE } from "./WidgetItem.constants";
6 |
7 | export default StyleSheet.create({
8 | container: {
9 | width: WIDGET_SQUARE_SIZE,
10 | height: WIDGET_SQUARE_SIZE,
11 | borderRadius: theme.spacing.lg,
12 | backgroundColor: theme.colors.white.white25,
13 | overflow: "hidden",
14 | marginBottom: theme.spacing.lg,
15 | },
16 | });
17 |
--------------------------------------------------------------------------------
/src/components/WidgetItem/WidgetItem.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { View } from "react-native";
3 |
4 | import { BlurView } from "expo-blur";
5 | import { TapGestureHandler } from "react-native-gesture-handler";
6 |
7 | import { WIDGET_SQUARE_SIZE } from "./WidgetItem.constants";
8 | import styles from "./WidgetItem.styles";
9 |
10 | type WidgetItemProps = {
11 | children?: React.ReactNode;
12 | containerStyles?: object;
13 | onPress?: () => void;
14 | wide?: boolean;
15 | };
16 |
17 | export default function WidgetItem({
18 | children,
19 | containerStyles,
20 | wide,
21 | onPress,
22 | }: WidgetItemProps) {
23 | return (
24 |
25 |
34 |
41 | {children}
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/src/components/WidgetItem/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./WidgetItem";
2 |
--------------------------------------------------------------------------------
/src/configs/horizontalGestureCalculations.ts:
--------------------------------------------------------------------------------
1 | import { SharedValue, withSpring } from "react-native-reanimated";
2 | import { PanGestureHandlerEventPayload } from "react-native-gesture-handler";
3 |
4 | import {
5 | MIN_VELOCITY_Y_TO_ACTIVATE,
6 | SNAP_POINTS_HORIZONTAL_AS_ARRAY,
7 | SPRING_CONFIG,
8 | } from "@react-native-ios/constants/animation";
9 | import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
10 |
11 | type handleOnUpdateHorizontalProps = {
12 | e: PanGestureHandlerEventPayload;
13 | startX: SharedValue;
14 | offsetX: SharedValue;
15 | };
16 |
17 | const getNextSnapPoint = (offset: number) => {
18 | "worklet";
19 |
20 | let nextSnapPointAvailable = false;
21 | let snapPoint = 0;
22 |
23 | for (let i = 2; i < 10; i += 2) {
24 | if (
25 | offset <= SNAP_POINTS_HORIZONTAL_AS_ARRAY[i - 1] &&
26 | offset >= SNAP_POINTS_HORIZONTAL_AS_ARRAY[i + 1]
27 | ) {
28 | snapPoint = SNAP_POINTS_HORIZONTAL_AS_ARRAY[i];
29 | nextSnapPointAvailable = true;
30 | }
31 | }
32 |
33 | return nextSnapPointAvailable ? snapPoint : -1;
34 | };
35 |
36 | export const handleOnEndHorizontal = ({
37 | e,
38 | startX,
39 | offsetX,
40 | }: handleOnUpdateHorizontalProps & {
41 | destination: SharedValue;
42 | }) => {
43 | "worklet";
44 |
45 | const velocity = Math.abs(e.velocityX);
46 | const direction = e.translationX < 0 ? "right" : "left";
47 | const nextXValue =
48 | direction === "right"
49 | ? offsetX.value - SCREEN_WIDTH
50 | : offsetX.value + SCREEN_WIDTH;
51 |
52 | startX.value = startX.value + e.translationX;
53 |
54 | if (velocity > MIN_VELOCITY_Y_TO_ACTIVATE) {
55 | const nextSnapPoint = getNextSnapPoint(nextXValue);
56 | if (nextSnapPoint !== -1) {
57 | offsetX.value = withSpring(nextSnapPoint, {
58 | ...SPRING_CONFIG,
59 | velocity,
60 | });
61 | startX.value = withSpring(nextSnapPoint, {
62 | ...SPRING_CONFIG,
63 | velocity,
64 | });
65 | }
66 | } else {
67 | const nextSnapPoint2 = getNextSnapPoint(offsetX.value + e.translationX);
68 | console.log("else", nextSnapPoint2);
69 | offsetX.value = withSpring(nextSnapPoint2, {
70 | ...SPRING_CONFIG,
71 | velocity,
72 | });
73 | startX.value = withSpring(nextSnapPoint2, {
74 | ...SPRING_CONFIG,
75 | velocity,
76 | });
77 | }
78 | };
79 |
--------------------------------------------------------------------------------
/src/configs/verticalGestureCalculations.ts:
--------------------------------------------------------------------------------
1 | import { SharedValue, withSpring } from "react-native-reanimated";
2 | import { PanGestureHandlerEventPayload } from "react-native-gesture-handler";
3 |
4 | import {
5 | DISTANCE_TO_ACTIVATE,
6 | MAX_OFFSET_TO_ANIMATE,
7 | MIN_VELOCITY_Y_TO_ACTIVATE,
8 | SPRING_CONFIG,
9 | } from "@react-native-ios/constants/animation";
10 |
11 | type handleOnUpdateVerticalProps = {
12 | e: PanGestureHandlerEventPayload;
13 | offsetY: SharedValue;
14 | };
15 |
16 | export const handleOnEndVertical = ({
17 | e,
18 | offsetY,
19 | }: handleOnUpdateVerticalProps) => {
20 | "worklet";
21 |
22 | const velocity = Math.abs(e.velocityY);
23 | const translation = Math.abs(e.translationY);
24 |
25 | if (
26 | translation > DISTANCE_TO_ACTIVATE ||
27 | velocity > MIN_VELOCITY_Y_TO_ACTIVATE
28 | ) {
29 | offsetY.value = withSpring(MAX_OFFSET_TO_ANIMATE, {
30 | ...SPRING_CONFIG,
31 | overshootClamping: false,
32 | velocity,
33 | });
34 | } else {
35 | offsetY.value = withSpring(0, SPRING_CONFIG);
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/src/constants/animation.ts:
--------------------------------------------------------------------------------
1 | import { SCREEN_WIDTH } from "./ui";
2 |
3 | export const BLUR_VIEW_MAX_INTENSITY = 75;
4 | export const MAX_OFFSET_TO_ANIMATE = 90;
5 | export const DISTANCE_TO_ACTIVATE = MAX_OFFSET_TO_ANIMATE / 2;
6 | export const MIN_VELOCITY_Y_TO_ACTIVATE = 100;
7 |
8 | export const SPRING_CONFIG = {
9 | damping: 500,
10 | stiffness: 1000,
11 | mass: 3,
12 | overshootClamping: true,
13 | restDisplacementThreshold: 10,
14 | restSpeedThreshold: 10,
15 | };
16 |
17 | export const SNAP_POINTS_HORIZONTAL = {
18 | LEFT_PAGE: SCREEN_WIDTH,
19 | LEFT_PAGE_HALF: SCREEN_WIDTH / 2,
20 | ORIGIN: 0,
21 | FIRST_PAGE_HALF: SCREEN_WIDTH / -2,
22 | SECOND_PAGE: SCREEN_WIDTH * -1,
23 | SECOND_PAGE_HALF: (SCREEN_WIDTH * -3) / 2,
24 | RIGHT_PAGE: SCREEN_WIDTH * -2, // -750
25 | };
26 |
27 | export const SNAP_POINTS_HORIZONTAL_AS_ARRAY = [
28 | SCREEN_WIDTH * 3,
29 | (SCREEN_WIDTH * 3) / 2,
30 | SCREEN_WIDTH,
31 | SCREEN_WIDTH / 2, // 187.5
32 | 0,
33 | SCREEN_WIDTH / -2, // -187
34 | SCREEN_WIDTH * -1, // -375
35 | (SCREEN_WIDTH * -3) / 2, // -375 + (-375/2) = -562
36 | SCREEN_WIDTH * -2, // -375 * 2 == -750
37 | (SCREEN_WIDTH * -5) / 2, // -375 * 2 + (-375/2) = -937
38 | SCREEN_WIDTH * -3, // -375 * 3 = -1500
39 | (SCREEN_WIDTH * -7) / 2, // -375 * 3 + (-375/2) = -1875
40 | ];
41 |
--------------------------------------------------------------------------------
/src/constants/apps.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | footer: {
3 | settings: {
4 | title: "Settings",
5 | icon: require("../assets/img/app-icons/Settings.png"),
6 | },
7 | safari: {
8 | title: "Safari",
9 | icon: require("../assets/img/app-icons/Safari.png"),
10 | },
11 | photos: {
12 | title: "Photos",
13 | icon: require("../assets/img/app-icons/Photos.png"),
14 | },
15 | messages: {
16 | title: "Messages",
17 | icon: require("../assets/img/app-icons/Messages.png"),
18 | },
19 | },
20 | home: {
21 | row1: [
22 | {
23 | title: "FaceTime",
24 | icon: require("../assets/img/app-icons/Facetime.png"),
25 | },
26 | {
27 | title: "Messages",
28 | icon: require("../assets/img/app-icons/Messages.png"),
29 | },
30 | {
31 | title: "Clock",
32 | icon: require("../assets/img/app-icons/Clock.png"),
33 | },
34 | {
35 | title: "Weather",
36 | icon: require("../assets/img/app-icons/Weather.png"),
37 | },
38 | ],
39 | row2: [
40 | {
41 | title: "Notes",
42 | icon: require("../assets/img/app-icons/Notes.png"),
43 | },
44 | {
45 | title: "Calculator",
46 | icon: require("../assets/img/app-icons/Calculator.png"),
47 | },
48 | {
49 | title: "Wallet",
50 | icon: require("../assets/img/app-icons/Wallet.png"),
51 | },
52 | {
53 | title: "News",
54 | icon: require("../assets/img/app-icons/News.png"),
55 | },
56 | ],
57 | row3: [
58 | {
59 | title: "Compass",
60 | icon: require("../assets/img/app-icons/Compass.png"),
61 | },
62 | {
63 | title: "iTunes",
64 | icon: require("../assets/img/app-icons/iTunes.png"),
65 | },
66 | {
67 | title: "AppStore",
68 | icon: require("../assets/img/app-icons/AppStore.png"),
69 | },
70 | {
71 | title: "Home",
72 | icon: require("../assets/img/app-icons/Home.png"),
73 | },
74 | ],
75 | },
76 | home2: {
77 | row1: [],
78 | },
79 | };
80 |
--------------------------------------------------------------------------------
/src/constants/theme.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | colors: {
3 | white: {
4 | white100: "rgba(255, 255, 255, 1)",
5 | white75: "rgba(255, 255, 255, 0.75)",
6 | white50: "rgba(255, 255, 255, 0.5)",
7 | white25: "rgba(255, 255, 255, 0.25)",
8 | white15: "rgba(255, 255, 255, 0.15)",
9 | },
10 | black: {
11 | black100: "rgba(0, 0, 0, 1)",
12 | black75: "rgba(0, 0, 0, 0.75)",
13 | black50: "rgba(0, 0, 0, 0.5)",
14 | black25: "rgba(0, 0, 0, 0.25)",
15 | },
16 | },
17 | spacing: {
18 | xs: 4,
19 | sm: 7,
20 | sm13: 13,
21 | md: 16,
22 | lg: 24,
23 | xl: 32,
24 | },
25 | font: {
26 | largeTitle: { fontSize: 34, lineHeight: 41 },
27 | title1: { fontSize: 28, lineHeight: 34 },
28 | title2: { fontSize: 22, lineHeight: 28 },
29 | title3: { fontSize: 20, lineHeight: 25 },
30 | headline: { fontSize: 17, lineHeight: 22 },
31 | body: { fontSize: 17, lineHeight: 22 },
32 | callout: { fontSize: 16, lineHeight: 21 },
33 | subhead: { fontSize: 15, lineHeight: 20 },
34 | footnote: { fontSize: 13, lineHeight: 18 },
35 | caption1: { fontSize: 12, lineHeight: 16 },
36 | caption2: { fontSize: 11, lineHeight: 13 },
37 | },
38 | };
39 |
--------------------------------------------------------------------------------
/src/constants/ui.ts:
--------------------------------------------------------------------------------
1 | import { Dimensions } from "react-native";
2 |
3 | const { width, height } = Dimensions.get("screen");
4 |
5 | export const SCREEN_WIDTH = width;
6 | export const SCREEN_HEIGHT = height;
7 |
--------------------------------------------------------------------------------
/src/hooks/useGestureHandler.ts:
--------------------------------------------------------------------------------
1 | import {
2 | handleGestureOnEnd,
3 | handleGestureOnStart,
4 | handleGestureOnUpdate,
5 | } from "@react-native-ios/utils/swipeableGestureHandlers";
6 | import { Gesture } from "react-native-gesture-handler";
7 | import { SharedValue, useSharedValue } from "react-native-reanimated";
8 |
9 | type useGestureHandlerProps = {
10 | startX: SharedValue;
11 | offsetX: SharedValue;
12 | offsetY?: SharedValue;
13 | };
14 |
15 | const useGestureHandler = ({
16 | startX,
17 | offsetX,
18 | offsetY,
19 | }: useGestureHandlerProps) => {
20 | const direction = useSharedValue(0);
21 | const destination = useSharedValue(0);
22 |
23 | const swipeableProviderGesture = Gesture.Pan()
24 | .onStart((e) => {
25 | handleGestureOnStart({
26 | direction,
27 | e,
28 | startX,
29 | });
30 | })
31 | .onUpdate((e) => {
32 | handleGestureOnUpdate({
33 | direction,
34 | e,
35 | startX,
36 | offsetX,
37 | offsetY,
38 | });
39 | })
40 | .onEnd((e) => {
41 | handleGestureOnEnd({
42 | direction,
43 | e,
44 | startX,
45 | offsetX,
46 | offsetY,
47 | destination,
48 | });
49 | });
50 |
51 | return { swipeableProviderGesture };
52 | };
53 |
54 | export default useGestureHandler;
55 |
--------------------------------------------------------------------------------
/src/hooks/useSwipeableProvider.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Extrapolate,
3 | interpolate,
4 | useAnimatedStyle,
5 | useSharedValue,
6 | } from "react-native-reanimated";
7 | import { useSafeAreaInsets } from "react-native-safe-area-context";
8 |
9 | import {
10 | MAX_OFFSET_TO_ANIMATE,
11 | SNAP_POINTS_HORIZONTAL,
12 | } from "@react-native-ios/constants/animation";
13 | import theme from "@react-native-ios/constants/theme";
14 |
15 | import useGestureHandler from "./useGestureHandler";
16 |
17 | const useSwipeableProvider = () => {
18 | const { top } = useSafeAreaInsets();
19 | const offsetY = useSharedValue(0);
20 | const offsetX = useSharedValue(0);
21 | const startX = useSharedValue(0);
22 | const { swipeableProviderGesture } = useGestureHandler({
23 | offsetY,
24 | offsetX,
25 | startX,
26 | });
27 |
28 | const animatedStyles = useAnimatedStyle(() => {
29 | return {
30 | transform: [{ translateY: offsetY.value }],
31 | opacity: interpolate(
32 | offsetY.value,
33 | [0, MAX_OFFSET_TO_ANIMATE],
34 | [1, 0.6],
35 | Extrapolate.CLAMP
36 | ),
37 | paddingTop: top + theme.spacing.md,
38 | };
39 | });
40 |
41 | const animatedPageContainerStyles = useAnimatedStyle(() => {
42 | return {
43 | transform: [
44 | {
45 | scale:
46 | offsetX.value > 0
47 | ? interpolate(
48 | offsetX.value,
49 | [
50 | SNAP_POINTS_HORIZONTAL.LEFT_PAGE,
51 | SNAP_POINTS_HORIZONTAL.ORIGIN,
52 | ],
53 | [0.85, 1],
54 | Extrapolate.CLAMP
55 | )
56 | : interpolate(
57 | offsetX.value,
58 | [
59 | SNAP_POINTS_HORIZONTAL.SECOND_PAGE,
60 | SNAP_POINTS_HORIZONTAL.RIGHT_PAGE,
61 | ],
62 | [1, 0.85],
63 | Extrapolate.CLAMP
64 | ),
65 | },
66 | ],
67 | };
68 | });
69 |
70 | const animatedPagesContainerStyles = useAnimatedStyle(() => {
71 | return {
72 | transform: [
73 | {
74 | translateX: Math.min(
75 | Math.max(offsetX.value, SNAP_POINTS_HORIZONTAL.SECOND_PAGE),
76 | SNAP_POINTS_HORIZONTAL.ORIGIN
77 | ),
78 | },
79 | ],
80 | };
81 | }, [offsetX]);
82 |
83 | return {
84 | offsetY,
85 | offsetX,
86 | startX,
87 | animatedStyles,
88 | animatedPageContainerStyles,
89 | animatedPagesContainerStyles,
90 | swipeableProviderGesture,
91 | };
92 | };
93 |
94 | export default useSwipeableProvider;
95 |
--------------------------------------------------------------------------------
/src/screens/Home/Home.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, View } from "react-native";
2 |
3 | import { SCREEN_HEIGHT, SCREEN_WIDTH } from "@react-native-ios/constants/ui";
4 |
5 | import AppItem from "@react-native-ios/components/AppItem";
6 | import apps from "@react-native-ios/constants/apps";
7 |
8 | export function Page1() {
9 | return (
10 |
11 |
12 | {apps.home.row1.map((item) => {
13 | return ;
14 | })}
15 |
16 |
17 | {apps.home.row2.map((item) => {
18 | return ;
19 | })}
20 |
21 |
22 | {apps.home.row3.map((item) => {
23 | return ;
24 | })}
25 |
26 |
27 | );
28 | }
29 |
30 | export function Page2() {
31 | return (
32 |
33 |
34 | {apps.home.row3.map((item) => {
35 | return ;
36 | })}
37 |
38 |
39 | {apps.home.row2.map((item) => {
40 | return ;
41 | })}
42 |
43 |
44 | {apps.home.row1.map((item) => {
45 | return ;
46 | })}
47 |
48 |
49 | );
50 | }
51 |
52 | const styles = StyleSheet.create({
53 | gestureContainer: {
54 | width: SCREEN_WIDTH * 2,
55 | height: SCREEN_HEIGHT,
56 | display: "flex",
57 | flexDirection: "row",
58 | },
59 | container: {
60 | flex: 1,
61 | width: SCREEN_WIDTH,
62 | display: "flex",
63 | flexDirection: "column",
64 | },
65 | row: {
66 | display: "flex",
67 | flexDirection: "row",
68 | width: "100%",
69 | justifyContent: "space-between",
70 | paddingHorizontal: 24,
71 | marginBottom: 32,
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/src/screens/Home/index.ts:
--------------------------------------------------------------------------------
1 | export { Page1, Page2 } from "./Home";
2 |
--------------------------------------------------------------------------------
/src/screens/Search/AnimatedProvider.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Keyboard, StyleSheet, View } from "react-native";
3 |
4 | import { BlurView } from "expo-blur";
5 | import { TapGestureHandler } from "react-native-gesture-handler";
6 | import Animated, {
7 | Extrapolate,
8 | interpolate,
9 | SharedValue,
10 | useAnimatedProps,
11 | useAnimatedStyle,
12 | useSharedValue,
13 | withSpring,
14 | } from "react-native-reanimated";
15 | import { useSafeAreaInsets } from "react-native-safe-area-context";
16 |
17 | const AnimatedBlurView = Animated.createAnimatedComponent(BlurView);
18 |
19 | import {
20 | BLUR_VIEW_MAX_INTENSITY,
21 | MAX_OFFSET_TO_ANIMATE,
22 | SNAP_POINTS_HORIZONTAL,
23 | SPRING_CONFIG,
24 | } from "@react-native-ios/constants/animation";
25 | import { SCREEN_HEIGHT, SCREEN_WIDTH } from "@react-native-ios/constants/ui";
26 | import theme from "@react-native-ios/constants/theme";
27 |
28 | type AnimatedProviderProps = {
29 | startPoint: number;
30 | snapPoint: number;
31 | direction: "left" | "right" | "vertical";
32 | offset: SharedValue;
33 | start?: SharedValue;
34 | children: React.ReactNode;
35 | };
36 |
37 | export default function AnimatedProvider({
38 | startPoint = 0,
39 | snapPoint = 0,
40 | direction,
41 | offset,
42 | start,
43 | children,
44 | }: AnimatedProviderProps) {
45 | const { top } = useSafeAreaInsets();
46 | const direMultiply = useSharedValue(direction == "right" ? -1 : 1);
47 |
48 | const animatedBlurBackdropStyles = useAnimatedStyle(() => {
49 | return {
50 | zIndex:
51 | offset.value * direMultiply.value > startPoint * direMultiply.value
52 | ? 10
53 | : -1,
54 | };
55 | });
56 |
57 | const animatedBlurBackdropProps = useAnimatedProps(() => {
58 | return {
59 | intensity: interpolate(
60 | offset.value * direMultiply.value,
61 | [startPoint * direMultiply.value, snapPoint * direMultiply.value],
62 | [0, BLUR_VIEW_MAX_INTENSITY],
63 | Extrapolate.CLAMP
64 | ),
65 | };
66 | });
67 |
68 | const animatedContentStyles = useAnimatedStyle(
69 | () => ({
70 | opacity:
71 | direction == "vertical"
72 | ? interpolate(
73 | offset.value * direMultiply.value,
74 | [startPoint * direMultiply.value, snapPoint * direMultiply.value],
75 | [0, 1],
76 | Extrapolate.CLAMP
77 | )
78 | : 1,
79 | transform:
80 | direction === "vertical"
81 | ? [{ translateY: offset.value - MAX_OFFSET_TO_ANIMATE }]
82 | : [
83 | {
84 | translateX: interpolate(
85 | offset.value * direMultiply.value,
86 | [
87 | startPoint * direMultiply.value,
88 | snapPoint * direMultiply.value,
89 | ],
90 | [SCREEN_WIDTH * (direction === "right" ? 1 : -1), 0],
91 | Extrapolate.CLAMP
92 | ),
93 | },
94 | ],
95 | }),
96 | [offset]
97 | );
98 |
99 | const handleTapOutside = () => {
100 | offset.value = withSpring(startPoint, SPRING_CONFIG);
101 | if (start) start.value = withSpring(startPoint, SPRING_CONFIG);
102 | Keyboard.dismiss();
103 | };
104 |
105 | return (
106 |
111 |
112 |
113 |
120 | {children}
121 |
122 |
123 |
124 |
125 | );
126 | }
127 |
128 | const styles = StyleSheet.create({
129 | container: {
130 | width: SCREEN_WIDTH,
131 | height: SCREEN_HEIGHT,
132 | },
133 | contentContainer: {
134 | display: "flex",
135 | flexDirection: "column",
136 | paddingHorizontal: theme.spacing.sm,
137 | },
138 | blurBackdrop: {
139 | width: SCREEN_WIDTH,
140 | height: SCREEN_HEIGHT,
141 | ...StyleSheet.absoluteFillObject,
142 | },
143 | });
144 |
--------------------------------------------------------------------------------
/src/screens/Search/LeftSearch.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { SharedValue } from "react-native-reanimated";
4 |
5 | import { SNAP_POINTS_HORIZONTAL } from "@react-native-ios/constants/animation";
6 | import LeftSearchContent from "./components/LeftSearchContent";
7 | import AnimatedProvider from "./AnimatedProvider";
8 |
9 | type AnimatedProviderProps = {
10 | offsetX: SharedValue;
11 | startX: SharedValue;
12 | };
13 |
14 | export default function LeftSearch({ offsetX, startX }: AnimatedProviderProps) {
15 | return (
16 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/src/screens/Search/RightSearch.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { SharedValue } from "react-native-reanimated";
4 |
5 | import { SNAP_POINTS_HORIZONTAL } from "@react-native-ios/constants/animation";
6 | import LeftSearchContent from "./components/LeftSearchContent";
7 | import AnimatedProvider from "./AnimatedProvider";
8 |
9 | type AnimatedProviderProps = {
10 | offsetX: SharedValue;
11 | startX: SharedValue;
12 | };
13 |
14 | export default function LeftSearch({ offsetX, startX }: AnimatedProviderProps) {
15 | return (
16 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/src/screens/Search/Search.styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | import theme from "@react-native-ios/constants/theme";
4 |
5 | export default StyleSheet.create({
6 | searchContainer: {
7 | display: "flex",
8 | flexDirection: "row",
9 | alignItems: "center",
10 | marginBottom: theme.spacing.md,
11 | },
12 | searchInputContainer: {
13 | flex: 1,
14 | },
15 | searchIconContainer: {
16 | position: "absolute",
17 | top: theme.spacing.sm + theme.spacing.xs - 1,
18 | left: theme.spacing.sm + theme.spacing.xs,
19 | },
20 | michrophoneIconContainer: {
21 | position: "absolute",
22 | top: 8,
23 | right: 12 + theme.spacing.sm,
24 | },
25 | searchInput: {
26 | paddingVertical: theme.spacing.sm,
27 | paddingLeft: (theme.spacing.sm + theme.spacing.xs) * 2 + 14,
28 | paddingRight: theme.spacing.sm * 2 + theme.spacing.xs + 12,
29 | backgroundColor: theme.colors.white.white15,
30 | color: theme.colors.white.white75,
31 | borderRadius: 12,
32 | marginRight: 8,
33 | ...theme.font.body,
34 | },
35 | cancelText: {
36 | color: theme.colors.white.white50,
37 | ...theme.font.body,
38 | },
39 | titleSectionContainer: {
40 | display: "flex",
41 | flexDirection: "row",
42 | justifyContent: "space-between",
43 | alignItems: "center",
44 | paddingHorizontal: theme.spacing.xs,
45 | marginBottom: theme.spacing.xs,
46 | },
47 | titleText: {
48 | ...theme.font.title2,
49 | fontWeight: "600",
50 | color: "white",
51 | },
52 | showMoreText: {
53 | ...theme.font.footnote,
54 | color: theme.colors.white.white50,
55 | },
56 | appsContainer: {
57 | display: "flex",
58 | flexDirection: "column",
59 | padding: theme.spacing.md,
60 | borderRadius: theme.spacing.md,
61 | width: "100%",
62 | backgroundColor: theme.colors.white.white15,
63 | },
64 | row: {
65 | display: "flex",
66 | flexDirection: "row",
67 | justifyContent: "space-between",
68 | marginBottom: theme.spacing.xl,
69 | },
70 | });
71 |
--------------------------------------------------------------------------------
/src/screens/Search/Search.tsx:
--------------------------------------------------------------------------------
1 | import { MAX_OFFSET_TO_ANIMATE } from "@react-native-ios/constants/animation";
2 | import React from "react";
3 |
4 | import { SharedValue } from "react-native-reanimated";
5 |
6 | import AnimatedProvider from "./AnimatedProvider";
7 | import SearchContent from "./SearchContent";
8 |
9 | type SearchProps = {
10 | offsetY: SharedValue;
11 | };
12 |
13 | export default function Search({ offsetY }: SearchProps) {
14 | return (
15 |
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/screens/Search/SearchContent.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Pressable, Text, View } from "react-native";
3 |
4 | import { TextInput } from "react-native-gesture-handler";
5 |
6 | import SearchSVG from "@react-native-ios/assets/svg/search.svg";
7 | import MichrophoneSVG from "@react-native-ios/assets/svg/michrophone.svg";
8 | import ChevronRightSVG from "@react-native-ios/assets/svg/chevron-right.svg";
9 | import AppItem from "@react-native-ios/components/AppItem";
10 | import apps from "@react-native-ios/constants/apps";
11 |
12 | import styles from "./Search.styles";
13 | import theme from "@react-native-ios/constants/theme";
14 |
15 | export default function SearchContent() {
16 | return (
17 | <>
18 |
19 |
20 |
21 |
26 |
27 |
28 |
33 |
34 |
42 |
43 |
44 | Cancel
45 |
46 |
47 |
48 | Siri Suggestions
49 |
50 |
51 |
52 |
53 | {apps.home.row3.map((item) => {
54 | return ;
55 | })}
56 |
57 |
58 | {apps.home.row1.map((item) => {
59 | return ;
60 | })}
61 |
62 |
63 | >
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/src/screens/Search/components/LeftSearchContent.styles.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 |
3 | import theme from "@react-native-ios/constants/theme";
4 |
5 | export default StyleSheet.create({
6 | searchContainer: {
7 | display: "flex",
8 | flexDirection: "row",
9 | alignItems: "center",
10 | justifyContent: "center",
11 | paddingHorizontal: theme.spacing.lg - theme.spacing.sm,
12 | },
13 | searchInputContainer: {
14 | flex: 1,
15 | paddingVertical: theme.spacing.md,
16 | borderRadius: theme.spacing.md,
17 | backgroundColor: theme.colors.white.white15,
18 | display: "flex",
19 | flexDirection: "row",
20 | justifyContent: "center",
21 | },
22 | searchIconContainer: {
23 | position: "absolute",
24 | top: theme.spacing.sm + theme.spacing.xs - 1,
25 | left: theme.spacing.sm + theme.spacing.xs,
26 | },
27 | michrophoneIconContainer: {
28 | position: "absolute",
29 | top: theme.spacing.sm,
30 | right: 12 + theme.spacing.sm,
31 | },
32 | searchInput: {
33 | paddingHorizontal: theme.spacing.sm,
34 | color: theme.colors.white.white75,
35 | ...theme.font.body,
36 | },
37 | cancelButton: {
38 | position: "absolute",
39 | right: 0,
40 | opacity: 0,
41 | },
42 | cancelText: {
43 | color: theme.colors.white.white50,
44 | ...theme.font.body,
45 | },
46 | appsContainer: {
47 | display: "flex",
48 | flexDirection: "column",
49 | padding: theme.spacing.lg + 4,
50 | paddingHorizontal: 56 / 2 - 8,
51 | paddingTop: 0,
52 | width: "100%",
53 | backgroundColor: "transparent",
54 | overflow: "hidden",
55 | },
56 | row: {
57 | display: "flex",
58 | flexDirection: "row",
59 | justifyContent: "space-between",
60 | },
61 | center: {
62 | display: "flex",
63 | flexDirection: "column",
64 | alignItems: "center",
65 | justifyContent: "center",
66 | width: "100%",
67 | height: "100%",
68 | },
69 | column: {
70 | display: "flex",
71 | flexDirection: "column",
72 | alignItems: "flex-start",
73 | justifyContent: "flex-start",
74 | height: "100%",
75 | width: "100%",
76 | position: "relative",
77 | padding: 24,
78 | },
79 | linkIconContainer: {
80 | position: "absolute",
81 | right: 16,
82 | bottom: 16,
83 | },
84 | textBody: {
85 | ...theme.font.body,
86 | color: theme.colors.white.white50,
87 | },
88 | textBodySecondary: {
89 | marginLeft: 8,
90 | ...theme.font.body,
91 | color: theme.colors.white.white75,
92 | },
93 | });
94 |
--------------------------------------------------------------------------------
/src/screens/Search/components/LeftSearchContent.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Text, View } from "react-native";
3 |
4 | import * as WebBrowser from "expo-web-browser";
5 |
6 | import ELetterSVG from "@react-native-ios/assets/svg/e-letter.svg";
7 | import GithubSVG from "@react-native-ios/assets/svg/github.svg";
8 | import LinkSVG from "@react-native-ios/assets/svg/link.svg";
9 | import MessageSVG from "@react-native-ios/assets/svg/message.svg";
10 | import WidgetItem from "@react-native-ios/components/WidgetItem";
11 | import AnimatedInput from "@react-native-ios/components/AnimatedInput";
12 |
13 | import styles from "./LeftSearchContent.styles";
14 | import theme from "@react-native-ios/constants/theme";
15 |
16 | export default function LeftSearchContent() {
17 | const handleNavigateWebsite = async () => {
18 | await WebBrowser.openBrowserAsync("https://ozturkenes.com");
19 | };
20 |
21 | const handleNavigateToGithub = async () => {
22 | await WebBrowser.openBrowserAsync("https://github.com/enesozturk");
23 | };
24 |
25 | return (
26 | <>
27 |
28 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
44 |
45 |
46 |
47 |
48 |
49 |
61 |
62 |
67 |
68 |
69 | Did you like this app? Give me a star and follow me on Github for
70 | more ⭐️
71 |
72 |
82 |
87 |
88 | enesozturk/react-native-ios
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
101 |
102 |
103 | You need a React & React Native developer? Contact me!
104 |
105 |
115 |
120 |
121 | enesozturk.d@gmail.com
122 |
123 |
124 |
125 |
126 |
127 | >
128 | );
129 | }
130 |
--------------------------------------------------------------------------------
/src/screens/Search/index.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./Search";
2 |
--------------------------------------------------------------------------------
/src/utils/swipeableGestureHandlers.ts:
--------------------------------------------------------------------------------
1 | import { PanGestureHandlerEventPayload } from "react-native-gesture-handler";
2 | import { cancelAnimation, SharedValue } from "react-native-reanimated";
3 |
4 | import { handleOnEndHorizontal } from "@react-native-ios/configs/horizontalGestureCalculations";
5 | import { handleOnEndVertical } from "@react-native-ios/configs/verticalGestureCalculations";
6 |
7 | type GestureHandlerStartProps = {
8 | direction: SharedValue;
9 | e: PanGestureHandlerEventPayload;
10 | startX: SharedValue;
11 | };
12 |
13 | type GestureHandlerProps = {
14 | direction: SharedValue;
15 | e: PanGestureHandlerEventPayload;
16 | offsetY?: SharedValue;
17 | startX: SharedValue;
18 | offsetX: SharedValue;
19 | };
20 |
21 | export const handleGestureOnStart = ({
22 | e,
23 | direction,
24 | startX,
25 | }: GestureHandlerStartProps) => {
26 | "worklet";
27 |
28 | cancelAnimation(startX);
29 | direction.value = Math.abs(e.translationX) > Math.abs(e.translationY) ? 1 : 0;
30 | };
31 |
32 | export const handleGestureOnUpdate = ({
33 | direction,
34 | offsetY,
35 | offsetX,
36 | startX,
37 | e,
38 | }: GestureHandlerProps) => {
39 | "worklet";
40 |
41 | if (direction.value) {
42 | cancelAnimation(startX);
43 | offsetX.value = e.translationX + startX.value;
44 | } else {
45 | if (offsetY) offsetY.value = Math.max(0, e.translationY);
46 | }
47 | };
48 |
49 | export const handleGestureOnEnd = ({
50 | direction,
51 | offsetY,
52 | offsetX,
53 | startX,
54 | e,
55 | destination,
56 | }: GestureHandlerProps & { destination: SharedValue }) => {
57 | "worklet";
58 | if (direction.value) {
59 | handleOnEndHorizontal({
60 | e,
61 | startX,
62 | offsetX,
63 | destination,
64 | });
65 | } else {
66 | if (offsetY)
67 | handleOnEndVertical({
68 | e,
69 | offsetY,
70 | });
71 | }
72 | };
73 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true,
5 | "baseUrl": "./src",
6 | "paths": {
7 | "@react-native-ios/api/*": ["./api/*"],
8 | "@react-native-ios/assets/*": ["./assets/*"],
9 | "@react-native-ios/configs/*": ["./configs/*"],
10 | "@react-native-ios/constants/*": ["./constants/*"],
11 | "@react-native-ios/components/*": ["./components/*"],
12 | "@react-native-ios/helpers/*": ["./helpers/*"],
13 | "@react-native-ios/hooks/*": ["./hooks/*"],
14 | "@react-native-ios/jest/*": ["./jest/*"],
15 | "@react-native-ios/navigators/*": ["./navigators/*"],
16 | "@react-native-ios/providers/*": ["./providers/*"],
17 | "@react-native-ios/screens/*": ["./screens/*"],
18 | "@react-native-ios/store/*": ["./store/*"],
19 | "@react-native-ios/storybook/*": ["./storybook/*"],
20 | "@react-native-ios/translations/*": ["./translations/*"],
21 | "@react-native-ios/utils/*": ["./utils/*"]
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------