Repository: enesozturk/react-native-ios
Branch: main
Commit: 89f73c56a514
Files: 53
Total size: 53.8 KB
Directory structure:
gitextract_g5jugsi7/
├── .expo-shared/
│ └── assets.json
├── .gitignore
├── .svgrrc
├── App.tsx
├── LICENSE
├── README.md
├── app.json
├── babel.config.aliases.js
├── babel.config.js
├── declarations.d.ts
├── metro.config.js
├── package.json
├── src/
│ ├── 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
================================================
FILE CONTENTS
================================================
================================================
FILE: .expo-shared/assets.json
================================================
{
"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
"40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
}
================================================
FILE: .gitignore
================================================
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# macOS
.DS_Store
================================================
FILE: .svgrrc
================================================
{
"memo": true,
"native": true,
"replaceAttrValues": {
"fill": "{props.fill}",
"stroke": "{props.stroke}"
}
}
================================================
FILE: App.tsx
================================================
import { StyleSheet } from "react-native";
import { StatusBar } from "expo-status-bar";
import { SafeAreaProvider } from "react-native-safe-area-context";
import Wallpaper from "@react-native-ios/components/Wallpaper";
import Footer from "@react-native-ios/components/Footer/Footer";
import SwipeableProvider from "@react-native-ios/components/SwipeableProvider";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { Page1, Page2 } from "@react-native-ios/screens/Home";
export default function App() {
return (
<GestureHandlerRootView style={styles.gestureHandler}>
<SafeAreaProvider>
<StatusBar style="light" />
<Wallpaper />
<SwipeableProvider pages={[<Page1 />, <Page2 />]} />
<Footer />
</SafeAreaProvider>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
gestureHandler: {
flex: 1,
},
});
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Enes Ozturk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<!-- Banner Image -->
<p align="center">
<img alt="app icon" height="128" src="./assets/icon.png">
<h1 align="center">React Native iOS</h1>
</p>
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!
## Stack
- [Expo](https://expo.dev/) (SDK 44)
- TypeScript
- [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/)
- [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/)
- [Expo Blur](https://github.com/expo/expo/tree/master/packages/expo-blur) for animatable blur component
## Want to try?
If you want to try this on your device, you can install and run the app in a few seconds with the following commands;
Install the packages:
```
yarn install
```
Start the server
```bash
yarn start
```
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.
> 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.
That's it, enjoy 🤞🏽
## License
The source code is made available under the [MIT license](./LICENSE).
## Show Your Support
If you like this project, please give a star and follow me on Github for more 🤩
================================================
FILE: app.json
================================================
{
"expo": {
"name": "react-native-ios",
"slug": "react-native-ios",
"privacy": "public",
"platforms": ["ios"],
"sdkVersion": "44.0.0",
"githubUrl": "https://github.com/enesozturk/react-native-ios",
"version": "1.0.2",
"orientation": "portrait",
"owner": "enesozturk",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "cover",
"backgroundColor": "#000000"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.enesozturk.react-native-ios"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
================================================
FILE: babel.config.aliases.js
================================================
module.exports = {
"@react-native-ios/api": "./src/api",
"@react-native-ios/assets": "./src/assets",
"@react-native-ios/components": "./src/components",
"@react-native-ios/configs": "./src/configs",
"@react-native-ios/constants": "./src/constants",
"@react-native-ios/helpers": "./src/helpers",
"@react-native-ios/hooks": "./src/hooks",
"@react-native-ios/navigators": "./src/navigators",
"@react-native-ios/providers": "./src/providers",
"@react-native-ios/screens": "./src/screens",
"@react-native-ios/store": "./src/store",
"@react-native-ios/storybook": "./src/storybook",
"@react-native-ios/translations": "./src/translations",
"@react-native-ios/utils": "./src/utils",
};
================================================
FILE: babel.config.js
================================================
const aliases = require("./babel.config.aliases");
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
["module-resolver", { root: ["./"], alias: aliases }],
"react-native-reanimated/plugin",
],
};
};
================================================
FILE: declarations.d.ts
================================================
declare module "*.svg" {
import React from "react";
import { SvgProps } from "react-native-svg";
const content: React.FC<SvgProps>;
export default content;
}
declare module "*.jpg";
================================================
FILE: metro.config.js
================================================
const { getDefaultConfig } = require("expo/metro-config");
module.exports = (async () => {
const {
resolver: { sourceExts, assetExts },
} = await getDefaultConfig(__dirname);
return {
...getDefaultConfig,
transformer: {
babelTransformerPath: require.resolve("react-native-svg-transformer"),
getTransformOptions: async () => ({
transform: {
inlineRequires: true,
},
}),
},
resolver: {
assetExts: assetExts.filter((ext) => ext !== "svg"),
sourceExts: [...sourceExts, "svg"],
},
};
})();
================================================
FILE: package.json
================================================
{
"name": "react-native-ios",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"expo": "~44.0.0",
"expo-blur": "~11.0.0",
"expo-status-bar": "~1.2.0",
"expo-web-browser": "~10.1.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.3",
"react-native-gesture-handler": "~2.1.0",
"react-native-reanimated": "~2.3.1",
"react-native-safe-area-context": "3.3.2",
"react-native-svg": "^12.1.1",
"react-native-web": "0.17.1"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@svgr/cli": "^6.2.0",
"@types/react": "~17.0.21",
"@types/react-native": "~0.64.12",
"babel-plugin-module-resolver": "^4.1.0",
"react-native-svg-transformer": "^1.0.0",
"typescript": "~4.3.5"
},
"private": true
}
================================================
FILE: src/components/AnimatedInput/AnimatedInput.hooks.ts
================================================
import {
Extrapolate,
interpolate,
useAnimatedStyle,
useSharedValue,
} from "react-native-reanimated";
import theme from "@react-native-ios/constants/theme";
import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
const INPUT_WIDTH = SCREEN_WIDTH - 54;
const useAnimatedInput = () => {
const containerWidth = useSharedValue(0);
const cancelTextWidth = useSharedValue(0);
const active = useSharedValue(0);
const animatedContainerStyles = useAnimatedStyle(
() => ({
width: "100%",
}),
[]
);
const animatedContentContainerStyles = useAnimatedStyle(
() => ({
height: interpolate(
active.value,
[0, 1],
[22 + 26, 22 + 12], // 48 - 24 24/2 14, 38-20 18/2 7
Extrapolate.CLAMP
),
width: interpolate(
active.value,
[0, 1],
[INPUT_WIDTH, INPUT_WIDTH - cancelTextWidth.value],
Extrapolate.CLAMP
),
borderRadius: interpolate(
active.value,
[0, 1],
[theme.spacing.md, 12],
Extrapolate.CLAMP
),
}),
[cancelTextWidth]
);
const animatedInputContainerStyles = useAnimatedStyle(
() => ({
position: "absolute",
left: interpolate(
active.value,
[0, 1],
[containerWidth.value / 2, 0],
Extrapolate.CLAMP
),
}),
[]
);
const animatedInputStyles = useAnimatedStyle(
() => ({
marginLeft: interpolate(
active.value,
[0, 1],
[INPUT_WIDTH / 2 - 30, (theme.spacing.sm + theme.spacing.xs) * 2 - 2],
Extrapolate.CLAMP
),
marginBottom: interpolate(
active.value,
[0, 1],
[4, 2],
Extrapolate.CLAMP
),
transform: [
{
scale: interpolate(
active.value,
[0, 1],
[1, 0.84],
Extrapolate.CLAMP
),
},
],
}),
[]
);
const animatedCancelTextStyles = useAnimatedStyle(
() => ({
opacity: active.value,
}),
[]
);
const animatedSearchIconStyles = useAnimatedStyle(
() => ({
opacity: 1,
top: interpolate(active.value, [0, 1], [12, 8], Extrapolate.CLAMP),
left: interpolate(
active.value,
[0, 1],
[INPUT_WIDTH / 2 - 30 - 32, 8],
Extrapolate.CLAMP
),
}),
[]
);
const animatedSearchIconProps = useAnimatedStyle(
() => ({
width: interpolate(active.value, [0, 1], [20, 18], Extrapolate.CLAMP),
height: interpolate(active.value, [0, 1], [20, 18], Extrapolate.CLAMP),
}),
[]
);
const animatedMicIconStyles = useAnimatedStyle(
() => ({
opacity: active.value,
top: interpolate(active.value, [0, 1], [14, 8], Extrapolate.CLAMP),
}),
[]
);
return {
active,
cancelTextWidth,
containerWidth,
animatedSearchIconStyles,
animatedSearchIconProps,
animatedMicIconStyles,
animatedContainerStyles,
animatedContentContainerStyles,
animatedInputContainerStyles,
animatedInputStyles,
animatedCancelTextStyles,
};
};
export default useAnimatedInput;
================================================
FILE: src/components/AnimatedInput/AnimatedInput.styles.ts
================================================
import { StyleSheet } from "react-native";
import theme from "@react-native-ios/constants/theme";
import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
export default StyleSheet.create({
container: {
display: "flex",
flexDirection: "row",
justifyContent: "flex-start",
alignItems: "center",
marginBottom: theme.spacing.lg,
paddingHorizontal: 56 / 2 - 8,
},
contentContainer: {
width: SCREEN_WIDTH - theme.spacing.lg,
display: "flex",
flexDirection: "row",
position: "relative",
backgroundColor: theme.colors.white.white25,
},
input: {
paddingHorizontal: theme.spacing.sm,
paddingRight: 36,
color: theme.colors.white.white75,
...theme.font.title2,
},
searchIconContainer: {
position: "absolute",
},
michrophoneIconContainer: {
position: "absolute",
right: 0,
},
cancelText: {
...theme.font.body,
textAlignVertical: "center",
color: theme.colors.white.white50,
height: "100%",
paddingVertical: theme.spacing.sm13,
},
});
================================================
FILE: src/components/AnimatedInput/AnimatedIntput.tsx
================================================
import React from "react";
import { TextInput, View } from "react-native";
import Animated, { withTiming } from "react-native-reanimated";
import { TapGestureHandler } from "react-native-gesture-handler";
import theme from "@react-native-ios/constants/theme";
import SearchSVG from "@react-native-ios/assets/svg/search.svg";
import MichrophoneSVG from "@react-native-ios/assets/svg/michrophone.svg";
import styles from "./AnimatedInput.styles";
import useAnimatedInput from "./AnimatedInput.hooks";
import { useRef } from "react";
import CancelButton from "./CancelButton";
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
const AnimatedSearchIcon = Animated.createAnimatedComponent(SearchSVG);
export default function AnimatedIntput() {
const {
active,
cancelTextWidth,
containerWidth,
animatedSearchIconStyles,
animatedMicIconStyles,
animatedSearchIconProps,
animatedContainerStyles,
animatedContentContainerStyles,
animatedInputStyles,
animatedCancelTextStyles,
} = useAnimatedInput();
const inputRef = useRef(null);
return (
<TapGestureHandler onEnded={() => {}}>
<Animated.View style={[styles.container, animatedContainerStyles]}>
<TapGestureHandler
onEnded={() => {
inputRef?.current?.focus();
active.value = withTiming(1, { duration: 300 });
}}
>
<Animated.View
onLayout={(e) => {
containerWidth.value = e.nativeEvent.layout.width;
}}
style={[styles.contentContainer, animatedContentContainerStyles]}
>
<Animated.View
style={[styles.searchIconContainer, animatedSearchIconStyles]}
>
<AnimatedSearchIcon
animatedProps={animatedSearchIconProps}
fill={theme.colors.white.white50}
/>
</Animated.View>
<Animated.View
style={[styles.michrophoneIconContainer, animatedMicIconStyles]}
>
<MichrophoneSVG height={18} fill={theme.colors.white.white50} />
</Animated.View>
<AnimatedTextInput
ref={inputRef}
placeholder="Search"
placeholderTextColor={theme.colors.white.white75}
autoCorrect={false}
autoCompleteType="off"
keyboardAppearance="dark"
style={[styles.input, animatedInputStyles]}
onFocus={() => {
active.value = withTiming(1, { duration: 300 });
}}
onBlur={() => {
active.value = withTiming(0, { duration: 300 });
}}
/>
</Animated.View>
</TapGestureHandler>
<CancelButton
active={active}
animatedCancelTextStyles={animatedCancelTextStyles}
onClose={() => {
inputRef?.current?.blur();
}}
cancelTextWidth={cancelTextWidth}
/>
</Animated.View>
</TapGestureHandler>
);
}
================================================
FILE: src/components/AnimatedInput/CancelButton.tsx
================================================
import React from "react";
import { StyleSheet, View } from "react-native";
import { TapGestureHandler } from "react-native-gesture-handler";
import Animated, { SharedValue, withSpring } from "react-native-reanimated";
import { SPRING_CONFIG } from "@react-native-ios/constants/animation";
import theme from "@react-native-ios/constants/theme";
type CancelButtonProps = {
active: SharedValue<number>;
cancelTextWidth: SharedValue<number>;
animatedCancelTextStyles: any;
onClose: () => void;
};
export default function CancelButton({
active,
cancelTextWidth,
animatedCancelTextStyles,
onClose,
}: CancelButtonProps) {
return (
<View
style={styles.container}
onLayout={(e) => {
cancelTextWidth.value = e.nativeEvent.layout.width;
}}
>
<TapGestureHandler
onEnded={() => {
onClose?.();
active.value = withSpring(0, SPRING_CONFIG);
}}
>
<Animated.Text style={[styles.cancelText, animatedCancelTextStyles]}>
Cancel
</Animated.Text>
</TapGestureHandler>
</View>
);
}
const styles = StyleSheet.create({
container: {
paddingVertical: theme.spacing.sm13,
display: "flex",
flexDirection: "row",
},
cancelText: {
...theme.font.body,
color: theme.colors.white.white50,
height: "100%",
marginLeft: 4,
},
});
================================================
FILE: src/components/AnimatedInput/index.ts
================================================
export { default } from "./AnimatedIntput";
================================================
FILE: src/components/AppItem/AppItem.constants.ts
================================================
import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
export const APP_ICON_WIDTH_RATIO = 828 / 128;
export const APP_ICON_SIZE = SCREEN_WIDTH / APP_ICON_WIDTH_RATIO;
================================================
FILE: src/components/AppItem/AppItem.styles.ts
================================================
import { StyleSheet } from "react-native";
import theme from "@react-native-ios/constants/theme";
import { APP_ICON_SIZE } from "./AppItem.constants";
export default StyleSheet.create({
container: {},
image: {
width: "100%",
height: "100%",
borderRadius: 17,
},
title: {
position: "absolute",
bottom: -20,
left: -8,
textAlign: "center",
color: "white",
width: APP_ICON_SIZE + 16,
...theme.font.caption1,
},
});
================================================
FILE: src/components/AppItem/AppItem.tsx
================================================
import { Image, Text, View } from "react-native";
import { APP_ICON_SIZE } from "./AppItem.constants";
import styles from "./AppItem.styles";
type AppItemProps = {
size?: "small" | undefined;
noTitle?: boolean;
title?: string;
icon: any;
};
export default function AppItem({ icon, title, size, noTitle }: AppItemProps) {
return (
<View
style={[
styles.container,
{
width: size === "small" ? 16 : APP_ICON_SIZE,
height: size === "small" ? 16 : APP_ICON_SIZE,
borderRadius: size === "small" ? 4 : 17,
},
]}
>
<Image style={styles.image} source={icon} />
{noTitle || size === "small" ? null : (
<Text
style={styles.title}
numberOfLines={1}
adjustsFontSizeToFit={false}
>
{title ? title : "Square"}
</Text>
)}
</View>
);
}
================================================
FILE: src/components/AppItem/index.ts
================================================
export { default } from "./AppItem";
================================================
FILE: src/components/Footer/Footer.tsx
================================================
import { StyleSheet, View } from "react-native";
import { BlurView } from "expo-blur";
import AppItem from "@react-native-ios/components/AppItem";
import apps from "@react-native-ios/constants/apps";
import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
export default function Footer() {
return (
<View style={styles.container}>
<BlurView intensity={100} style={styles.contentContainer}>
{Object.keys(apps.footer).map((item, index) => {
return (
<AppItem
key={`footer-app-${item.title}-${index}`}
noTitle
icon={apps.footer[item].icon}
/>
);
})}
</BlurView>
</View>
);
}
const styles = StyleSheet.create({
container: {
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
},
contentContainer: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: 20,
paddingHorizontal: 20,
position: "absolute",
bottom: 24,
width: SCREEN_WIDTH - 24,
borderRadius: 36,
overflow: "hidden",
},
});
================================================
FILE: src/components/Footer/index.ts
================================================
export { default } from "./Footer";
================================================
FILE: src/components/SwipeableProvider/SwipeableProvider.styles.ts
================================================
import { StyleSheet } from "react-native";
import { SCREEN_HEIGHT, SCREEN_WIDTH } from "@react-native-ios/constants/ui";
export default StyleSheet.create({
pagesContainer: {
width: SCREEN_WIDTH * 2,
height: SCREEN_HEIGHT,
display: "flex",
flexDirection: "row",
},
pageContainer: {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
display: "flex",
},
container: {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
},
searchContainer: {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
...StyleSheet.absoluteFillObject,
},
});
================================================
FILE: src/components/SwipeableProvider/SwipeableProvider.tsx
================================================
import React from "react";
import { GestureDetector } from "react-native-gesture-handler";
import Animated from "react-native-reanimated";
import Search from "@react-native-ios/screens/Search";
import RightSearch from "@react-native-ios/screens/Search/RightSearch";
import LeftSearch from "@react-native-ios/screens/Search/LeftSearch";
import useSwipeableProvider from "@react-native-ios/hooks/useSwipeableProvider";
import styles from "./SwipeableProvider.styles";
type SwipeableProviderProps = {
pages: React.ReactNode[];
};
export default function SwipeableProvider({ pages }: SwipeableProviderProps) {
const {
offsetY,
offsetX,
startX,
animatedStyles,
animatedPageContainerStyles,
animatedPagesContainerStyles,
swipeableProviderGesture,
} = useSwipeableProvider();
return (
<>
<Search {...{ offsetY }} />
<LeftSearch {...{ offsetX, startX }} />
<RightSearch {...{ offsetX, startX }} />
<GestureDetector gesture={swipeableProviderGesture}>
<Animated.View style={[styles.container, animatedStyles]}>
<Animated.View
style={[styles.pagesContainer, animatedPagesContainerStyles]}
>
{pages.map((item) => (
<Animated.View
style={[styles.pageContainer, animatedPageContainerStyles]}
>
{item}
</Animated.View>
))}
</Animated.View>
</Animated.View>
</GestureDetector>
</>
);
}
================================================
FILE: src/components/SwipeableProvider/index.ts
================================================
export { default } from "./SwipeableProvider";
================================================
FILE: src/components/Text.tsx
================================================
import React from "react";
import { Text as RNText, TextProps } from "react-native";
export default function Text(props: TextProps) {
return (
<RNText numberOfLines={1} adjustsFontSizeToFit={false} {...props}>
{props.children}
</RNText>
);
}
================================================
FILE: src/components/Wallpaper.tsx
================================================
import { useLayoutEffect, useState } from "react";
import { Image, StyleSheet, View } from "react-native";
import { SCREEN_WIDTH } from "../constants/ui";
import WalpaperImage from "../assets/img/wallpaper.jpg";
export default function Wallpaper() {
const [imageWidth, setImageWidth] = useState(0);
const [imageHeight, setImageHeight] = useState(0);
useLayoutEffect(() => {
const { width, height } = Image.resolveAssetSource(WalpaperImage);
setImageWidth(SCREEN_WIDTH);
setImageHeight((SCREEN_WIDTH * height) / width);
}, []);
return (
<View
style={{
...StyleSheet.absoluteFillObject,
zIndex: -1,
}}
>
<Image
resizeMode="contain"
source={WalpaperImage}
style={{
height: imageHeight,
width: imageWidth,
}}
/>
</View>
);
}
================================================
FILE: src/components/WidgetItem/WidgetItem.constants.ts
================================================
import theme from "@react-native-ios/constants/theme";
import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
export const WIDGET_SQUARE_SIZE =
(SCREEN_WIDTH - (theme.spacing.lg + 4) * 2 - 21) / 2;
export const WIDGET_WIDE_SIZE = SCREEN_WIDTH - theme.spacing.lg * 2;
================================================
FILE: src/components/WidgetItem/WidgetItem.styles.ts
================================================
import { StyleSheet } from "react-native";
import theme from "@react-native-ios/constants/theme";
import { WIDGET_SQUARE_SIZE } from "./WidgetItem.constants";
export default StyleSheet.create({
container: {
width: WIDGET_SQUARE_SIZE,
height: WIDGET_SQUARE_SIZE,
borderRadius: theme.spacing.lg,
backgroundColor: theme.colors.white.white25,
overflow: "hidden",
marginBottom: theme.spacing.lg,
},
});
================================================
FILE: src/components/WidgetItem/WidgetItem.tsx
================================================
import React from "react";
import { View } from "react-native";
import { BlurView } from "expo-blur";
import { TapGestureHandler } from "react-native-gesture-handler";
import { WIDGET_SQUARE_SIZE } from "./WidgetItem.constants";
import styles from "./WidgetItem.styles";
type WidgetItemProps = {
children?: React.ReactNode;
containerStyles?: object;
onPress?: () => void;
wide?: boolean;
};
export default function WidgetItem({
children,
containerStyles,
wide,
onPress,
}: WidgetItemProps) {
return (
<TapGestureHandler onEnded={onPress}>
<View
style={[
styles.container,
containerStyles,
{
width: wide ? "100%" : WIDGET_SQUARE_SIZE,
},
]}
>
<BlurView
intensity={80}
style={{
width: wide ? "100%" : WIDGET_SQUARE_SIZE,
height: WIDGET_SQUARE_SIZE,
}}
>
{children}
</BlurView>
</View>
</TapGestureHandler>
);
}
================================================
FILE: src/components/WidgetItem/index.ts
================================================
export { default } from "./WidgetItem";
================================================
FILE: src/configs/horizontalGestureCalculations.ts
================================================
import { SharedValue, withSpring } from "react-native-reanimated";
import { PanGestureHandlerEventPayload } from "react-native-gesture-handler";
import {
MIN_VELOCITY_Y_TO_ACTIVATE,
SNAP_POINTS_HORIZONTAL_AS_ARRAY,
SPRING_CONFIG,
} from "@react-native-ios/constants/animation";
import { SCREEN_WIDTH } from "@react-native-ios/constants/ui";
type handleOnUpdateHorizontalProps = {
e: PanGestureHandlerEventPayload;
startX: SharedValue<number>;
offsetX: SharedValue<number>;
};
const getNextSnapPoint = (offset: number) => {
"worklet";
let nextSnapPointAvailable = false;
let snapPoint = 0;
for (let i = 2; i < 10; i += 2) {
if (
offset <= SNAP_POINTS_HORIZONTAL_AS_ARRAY[i - 1] &&
offset >= SNAP_POINTS_HORIZONTAL_AS_ARRAY[i + 1]
) {
snapPoint = SNAP_POINTS_HORIZONTAL_AS_ARRAY[i];
nextSnapPointAvailable = true;
}
}
return nextSnapPointAvailable ? snapPoint : -1;
};
export const handleOnEndHorizontal = ({
e,
startX,
offsetX,
}: handleOnUpdateHorizontalProps & {
destination: SharedValue<number>;
}) => {
"worklet";
const velocity = Math.abs(e.velocityX);
const direction = e.translationX < 0 ? "right" : "left";
const nextXValue =
direction === "right"
? offsetX.value - SCREEN_WIDTH
: offsetX.value + SCREEN_WIDTH;
startX.value = startX.value + e.translationX;
if (velocity > MIN_VELOCITY_Y_TO_ACTIVATE) {
const nextSnapPoint = getNextSnapPoint(nextXValue);
if (nextSnapPoint !== -1) {
offsetX.value = withSpring(nextSnapPoint, {
...SPRING_CONFIG,
velocity,
});
startX.value = withSpring(nextSnapPoint, {
...SPRING_CONFIG,
velocity,
});
}
} else {
const nextSnapPoint2 = getNextSnapPoint(offsetX.value + e.translationX);
console.log("else", nextSnapPoint2);
offsetX.value = withSpring(nextSnapPoint2, {
...SPRING_CONFIG,
velocity,
});
startX.value = withSpring(nextSnapPoint2, {
...SPRING_CONFIG,
velocity,
});
}
};
================================================
FILE: src/configs/verticalGestureCalculations.ts
================================================
import { SharedValue, withSpring } from "react-native-reanimated";
import { PanGestureHandlerEventPayload } from "react-native-gesture-handler";
import {
DISTANCE_TO_ACTIVATE,
MAX_OFFSET_TO_ANIMATE,
MIN_VELOCITY_Y_TO_ACTIVATE,
SPRING_CONFIG,
} from "@react-native-ios/constants/animation";
type handleOnUpdateVerticalProps = {
e: PanGestureHandlerEventPayload;
offsetY: SharedValue<number>;
};
export const handleOnEndVertical = ({
e,
offsetY,
}: handleOnUpdateVerticalProps) => {
"worklet";
const velocity = Math.abs(e.velocityY);
const translation = Math.abs(e.translationY);
if (
translation > DISTANCE_TO_ACTIVATE ||
velocity > MIN_VELOCITY_Y_TO_ACTIVATE
) {
offsetY.value = withSpring(MAX_OFFSET_TO_ANIMATE, {
...SPRING_CONFIG,
overshootClamping: false,
velocity,
});
} else {
offsetY.value = withSpring(0, SPRING_CONFIG);
}
};
================================================
FILE: src/constants/animation.ts
================================================
import { SCREEN_WIDTH } from "./ui";
export const BLUR_VIEW_MAX_INTENSITY = 75;
export const MAX_OFFSET_TO_ANIMATE = 90;
export const DISTANCE_TO_ACTIVATE = MAX_OFFSET_TO_ANIMATE / 2;
export const MIN_VELOCITY_Y_TO_ACTIVATE = 100;
export const SPRING_CONFIG = {
damping: 500,
stiffness: 1000,
mass: 3,
overshootClamping: true,
restDisplacementThreshold: 10,
restSpeedThreshold: 10,
};
export const SNAP_POINTS_HORIZONTAL = {
LEFT_PAGE: SCREEN_WIDTH,
LEFT_PAGE_HALF: SCREEN_WIDTH / 2,
ORIGIN: 0,
FIRST_PAGE_HALF: SCREEN_WIDTH / -2,
SECOND_PAGE: SCREEN_WIDTH * -1,
SECOND_PAGE_HALF: (SCREEN_WIDTH * -3) / 2,
RIGHT_PAGE: SCREEN_WIDTH * -2, // -750
};
export const SNAP_POINTS_HORIZONTAL_AS_ARRAY = [
SCREEN_WIDTH * 3,
(SCREEN_WIDTH * 3) / 2,
SCREEN_WIDTH,
SCREEN_WIDTH / 2, // 187.5
0,
SCREEN_WIDTH / -2, // -187
SCREEN_WIDTH * -1, // -375
(SCREEN_WIDTH * -3) / 2, // -375 + (-375/2) = -562
SCREEN_WIDTH * -2, // -375 * 2 == -750
(SCREEN_WIDTH * -5) / 2, // -375 * 2 + (-375/2) = -937
SCREEN_WIDTH * -3, // -375 * 3 = -1500
(SCREEN_WIDTH * -7) / 2, // -375 * 3 + (-375/2) = -1875
];
================================================
FILE: src/constants/apps.ts
================================================
export default {
footer: {
settings: {
title: "Settings",
icon: require("../assets/img/app-icons/Settings.png"),
},
safari: {
title: "Safari",
icon: require("../assets/img/app-icons/Safari.png"),
},
photos: {
title: "Photos",
icon: require("../assets/img/app-icons/Photos.png"),
},
messages: {
title: "Messages",
icon: require("../assets/img/app-icons/Messages.png"),
},
},
home: {
row1: [
{
title: "FaceTime",
icon: require("../assets/img/app-icons/Facetime.png"),
},
{
title: "Messages",
icon: require("../assets/img/app-icons/Messages.png"),
},
{
title: "Clock",
icon: require("../assets/img/app-icons/Clock.png"),
},
{
title: "Weather",
icon: require("../assets/img/app-icons/Weather.png"),
},
],
row2: [
{
title: "Notes",
icon: require("../assets/img/app-icons/Notes.png"),
},
{
title: "Calculator",
icon: require("../assets/img/app-icons/Calculator.png"),
},
{
title: "Wallet",
icon: require("../assets/img/app-icons/Wallet.png"),
},
{
title: "News",
icon: require("../assets/img/app-icons/News.png"),
},
],
row3: [
{
title: "Compass",
icon: require("../assets/img/app-icons/Compass.png"),
},
{
title: "iTunes",
icon: require("../assets/img/app-icons/iTunes.png"),
},
{
title: "AppStore",
icon: require("../assets/img/app-icons/AppStore.png"),
},
{
title: "Home",
icon: require("../assets/img/app-icons/Home.png"),
},
],
},
home2: {
row1: [],
},
};
================================================
FILE: src/constants/theme.ts
================================================
export default {
colors: {
white: {
white100: "rgba(255, 255, 255, 1)",
white75: "rgba(255, 255, 255, 0.75)",
white50: "rgba(255, 255, 255, 0.5)",
white25: "rgba(255, 255, 255, 0.25)",
white15: "rgba(255, 255, 255, 0.15)",
},
black: {
black100: "rgba(0, 0, 0, 1)",
black75: "rgba(0, 0, 0, 0.75)",
black50: "rgba(0, 0, 0, 0.5)",
black25: "rgba(0, 0, 0, 0.25)",
},
},
spacing: {
xs: 4,
sm: 7,
sm13: 13,
md: 16,
lg: 24,
xl: 32,
},
font: {
largeTitle: { fontSize: 34, lineHeight: 41 },
title1: { fontSize: 28, lineHeight: 34 },
title2: { fontSize: 22, lineHeight: 28 },
title3: { fontSize: 20, lineHeight: 25 },
headline: { fontSize: 17, lineHeight: 22 },
body: { fontSize: 17, lineHeight: 22 },
callout: { fontSize: 16, lineHeight: 21 },
subhead: { fontSize: 15, lineHeight: 20 },
footnote: { fontSize: 13, lineHeight: 18 },
caption1: { fontSize: 12, lineHeight: 16 },
caption2: { fontSize: 11, lineHeight: 13 },
},
};
================================================
FILE: src/constants/ui.ts
================================================
import { Dimensions } from "react-native";
const { width, height } = Dimensions.get("screen");
export const SCREEN_WIDTH = width;
export const SCREEN_HEIGHT = height;
================================================
FILE: src/hooks/useGestureHandler.ts
================================================
import {
handleGestureOnEnd,
handleGestureOnStart,
handleGestureOnUpdate,
} from "@react-native-ios/utils/swipeableGestureHandlers";
import { Gesture } from "react-native-gesture-handler";
import { SharedValue, useSharedValue } from "react-native-reanimated";
type useGestureHandlerProps = {
startX: SharedValue<number>;
offsetX: SharedValue<number>;
offsetY?: SharedValue<number>;
};
const useGestureHandler = ({
startX,
offsetX,
offsetY,
}: useGestureHandlerProps) => {
const direction = useSharedValue(0);
const destination = useSharedValue(0);
const swipeableProviderGesture = Gesture.Pan()
.onStart((e) => {
handleGestureOnStart({
direction,
e,
startX,
});
})
.onUpdate((e) => {
handleGestureOnUpdate({
direction,
e,
startX,
offsetX,
offsetY,
});
})
.onEnd((e) => {
handleGestureOnEnd({
direction,
e,
startX,
offsetX,
offsetY,
destination,
});
});
return { swipeableProviderGesture };
};
export default useGestureHandler;
================================================
FILE: src/hooks/useSwipeableProvider.ts
================================================
import {
Extrapolate,
interpolate,
useAnimatedStyle,
useSharedValue,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import {
MAX_OFFSET_TO_ANIMATE,
SNAP_POINTS_HORIZONTAL,
} from "@react-native-ios/constants/animation";
import theme from "@react-native-ios/constants/theme";
import useGestureHandler from "./useGestureHandler";
const useSwipeableProvider = () => {
const { top } = useSafeAreaInsets();
const offsetY = useSharedValue(0);
const offsetX = useSharedValue(0);
const startX = useSharedValue(0);
const { swipeableProviderGesture } = useGestureHandler({
offsetY,
offsetX,
startX,
});
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [{ translateY: offsetY.value }],
opacity: interpolate(
offsetY.value,
[0, MAX_OFFSET_TO_ANIMATE],
[1, 0.6],
Extrapolate.CLAMP
),
paddingTop: top + theme.spacing.md,
};
});
const animatedPageContainerStyles = useAnimatedStyle(() => {
return {
transform: [
{
scale:
offsetX.value > 0
? interpolate(
offsetX.value,
[
SNAP_POINTS_HORIZONTAL.LEFT_PAGE,
SNAP_POINTS_HORIZONTAL.ORIGIN,
],
[0.85, 1],
Extrapolate.CLAMP
)
: interpolate(
offsetX.value,
[
SNAP_POINTS_HORIZONTAL.SECOND_PAGE,
SNAP_POINTS_HORIZONTAL.RIGHT_PAGE,
],
[1, 0.85],
Extrapolate.CLAMP
),
},
],
};
});
const animatedPagesContainerStyles = useAnimatedStyle(() => {
return {
transform: [
{
translateX: Math.min(
Math.max(offsetX.value, SNAP_POINTS_HORIZONTAL.SECOND_PAGE),
SNAP_POINTS_HORIZONTAL.ORIGIN
),
},
],
};
}, [offsetX]);
return {
offsetY,
offsetX,
startX,
animatedStyles,
animatedPageContainerStyles,
animatedPagesContainerStyles,
swipeableProviderGesture,
};
};
export default useSwipeableProvider;
================================================
FILE: src/screens/Home/Home.tsx
================================================
import { StyleSheet, View } from "react-native";
import { SCREEN_HEIGHT, SCREEN_WIDTH } from "@react-native-ios/constants/ui";
import AppItem from "@react-native-ios/components/AppItem";
import apps from "@react-native-ios/constants/apps";
export function Page1() {
return (
<View style={styles.container}>
<View style={styles.row}>
{apps.home.row1.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
<View style={styles.row}>
{apps.home.row2.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
<View style={styles.row}>
{apps.home.row3.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
</View>
);
}
export function Page2() {
return (
<View style={styles.container}>
<View style={styles.row}>
{apps.home.row3.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
<View style={styles.row}>
{apps.home.row2.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
<View style={styles.row}>
{apps.home.row1.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
</View>
);
}
const styles = StyleSheet.create({
gestureContainer: {
width: SCREEN_WIDTH * 2,
height: SCREEN_HEIGHT,
display: "flex",
flexDirection: "row",
},
container: {
flex: 1,
width: SCREEN_WIDTH,
display: "flex",
flexDirection: "column",
},
row: {
display: "flex",
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
paddingHorizontal: 24,
marginBottom: 32,
},
});
================================================
FILE: src/screens/Home/index.ts
================================================
export { Page1, Page2 } from "./Home";
================================================
FILE: src/screens/Search/AnimatedProvider.tsx
================================================
import React from "react";
import { Keyboard, StyleSheet, View } from "react-native";
import { BlurView } from "expo-blur";
import { TapGestureHandler } from "react-native-gesture-handler";
import Animated, {
Extrapolate,
interpolate,
SharedValue,
useAnimatedProps,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
const AnimatedBlurView = Animated.createAnimatedComponent(BlurView);
import {
BLUR_VIEW_MAX_INTENSITY,
MAX_OFFSET_TO_ANIMATE,
SNAP_POINTS_HORIZONTAL,
SPRING_CONFIG,
} from "@react-native-ios/constants/animation";
import { SCREEN_HEIGHT, SCREEN_WIDTH } from "@react-native-ios/constants/ui";
import theme from "@react-native-ios/constants/theme";
type AnimatedProviderProps = {
startPoint: number;
snapPoint: number;
direction: "left" | "right" | "vertical";
offset: SharedValue<number>;
start?: SharedValue<number>;
children: React.ReactNode;
};
export default function AnimatedProvider({
startPoint = 0,
snapPoint = 0,
direction,
offset,
start,
children,
}: AnimatedProviderProps) {
const { top } = useSafeAreaInsets();
const direMultiply = useSharedValue(direction == "right" ? -1 : 1);
const animatedBlurBackdropStyles = useAnimatedStyle(() => {
return {
zIndex:
offset.value * direMultiply.value > startPoint * direMultiply.value
? 10
: -1,
};
});
const animatedBlurBackdropProps = useAnimatedProps(() => {
return {
intensity: interpolate(
offset.value * direMultiply.value,
[startPoint * direMultiply.value, snapPoint * direMultiply.value],
[0, BLUR_VIEW_MAX_INTENSITY],
Extrapolate.CLAMP
),
};
});
const animatedContentStyles = useAnimatedStyle(
() => ({
opacity:
direction == "vertical"
? interpolate(
offset.value * direMultiply.value,
[startPoint * direMultiply.value, snapPoint * direMultiply.value],
[0, 1],
Extrapolate.CLAMP
)
: 1,
transform:
direction === "vertical"
? [{ translateY: offset.value - MAX_OFFSET_TO_ANIMATE }]
: [
{
translateX: interpolate(
offset.value * direMultiply.value,
[
startPoint * direMultiply.value,
snapPoint * direMultiply.value,
],
[SCREEN_WIDTH * (direction === "right" ? 1 : -1), 0],
Extrapolate.CLAMP
),
},
],
}),
[offset]
);
const handleTapOutside = () => {
offset.value = withSpring(startPoint, SPRING_CONFIG);
if (start) start.value = withSpring(startPoint, SPRING_CONFIG);
Keyboard.dismiss();
};
return (
<AnimatedBlurView
tint="dark"
animatedProps={animatedBlurBackdropProps}
style={[styles.blurBackdrop, animatedBlurBackdropStyles]}
>
<TapGestureHandler numberOfTaps={1} onEnded={handleTapOutside}>
<View style={styles.container}>
<Animated.View
style={[
styles.contentContainer,
{ paddingTop: top + theme.spacing.sm },
animatedContentStyles,
]}
>
{children}
</Animated.View>
</View>
</TapGestureHandler>
</AnimatedBlurView>
);
}
const styles = StyleSheet.create({
container: {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
},
contentContainer: {
display: "flex",
flexDirection: "column",
paddingHorizontal: theme.spacing.sm,
},
blurBackdrop: {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
...StyleSheet.absoluteFillObject,
},
});
================================================
FILE: src/screens/Search/LeftSearch.tsx
================================================
import React from "react";
import { SharedValue } from "react-native-reanimated";
import { SNAP_POINTS_HORIZONTAL } from "@react-native-ios/constants/animation";
import LeftSearchContent from "./components/LeftSearchContent";
import AnimatedProvider from "./AnimatedProvider";
type AnimatedProviderProps = {
offsetX: SharedValue<number>;
startX: SharedValue<number>;
};
export default function LeftSearch({ offsetX, startX }: AnimatedProviderProps) {
return (
<AnimatedProvider
direction="left"
startPoint={SNAP_POINTS_HORIZONTAL.ORIGIN}
snapPoint={SNAP_POINTS_HORIZONTAL.LEFT_PAGE}
offset={offsetX}
start={startX}
>
<LeftSearchContent />
</AnimatedProvider>
);
}
================================================
FILE: src/screens/Search/RightSearch.tsx
================================================
import React from "react";
import { SharedValue } from "react-native-reanimated";
import { SNAP_POINTS_HORIZONTAL } from "@react-native-ios/constants/animation";
import LeftSearchContent from "./components/LeftSearchContent";
import AnimatedProvider from "./AnimatedProvider";
type AnimatedProviderProps = {
offsetX: SharedValue<number>;
startX: SharedValue<number>;
};
export default function LeftSearch({ offsetX, startX }: AnimatedProviderProps) {
return (
<AnimatedProvider
direction="right"
startPoint={SNAP_POINTS_HORIZONTAL.SECOND_PAGE}
snapPoint={SNAP_POINTS_HORIZONTAL.RIGHT_PAGE}
offset={offsetX}
start={startX}
>
<LeftSearchContent />
</AnimatedProvider>
);
}
================================================
FILE: src/screens/Search/Search.styles.ts
================================================
import { StyleSheet } from "react-native";
import theme from "@react-native-ios/constants/theme";
export default StyleSheet.create({
searchContainer: {
display: "flex",
flexDirection: "row",
alignItems: "center",
marginBottom: theme.spacing.md,
},
searchInputContainer: {
flex: 1,
},
searchIconContainer: {
position: "absolute",
top: theme.spacing.sm + theme.spacing.xs - 1,
left: theme.spacing.sm + theme.spacing.xs,
},
michrophoneIconContainer: {
position: "absolute",
top: 8,
right: 12 + theme.spacing.sm,
},
searchInput: {
paddingVertical: theme.spacing.sm,
paddingLeft: (theme.spacing.sm + theme.spacing.xs) * 2 + 14,
paddingRight: theme.spacing.sm * 2 + theme.spacing.xs + 12,
backgroundColor: theme.colors.white.white15,
color: theme.colors.white.white75,
borderRadius: 12,
marginRight: 8,
...theme.font.body,
},
cancelText: {
color: theme.colors.white.white50,
...theme.font.body,
},
titleSectionContainer: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: theme.spacing.xs,
marginBottom: theme.spacing.xs,
},
titleText: {
...theme.font.title2,
fontWeight: "600",
color: "white",
},
showMoreText: {
...theme.font.footnote,
color: theme.colors.white.white50,
},
appsContainer: {
display: "flex",
flexDirection: "column",
padding: theme.spacing.md,
borderRadius: theme.spacing.md,
width: "100%",
backgroundColor: theme.colors.white.white15,
},
row: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginBottom: theme.spacing.xl,
},
});
================================================
FILE: src/screens/Search/Search.tsx
================================================
import { MAX_OFFSET_TO_ANIMATE } from "@react-native-ios/constants/animation";
import React from "react";
import { SharedValue } from "react-native-reanimated";
import AnimatedProvider from "./AnimatedProvider";
import SearchContent from "./SearchContent";
type SearchProps = {
offsetY: SharedValue<number>;
};
export default function Search({ offsetY }: SearchProps) {
return (
<AnimatedProvider
direction="vertical"
snapPoint={MAX_OFFSET_TO_ANIMATE}
startPoint={0}
offset={offsetY}
>
<SearchContent />
</AnimatedProvider>
);
}
================================================
FILE: src/screens/Search/SearchContent.tsx
================================================
import React from "react";
import { Pressable, Text, View } from "react-native";
import { TextInput } from "react-native-gesture-handler";
import SearchSVG from "@react-native-ios/assets/svg/search.svg";
import MichrophoneSVG from "@react-native-ios/assets/svg/michrophone.svg";
import ChevronRightSVG from "@react-native-ios/assets/svg/chevron-right.svg";
import AppItem from "@react-native-ios/components/AppItem";
import apps from "@react-native-ios/constants/apps";
import styles from "./Search.styles";
import theme from "@react-native-ios/constants/theme";
export default function SearchContent() {
return (
<>
<View style={styles.searchContainer}>
<View style={styles.searchInputContainer}>
<View style={styles.searchIconContainer}>
<SearchSVG
height={16}
width={16}
fill={theme.colors.white.white50}
/>
</View>
<View style={styles.michrophoneIconContainer}>
<MichrophoneSVG
width={18}
height={18}
fill={theme.colors.white.white50}
/>
</View>
<TextInput
placeholder="Search"
placeholderTextColor={theme.colors.white.white75}
autoCorrect={false}
autoCompleteType="off"
keyboardAppearance="dark"
style={styles.searchInput}
/>
</View>
<Pressable>
<Text style={styles.cancelText}>Cancel</Text>
</Pressable>
</View>
<View style={styles.titleSectionContainer}>
<Text style={styles.titleText}>Siri Suggestions</Text>
<ChevronRightSVG width={6} stroke={theme.colors.white.white50} />
</View>
<View style={styles.appsContainer}>
<View style={styles.row}>
{apps.home.row3.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
<View style={[styles.row, { marginBottom: 24 }]}>
{apps.home.row1.map((item) => {
return <AppItem key={`app-${item.title}`} {...item} />;
})}
</View>
</View>
</>
);
}
================================================
FILE: src/screens/Search/components/LeftSearchContent.styles.tsx
================================================
import { StyleSheet } from "react-native";
import theme from "@react-native-ios/constants/theme";
export default StyleSheet.create({
searchContainer: {
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
paddingHorizontal: theme.spacing.lg - theme.spacing.sm,
},
searchInputContainer: {
flex: 1,
paddingVertical: theme.spacing.md,
borderRadius: theme.spacing.md,
backgroundColor: theme.colors.white.white15,
display: "flex",
flexDirection: "row",
justifyContent: "center",
},
searchIconContainer: {
position: "absolute",
top: theme.spacing.sm + theme.spacing.xs - 1,
left: theme.spacing.sm + theme.spacing.xs,
},
michrophoneIconContainer: {
position: "absolute",
top: theme.spacing.sm,
right: 12 + theme.spacing.sm,
},
searchInput: {
paddingHorizontal: theme.spacing.sm,
color: theme.colors.white.white75,
...theme.font.body,
},
cancelButton: {
position: "absolute",
right: 0,
opacity: 0,
},
cancelText: {
color: theme.colors.white.white50,
...theme.font.body,
},
appsContainer: {
display: "flex",
flexDirection: "column",
padding: theme.spacing.lg + 4,
paddingHorizontal: 56 / 2 - 8,
paddingTop: 0,
width: "100%",
backgroundColor: "transparent",
overflow: "hidden",
},
row: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
},
center: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
},
column: {
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-start",
height: "100%",
width: "100%",
position: "relative",
padding: 24,
},
linkIconContainer: {
position: "absolute",
right: 16,
bottom: 16,
},
textBody: {
...theme.font.body,
color: theme.colors.white.white50,
},
textBodySecondary: {
marginLeft: 8,
...theme.font.body,
color: theme.colors.white.white75,
},
});
================================================
FILE: src/screens/Search/components/LeftSearchContent.tsx
================================================
import React from "react";
import { Text, View } from "react-native";
import * as WebBrowser from "expo-web-browser";
import ELetterSVG from "@react-native-ios/assets/svg/e-letter.svg";
import GithubSVG from "@react-native-ios/assets/svg/github.svg";
import LinkSVG from "@react-native-ios/assets/svg/link.svg";
import MessageSVG from "@react-native-ios/assets/svg/message.svg";
import WidgetItem from "@react-native-ios/components/WidgetItem";
import AnimatedInput from "@react-native-ios/components/AnimatedInput";
import styles from "./LeftSearchContent.styles";
import theme from "@react-native-ios/constants/theme";
export default function LeftSearchContent() {
const handleNavigateWebsite = async () => {
await WebBrowser.openBrowserAsync("https://ozturkenes.com");
};
const handleNavigateToGithub = async () => {
await WebBrowser.openBrowserAsync("https://github.com/enesozturk");
};
return (
<>
<AnimatedInput />
<View style={styles.appsContainer}>
<View style={styles.row}>
<WidgetItem onPress={handleNavigateWebsite}>
<View style={styles.center}>
<View style={styles.linkIconContainer}>
<LinkSVG
width={24}
height={24}
color={theme.colors.white.white50}
/>
</View>
<ELetterSVG
color={theme.colors.white.white75}
width={48}
height={48}
/>
</View>
</WidgetItem>
<WidgetItem />
</View>
<WidgetItem wide onPress={handleNavigateToGithub}>
<View
style={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
justifyContent: "flex-start",
padding: 24,
height: "100%",
width: "100%",
position: "relative",
}}
>
<View style={styles.linkIconContainer}>
<LinkSVG
width={24}
height={24}
color={theme.colors.white.white50}
/>
</View>
<Text style={styles.textBody}>
Did you like this app? Give me a star and follow me on Github for
more ⭐️
</Text>
<View
style={[
styles.row,
{
alignItems: "center",
justifyContent: "flex-start",
marginTop: 24,
},
]}
>
<GithubSVG
color={theme.colors.white.white75}
width={32}
height={32}
/>
<Text style={styles.textBodySecondary}>
enesozturk/react-native-ios
</Text>
</View>
</View>
</WidgetItem>
<WidgetItem wide>
<View style={styles.column}>
<View style={styles.linkIconContainer}>
<LinkSVG
width={24}
height={24}
color={theme.colors.white.white50}
/>
</View>
<Text style={styles.textBody}>
You need a React & React Native developer? Contact me!
</Text>
<View
style={[
styles.row,
{
alignItems: "center",
justifyContent: "flex-start",
marginTop: 24,
},
]}
>
<MessageSVG
color={theme.colors.white.white75}
width={32}
height={32}
/>
<Text style={styles.textBodySecondary}>
enesozturk.d@gmail.com
</Text>
</View>
</View>
</WidgetItem>
</View>
</>
);
}
================================================
FILE: src/screens/Search/index.ts
================================================
export { default } from "./Search";
================================================
FILE: src/utils/swipeableGestureHandlers.ts
================================================
import { PanGestureHandlerEventPayload } from "react-native-gesture-handler";
import { cancelAnimation, SharedValue } from "react-native-reanimated";
import { handleOnEndHorizontal } from "@react-native-ios/configs/horizontalGestureCalculations";
import { handleOnEndVertical } from "@react-native-ios/configs/verticalGestureCalculations";
type GestureHandlerStartProps = {
direction: SharedValue<number>;
e: PanGestureHandlerEventPayload;
startX: SharedValue<number>;
};
type GestureHandlerProps = {
direction: SharedValue<number>;
e: PanGestureHandlerEventPayload;
offsetY?: SharedValue<number>;
startX: SharedValue<number>;
offsetX: SharedValue<number>;
};
export const handleGestureOnStart = ({
e,
direction,
startX,
}: GestureHandlerStartProps) => {
"worklet";
cancelAnimation(startX);
direction.value = Math.abs(e.translationX) > Math.abs(e.translationY) ? 1 : 0;
};
export const handleGestureOnUpdate = ({
direction,
offsetY,
offsetX,
startX,
e,
}: GestureHandlerProps) => {
"worklet";
if (direction.value) {
cancelAnimation(startX);
offsetX.value = e.translationX + startX.value;
} else {
if (offsetY) offsetY.value = Math.max(0, e.translationY);
}
};
export const handleGestureOnEnd = ({
direction,
offsetY,
offsetX,
startX,
e,
destination,
}: GestureHandlerProps & { destination: SharedValue<number> }) => {
"worklet";
if (direction.value) {
handleOnEndHorizontal({
e,
startX,
offsetX,
destination,
});
} else {
if (offsetY)
handleOnEndVertical({
e,
offsetY,
});
}
};
================================================
FILE: tsconfig.json
================================================
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"baseUrl": "./src",
"paths": {
"@react-native-ios/api/*": ["./api/*"],
"@react-native-ios/assets/*": ["./assets/*"],
"@react-native-ios/configs/*": ["./configs/*"],
"@react-native-ios/constants/*": ["./constants/*"],
"@react-native-ios/components/*": ["./components/*"],
"@react-native-ios/helpers/*": ["./helpers/*"],
"@react-native-ios/hooks/*": ["./hooks/*"],
"@react-native-ios/jest/*": ["./jest/*"],
"@react-native-ios/navigators/*": ["./navigators/*"],
"@react-native-ios/providers/*": ["./providers/*"],
"@react-native-ios/screens/*": ["./screens/*"],
"@react-native-ios/store/*": ["./store/*"],
"@react-native-ios/storybook/*": ["./storybook/*"],
"@react-native-ios/translations/*": ["./translations/*"],
"@react-native-ios/utils/*": ["./utils/*"]
}
}
}
gitextract_g5jugsi7/ ├── .expo-shared/ │ └── assets.json ├── .gitignore ├── .svgrrc ├── App.tsx ├── LICENSE ├── README.md ├── app.json ├── babel.config.aliases.js ├── babel.config.js ├── declarations.d.ts ├── metro.config.js ├── package.json ├── src/ │ ├── 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
SYMBOL INDEX (44 symbols across 25 files)
FILE: App.tsx
function App (line 12) | function App() {
FILE: src/components/AnimatedInput/AnimatedInput.hooks.ts
constant INPUT_WIDTH (line 11) | const INPUT_WIDTH = SCREEN_WIDTH - 54;
FILE: src/components/AnimatedInput/AnimatedIntput.tsx
function AnimatedIntput (line 19) | function AnimatedIntput() {
FILE: src/components/AnimatedInput/CancelButton.tsx
type CancelButtonProps (line 10) | type CancelButtonProps = {
function CancelButton (line 17) | function CancelButton({
FILE: src/components/AppItem/AppItem.constants.ts
constant APP_ICON_WIDTH_RATIO (line 3) | const APP_ICON_WIDTH_RATIO = 828 / 128;
constant APP_ICON_SIZE (line 4) | const APP_ICON_SIZE = SCREEN_WIDTH / APP_ICON_WIDTH_RATIO;
FILE: src/components/AppItem/AppItem.tsx
type AppItemProps (line 6) | type AppItemProps = {
function AppItem (line 13) | function AppItem({ icon, title, size, noTitle }: AppItemProps) {
FILE: src/components/Footer/Footer.tsx
function Footer (line 9) | function Footer() {
FILE: src/components/SwipeableProvider/SwipeableProvider.tsx
type SwipeableProviderProps (line 13) | type SwipeableProviderProps = {
function SwipeableProvider (line 17) | function SwipeableProvider({ pages }: SwipeableProviderProps) {
FILE: src/components/Text.tsx
function Text (line 5) | function Text(props: TextProps) {
FILE: src/components/Wallpaper.tsx
function Wallpaper (line 7) | function Wallpaper() {
FILE: src/components/WidgetItem/WidgetItem.constants.ts
constant WIDGET_SQUARE_SIZE (line 4) | const WIDGET_SQUARE_SIZE =
constant WIDGET_WIDE_SIZE (line 6) | const WIDGET_WIDE_SIZE = SCREEN_WIDTH - theme.spacing.lg * 2;
FILE: src/components/WidgetItem/WidgetItem.tsx
type WidgetItemProps (line 10) | type WidgetItemProps = {
function WidgetItem (line 17) | function WidgetItem({
FILE: src/configs/horizontalGestureCalculations.ts
type handleOnUpdateHorizontalProps (line 11) | type handleOnUpdateHorizontalProps = {
FILE: src/configs/verticalGestureCalculations.ts
type handleOnUpdateVerticalProps (line 11) | type handleOnUpdateVerticalProps = {
FILE: src/constants/animation.ts
constant BLUR_VIEW_MAX_INTENSITY (line 3) | const BLUR_VIEW_MAX_INTENSITY = 75;
constant MAX_OFFSET_TO_ANIMATE (line 4) | const MAX_OFFSET_TO_ANIMATE = 90;
constant DISTANCE_TO_ACTIVATE (line 5) | const DISTANCE_TO_ACTIVATE = MAX_OFFSET_TO_ANIMATE / 2;
constant MIN_VELOCITY_Y_TO_ACTIVATE (line 6) | const MIN_VELOCITY_Y_TO_ACTIVATE = 100;
constant SPRING_CONFIG (line 8) | const SPRING_CONFIG = {
constant SNAP_POINTS_HORIZONTAL (line 17) | const SNAP_POINTS_HORIZONTAL = {
constant SNAP_POINTS_HORIZONTAL_AS_ARRAY (line 27) | const SNAP_POINTS_HORIZONTAL_AS_ARRAY = [
FILE: src/constants/ui.ts
constant SCREEN_WIDTH (line 5) | const SCREEN_WIDTH = width;
constant SCREEN_HEIGHT (line 6) | const SCREEN_HEIGHT = height;
FILE: src/hooks/useGestureHandler.ts
type useGestureHandlerProps (line 9) | type useGestureHandlerProps = {
FILE: src/screens/Home/Home.tsx
function Page1 (line 8) | function Page1() {
function Page2 (line 30) | function Page2() {
FILE: src/screens/Search/AnimatedProvider.tsx
type AnimatedProviderProps (line 28) | type AnimatedProviderProps = {
function AnimatedProvider (line 37) | function AnimatedProvider({
FILE: src/screens/Search/LeftSearch.tsx
type AnimatedProviderProps (line 9) | type AnimatedProviderProps = {
function LeftSearch (line 14) | function LeftSearch({ offsetX, startX }: AnimatedProviderProps) {
FILE: src/screens/Search/RightSearch.tsx
type AnimatedProviderProps (line 9) | type AnimatedProviderProps = {
function LeftSearch (line 14) | function LeftSearch({ offsetX, startX }: AnimatedProviderProps) {
FILE: src/screens/Search/Search.tsx
type SearchProps (line 9) | type SearchProps = {
function Search (line 13) | function Search({ offsetY }: SearchProps) {
FILE: src/screens/Search/SearchContent.tsx
function SearchContent (line 15) | function SearchContent() {
FILE: src/screens/Search/components/LeftSearchContent.tsx
function LeftSearchContent (line 16) | function LeftSearchContent() {
FILE: src/utils/swipeableGestureHandlers.ts
type GestureHandlerStartProps (line 7) | type GestureHandlerStartProps = {
type GestureHandlerProps (line 13) | type GestureHandlerProps = {
Condensed preview — 53 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (61K chars).
[
{
"path": ".expo-shared/assets.json",
"chars": 155,
"preview": "{\n \"12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb\": true,\n \"40b842e832070c58deac6aa9e08fa459302ee3f"
},
{
"path": ".gitignore",
"chars": 119,
"preview": "node_modules/\n.expo/\ndist/\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n\n# macOS\n.DS_Store\n"
},
{
"path": ".svgrrc",
"chars": 126,
"preview": "{\n \"memo\": true,\n \"native\": true,\n \"replaceAttrValues\": {\n \"fill\": \"{props.fill}\",\n \"stroke\": \"{props.stroke}\"\n"
},
{
"path": "App.tsx",
"chars": 907,
"preview": "import { StyleSheet } from \"react-native\";\n\nimport { StatusBar } from \"expo-status-bar\";\nimport { SafeAreaProvider } fro"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2022 Enes Ozturk\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "README.md",
"chars": 1616,
"preview": "<!-- Banner Image -->\n\n<p align=\"center\">\n <img alt=\"app icon\" height=\"128\" src=\"./assets/icon.png\">\n <h1 align=\"c"
},
{
"path": "app.json",
"chars": 884,
"preview": "{\n \"expo\": {\n \"name\": \"react-native-ios\",\n \"slug\": \"react-native-ios\",\n \"privacy\": \"public\",\n \"platforms\": "
},
{
"path": "babel.config.aliases.js",
"chars": 706,
"preview": "module.exports = {\n \"@react-native-ios/api\": \"./src/api\",\n \"@react-native-ios/assets\": \"./src/assets\",\n \"@react-nativ"
},
{
"path": "babel.config.js",
"chars": 283,
"preview": "const aliases = require(\"./babel.config.aliases\");\n\nmodule.exports = function (api) {\n api.cache(true);\n return {\n "
},
{
"path": "declarations.d.ts",
"chars": 191,
"preview": "declare module \"*.svg\" {\n import React from \"react\";\n import { SvgProps } from \"react-native-svg\";\n const content: Re"
},
{
"path": "metro.config.js",
"chars": 574,
"preview": "const { getDefaultConfig } = require(\"expo/metro-config\");\n\nmodule.exports = (async () => {\n const {\n resolver: { so"
},
{
"path": "package.json",
"chars": 992,
"preview": "{\n \"name\": \"react-native-ios\",\n \"version\": \"1.0.0\",\n \"main\": \"node_modules/expo/AppEntry.js\",\n \"scripts\": {\n \"sta"
},
{
"path": "src/components/AnimatedInput/AnimatedInput.hooks.ts",
"chars": 3144,
"preview": "import {\n Extrapolate,\n interpolate,\n useAnimatedStyle,\n useSharedValue,\n} from \"react-native-reanimated\";\n\nimport t"
},
{
"path": "src/components/AnimatedInput/AnimatedInput.styles.ts",
"chars": 1046,
"preview": "import { StyleSheet } from \"react-native\";\n\nimport theme from \"@react-native-ios/constants/theme\";\nimport { SCREEN_WIDTH"
},
{
"path": "src/components/AnimatedInput/AnimatedIntput.tsx",
"chars": 3081,
"preview": "import React from \"react\";\nimport { TextInput, View } from \"react-native\";\n\nimport Animated, { withTiming } from \"react-"
},
{
"path": "src/components/AnimatedInput/CancelButton.tsx",
"chars": 1373,
"preview": "import React from \"react\";\nimport { StyleSheet, View } from \"react-native\";\n\nimport { TapGestureHandler } from \"react-na"
},
{
"path": "src/components/AnimatedInput/index.ts",
"chars": 44,
"preview": "export { default } from \"./AnimatedIntput\";\n"
},
{
"path": "src/components/AppItem/AppItem.constants.ts",
"chars": 177,
"preview": "import { SCREEN_WIDTH } from \"@react-native-ios/constants/ui\";\n\nexport const APP_ICON_WIDTH_RATIO = 828 / 128;\nexport co"
},
{
"path": "src/components/AppItem/AppItem.styles.ts",
"chars": 464,
"preview": "import { StyleSheet } from \"react-native\";\n\nimport theme from \"@react-native-ios/constants/theme\";\n\nimport { APP_ICON_SI"
},
{
"path": "src/components/AppItem/AppItem.tsx",
"chars": 894,
"preview": "import { Image, Text, View } from \"react-native\";\nimport { APP_ICON_SIZE } from \"./AppItem.constants\";\n\nimport styles fr"
},
{
"path": "src/components/AppItem/index.ts",
"chars": 37,
"preview": "export { default } from \"./AppItem\";\n"
},
{
"path": "src/components/Footer/Footer.tsx",
"chars": 1145,
"preview": "import { StyleSheet, View } from \"react-native\";\n\nimport { BlurView } from \"expo-blur\";\n\nimport AppItem from \"@react-nat"
},
{
"path": "src/components/Footer/index.ts",
"chars": 36,
"preview": "export { default } from \"./Footer\";\n"
},
{
"path": "src/components/SwipeableProvider/SwipeableProvider.styles.ts",
"chars": 575,
"preview": "import { StyleSheet } from \"react-native\";\n\nimport { SCREEN_HEIGHT, SCREEN_WIDTH } from \"@react-native-ios/constants/ui\""
},
{
"path": "src/components/SwipeableProvider/SwipeableProvider.tsx",
"chars": 1509,
"preview": "import React from \"react\";\n\nimport { GestureDetector } from \"react-native-gesture-handler\";\nimport Animated from \"react-"
},
{
"path": "src/components/SwipeableProvider/index.ts",
"chars": 47,
"preview": "export { default } from \"./SwipeableProvider\";\n"
},
{
"path": "src/components/Text.tsx",
"chars": 262,
"preview": "import React from \"react\";\n\nimport { Text as RNText, TextProps } from \"react-native\";\n\nexport default function Text(prop"
},
{
"path": "src/components/Wallpaper.tsx",
"chars": 853,
"preview": "import { useLayoutEffect, useState } from \"react\";\nimport { Image, StyleSheet, View } from \"react-native\";\nimport { SCRE"
},
{
"path": "src/components/WidgetItem/WidgetItem.constants.ts",
"chars": 278,
"preview": "import theme from \"@react-native-ios/constants/theme\";\nimport { SCREEN_WIDTH } from \"@react-native-ios/constants/ui\";\n\ne"
},
{
"path": "src/components/WidgetItem/WidgetItem.styles.ts",
"chars": 429,
"preview": "import { StyleSheet } from \"react-native\";\n\nimport theme from \"@react-native-ios/constants/theme\";\n\nimport { WIDGET_SQUA"
},
{
"path": "src/components/WidgetItem/WidgetItem.tsx",
"chars": 1015,
"preview": "import React from \"react\";\nimport { View } from \"react-native\";\n\nimport { BlurView } from \"expo-blur\";\nimport { TapGestu"
},
{
"path": "src/components/WidgetItem/index.ts",
"chars": 40,
"preview": "export { default } from \"./WidgetItem\";\n"
},
{
"path": "src/configs/horizontalGestureCalculations.ts",
"chars": 2045,
"preview": "import { SharedValue, withSpring } from \"react-native-reanimated\";\nimport { PanGestureHandlerEventPayload } from \"react-"
},
{
"path": "src/configs/verticalGestureCalculations.ts",
"chars": 906,
"preview": "import { SharedValue, withSpring } from \"react-native-reanimated\";\nimport { PanGestureHandlerEventPayload } from \"react-"
},
{
"path": "src/constants/animation.ts",
"chars": 1134,
"preview": "import { SCREEN_WIDTH } from \"./ui\";\n\nexport const BLUR_VIEW_MAX_INTENSITY = 75;\nexport const MAX_OFFSET_TO_ANIMATE = 90"
},
{
"path": "src/constants/apps.ts",
"chars": 1811,
"preview": "export default {\n footer: {\n settings: {\n title: \"Settings\",\n icon: require(\"../assets/img/app-icons/Setti"
},
{
"path": "src/constants/theme.ts",
"chars": 1066,
"preview": "export default {\n colors: {\n white: {\n white100: \"rgba(255, 255, 255, 1)\",\n white75: \"rgba(255, 255, 255, "
},
{
"path": "src/constants/ui.ts",
"chars": 169,
"preview": "import { Dimensions } from \"react-native\";\n\nconst { width, height } = Dimensions.get(\"screen\");\n\nexport const SCREEN_WID"
},
{
"path": "src/hooks/useGestureHandler.ts",
"chars": 1130,
"preview": "import {\n handleGestureOnEnd,\n handleGestureOnStart,\n handleGestureOnUpdate,\n} from \"@react-native-ios/utils/swipeabl"
},
{
"path": "src/hooks/useSwipeableProvider.ts",
"chars": 2295,
"preview": "import {\n Extrapolate,\n interpolate,\n useAnimatedStyle,\n useSharedValue,\n} from \"react-native-reanimated\";\nimport { "
},
{
"path": "src/screens/Home/Home.tsx",
"chars": 1856,
"preview": "import { StyleSheet, View } from \"react-native\";\n\nimport { SCREEN_HEIGHT, SCREEN_WIDTH } from \"@react-native-ios/constan"
},
{
"path": "src/screens/Home/index.ts",
"chars": 39,
"preview": "export { Page1, Page2 } from \"./Home\";\n"
},
{
"path": "src/screens/Search/AnimatedProvider.tsx",
"chars": 3839,
"preview": "import React from \"react\";\nimport { Keyboard, StyleSheet, View } from \"react-native\";\n\nimport { BlurView } from \"expo-bl"
},
{
"path": "src/screens/Search/LeftSearch.tsx",
"chars": 724,
"preview": "import React from \"react\";\n\nimport { SharedValue } from \"react-native-reanimated\";\n\nimport { SNAP_POINTS_HORIZONTAL } fr"
},
{
"path": "src/screens/Search/RightSearch.tsx",
"chars": 731,
"preview": "import React from \"react\";\n\nimport { SharedValue } from \"react-native-reanimated\";\n\nimport { SNAP_POINTS_HORIZONTAL } fr"
},
{
"path": "src/screens/Search/Search.styles.ts",
"chars": 1747,
"preview": "import { StyleSheet } from \"react-native\";\n\nimport theme from \"@react-native-ios/constants/theme\";\n\nexport default Style"
},
{
"path": "src/screens/Search/Search.tsx",
"chars": 581,
"preview": "import { MAX_OFFSET_TO_ANIMATE } from \"@react-native-ios/constants/animation\";\nimport React from \"react\";\n\nimport { Shar"
},
{
"path": "src/screens/Search/SearchContent.tsx",
"chars": 2190,
"preview": "import React from \"react\";\nimport { Pressable, Text, View } from \"react-native\";\n\nimport { TextInput } from \"react-nativ"
},
{
"path": "src/screens/Search/components/LeftSearchContent.styles.tsx",
"chars": 2129,
"preview": "import { StyleSheet } from \"react-native\";\n\nimport theme from \"@react-native-ios/constants/theme\";\n\nexport default Style"
},
{
"path": "src/screens/Search/components/LeftSearchContent.tsx",
"chars": 3987,
"preview": "import React from \"react\";\nimport { Text, View } from \"react-native\";\n\nimport * as WebBrowser from \"expo-web-browser\";\n\n"
},
{
"path": "src/screens/Search/index.ts",
"chars": 36,
"preview": "export { default } from \"./Search\";\n"
},
{
"path": "src/utils/swipeableGestureHandlers.ts",
"chars": 1628,
"preview": "import { PanGestureHandlerEventPayload } from \"react-native-gesture-handler\";\nimport { cancelAnimation, SharedValue } fr"
},
{
"path": "tsconfig.json",
"chars": 946,
"preview": "{\n \"extends\": \"expo/tsconfig.base\",\n \"compilerOptions\": {\n \"strict\": true,\n \"baseUrl\": \"./src\",\n \"paths\": {\n "
}
]
About this extraction
This page contains the full source code of the enesozturk/react-native-ios GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 53 files (53.8 KB), approximately 14.8k tokens, and a symbol index with 44 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.