├── .gitignore
├── README.md
├── app.json
├── app
├── _layout.tsx
├── home.tsx
└── index.tsx
├── assets
├── fonts
│ └── SpaceMono-Regular.ttf
└── images
│ ├── adaptive-icon.png
│ ├── favicon.png
│ ├── icon.png
│ ├── partial-react-logo.png
│ ├── react-logo.png
│ ├── react-logo@2x.png
│ ├── react-logo@3x.png
│ └── splash.png
├── babel.config.js
├── components
├── Footer
│ └── index.tsx
├── Header
│ └── index.tsx
├── List
│ └── index.tsx
├── ListFooter
│ └── index.tsx
└── ListHeader
│ └── index.tsx
├── constants
└── index.tsx
├── package-lock.json
├── package.json
├── tsconfig.json
├── types
└── index.ts
└── yarn.lock
/.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 |
16 | # @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
17 | # The following patterns were generated by expo-cli
18 |
19 | expo-env.d.ts
20 | # @end expo-cli
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to your Expo app 👋
2 |
3 | This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
4 |
5 | ## Get started
6 |
7 | 1. Install dependencies
8 |
9 | ```bash
10 | npm install
11 | ```
12 |
13 | 2. Start the app
14 |
15 | ```bash
16 | npx expo start
17 | ```
18 |
19 | In the output, you'll find options to open the app in a
20 |
21 | - [development build](https://docs.expo.dev/develop/development-builds/introduction/)
22 | - [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
23 | - [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
24 | - [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
25 |
26 | You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
27 |
28 | ## Get a fresh project
29 |
30 | When you're ready, run:
31 |
32 | ```bash
33 | npm run reset-project
34 | ```
35 |
36 | This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
37 |
38 | ## Learn more
39 |
40 | To learn more about developing your project with Expo, look at the following resources:
41 |
42 | - [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
43 | - [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
44 |
45 | ## Join the community
46 |
47 | Join our community of developers creating universal apps.
48 |
49 | - [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
50 | - [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
51 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "expo-drag-selection",
4 | "slug": "expo-drag-selection",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/images/icon.png",
8 | "scheme": "myapp",
9 | "userInterfaceStyle": "automatic",
10 | "splash": {
11 | "image": "./assets/images/splash.png",
12 | "resizeMode": "contain",
13 | "backgroundColor": "#ffffff"
14 | },
15 | "ios": {
16 | "supportsTablet": true,
17 | "infoPlist": {
18 | "UIViewControllerBasedStatusBarAppearance": true
19 | }
20 | },
21 | "android": {
22 | "adaptiveIcon": {
23 | "foregroundImage": "./assets/images/adaptive-icon.png",
24 | "backgroundColor": "#ffffff"
25 | }
26 | },
27 | "web": {
28 | "bundler": "metro",
29 | "output": "static",
30 | "favicon": "./assets/images/favicon.png"
31 | },
32 | "plugins": [
33 | ["expo-router"],
34 | [
35 | "expo-media-library",
36 | {
37 | "photosPermission": "Allow $(PRODUCT_NAME) to access your photos.",
38 | "savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos.",
39 | "isAccessMediaLocationEnabled": true
40 | }
41 | ]
42 | ],
43 | "experiments": {
44 | "typedRoutes": true
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/_layout.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | DarkTheme,
3 | DefaultTheme,
4 | ThemeProvider,
5 | } from "@react-navigation/native";
6 | import { Stack } from "expo-router";
7 | import { useColorScheme } from "react-native";
8 | import { GestureHandlerRootView } from "react-native-gesture-handler";
9 | import "react-native-reanimated";
10 |
11 | export default function RootLayout() {
12 | const colorScheme = useColorScheme();
13 |
14 | return (
15 |
16 |
17 |
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/app/home.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | memo,
3 | useCallback,
4 | useEffect,
5 | useLayoutEffect,
6 | useRef,
7 | useState,
8 | } from "react";
9 | import { RefreshControl, StyleSheet } from "react-native";
10 | import {
11 | SafeAreaView,
12 | useSafeAreaInsets,
13 | } from "react-native-safe-area-context";
14 | import Animated, {
15 | Extrapolation,
16 | interpolate,
17 | runOnJS,
18 | useAnimatedScrollHandler,
19 | useDerivedValue,
20 | useSharedValue,
21 | } from "react-native-reanimated";
22 | import { useNavigation } from "expo-router";
23 | import { COLUMNS, MIDDLE_MARGIN, EDGE_MARGIN } from "@/constants";
24 | import ListHeaderComponent from "@/components/ListHeader";
25 | import ListFooterComponent from "@/components/ListFooter";
26 | import FooterComponent from "@/components/Footer";
27 | import Header from "@/components/Header";
28 | import List from "@/components/List";
29 | import { IMAGE_TYPE, STATE_TYPE } from "@/types";
30 |
31 | const Home = () => {
32 | const navigation = useNavigation();
33 | const translationY = useSharedValue(-130);
34 |
35 | const toggleHeader = (isZero: boolean) => {
36 | if (isZero) {
37 | navigation.setOptions({
38 | headerTintColor: undefined,
39 | });
40 | } else {
41 | navigation.setOptions({
42 | headerTintColor: "white",
43 | });
44 | }
45 | };
46 |
47 | useDerivedValue(() => {
48 | let translateValue = interpolate(
49 | translationY.value,
50 | [-125, -130],
51 | [1, 0],
52 | Extrapolation.CLAMP
53 | );
54 | if (translateValue === 0) {
55 | runOnJS(toggleHeader)(true);
56 | } else {
57 | runOnJS(toggleHeader)(false);
58 | }
59 | }, []);
60 |
61 | const scrollHandler = useAnimatedScrollHandler((event) => {
62 | translationY.value = event.contentOffset.y;
63 | });
64 |
65 | const { bottom } = useSafeAreaInsets();
66 |
67 | const [state, setState] = useState({
68 | imageData: [],
69 | refreshing: false,
70 | activeIndex: [],
71 | canSelect: false,
72 | });
73 |
74 | useLayoutEffect(() => {
75 | navigation.setOptions({
76 | title: "Wallpaper",
77 | headerRight: (props: { canGoBAck: boolean; tintColor: string }) => (
78 |
82 | setState((prev) => ({
83 | ...prev,
84 | canSelect: !prev.canSelect,
85 | activeIndex: [],
86 | }))
87 | }
88 | />
89 | ),
90 | });
91 | }, [navigation, state.canSelect]);
92 |
93 | useEffect(() => {
94 | getImages();
95 | }, []);
96 |
97 | const onRefresh = () => {
98 | getImages();
99 | };
100 |
101 | const getImages = () => {
102 | setState((prev) => ({ ...prev, refreshing: true }));
103 | fetch("https://picsum.photos/v2/list?page=10&limit=100")
104 | .then((res) => res.json())
105 | .then((res) =>
106 | setState((prev) => ({ ...prev, imageData: res, refreshing: false }))
107 | );
108 | };
109 |
110 | const selectImage = (id: IMAGE_TYPE) => {
111 | if (state.canSelect) {
112 | if (state.activeIndex.includes(id)) {
113 | setState((prev) => ({
114 | ...prev,
115 | activeIndex: prev.activeIndex.filter((i) => i !== id),
116 | }));
117 | } else {
118 | setState((prev) => ({
119 | ...prev,
120 | activeIndex: [...prev.activeIndex, id],
121 | }));
122 | }
123 | }
124 | };
125 |
126 | const renderItem = useCallback(
127 | (props: { item: IMAGE_TYPE; index: number }) => {
128 | return (
129 |
134 | );
135 | },
136 | [state.activeIndex, state.canSelect]
137 | );
138 |
139 | const listFooter = useCallback(() => {
140 | return ;
141 | }, [state.imageData]);
142 |
143 | return (
144 |
145 |
146 | {state.imageData?.length > 0 && (
147 | val.id}
160 | refreshControl={
161 |
165 | }
166 | />
167 | )}
168 | {state.activeIndex.length > 0 && (
169 |
172 | setState((prev) => ({
173 | ...prev,
174 | activeIndex: prev.activeIndex.filter((item) => item.id !== id),
175 | }))
176 | }
177 | />
178 | )}
179 |
180 | );
181 | };
182 |
183 | export default memo(Home);
184 |
185 | const styles = StyleSheet.create({
186 | flexOne: {
187 | flex: 1,
188 | },
189 | listContainerStyle: {
190 | padding: EDGE_MARGIN,
191 | gap: MIDDLE_MARGIN,
192 | },
193 | gap: {
194 | gap: MIDDLE_MARGIN,
195 | },
196 | });
197 |
--------------------------------------------------------------------------------
/app/index.tsx:
--------------------------------------------------------------------------------
1 | import { StyleSheet, Text, View } from "react-native";
2 | import React from "react";
3 | import { Link } from "expo-router";
4 | import { useTheme } from "@react-navigation/native";
5 |
6 | const Home = () => {
7 | const theme = useTheme();
8 | return (
9 |
10 |
11 | Album
12 |
13 | Home
14 |
15 | );
16 | };
17 |
18 | export default Home;
19 |
20 | const styles = StyleSheet.create({
21 | container: {
22 | flex: 1,
23 | justifyContent: "center",
24 | alignItems: "center",
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/assets/images/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/images/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/favicon.png
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/partial-react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/partial-react-logo.png
--------------------------------------------------------------------------------
/assets/images/react-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/react-logo.png
--------------------------------------------------------------------------------
/assets/images/react-logo@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/react-logo@2x.png
--------------------------------------------------------------------------------
/assets/images/react-logo@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/react-logo@3x.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arunabhverma/expo-selection/60b9429cfdaec1130dabf35067df767b9192a9b1/assets/images/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/components/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { LayoutChangeEvent, Pressable, StyleSheet } from "react-native";
3 | import { useTheme } from "@react-navigation/native";
4 | import { useSafeAreaInsets } from "react-native-safe-area-context";
5 | import Animated, {
6 | FadeIn,
7 | FadeOut,
8 | LinearTransition,
9 | ZoomOut,
10 | useAnimatedStyle,
11 | useSharedValue,
12 | withTiming,
13 | } from "react-native-reanimated";
14 | import { IMAGE_TYPE } from "@/types";
15 | import { LinearGradient } from "expo-linear-gradient";
16 | import { Image } from "expo-image";
17 |
18 | const ITEM_GAP = 10;
19 | const CONTAINER_MARGIN = 20;
20 | const BORDER_RADIUS = 10;
21 |
22 | const FooterComponent = ({
23 | activeImageIndex,
24 | toggleImage,
25 | }: {
26 | activeImageIndex: IMAGE_TYPE[];
27 | toggleImage: (item: string) => void;
28 | }) => {
29 | const scrollViewRef = useRef(null);
30 | const previousContentWidth = useRef(0);
31 | const itemWidth = useSharedValue(0);
32 | const { colors } = useTheme();
33 | const { bottom } = useSafeAreaInsets();
34 | const scrollViewPadding = useSharedValue(10);
35 | const [data, setData] = useState([]);
36 |
37 | useEffect(() => {
38 | if (data.length !== activeImageIndex.length) {
39 | setData(activeImageIndex);
40 | if (data.length > activeImageIndex.length) {
41 | scrollViewPadding.value += itemWidth.value + ITEM_GAP;
42 | }
43 | }
44 | }, [activeImageIndex]);
45 |
46 | useEffect(() => {
47 | scrollViewPadding.value = withTiming(10);
48 | }, [data]);
49 |
50 | const onLayout = (e: LayoutChangeEvent) => {
51 | const { width } = e.nativeEvent.layout;
52 | itemWidth.value = width;
53 | };
54 |
55 | const animatedPadding = useAnimatedStyle(() => ({
56 | marginRight: scrollViewPadding.value,
57 | }));
58 |
59 | return (
60 |
66 |
70 | {
75 | if (contentWidth > previousContentWidth.current) {
76 | scrollViewRef.current?.scrollToEnd({ animated: true });
77 | }
78 | previousContentWidth.current = contentWidth;
79 | }}
80 | horizontal
81 | contentContainerStyle={styles.flexGrow}
82 | >
83 |
87 | {data.map((item) => (
88 |
95 | toggleImage(item.id)}>
96 |
100 |
101 |
102 | ))}
103 |
104 |
105 |
106 | );
107 | };
108 |
109 | const styles = StyleSheet.create({
110 | mainContainer: {
111 | position: "absolute",
112 | bottom: 0,
113 | width: "100%",
114 | zIndex: 10,
115 | shadowColor: "#000",
116 | shadowOffset: {
117 | width: 0,
118 | height: 10,
119 | },
120 | shadowOpacity: 0.51,
121 | shadowRadius: 13.16,
122 | elevation: 20,
123 | },
124 | flexGrow: {
125 | flexGrow: 1,
126 | },
127 | rowContainer: {
128 | gap: ITEM_GAP,
129 | margin: CONTAINER_MARGIN,
130 | flexDirection: "row",
131 | },
132 | imageStyle: {
133 | width: 50,
134 | aspectRatio: 1,
135 | borderWidth: 1,
136 | borderRadius: BORDER_RADIUS,
137 | },
138 | });
139 |
140 | export default FooterComponent;
141 |
--------------------------------------------------------------------------------
/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | Pressable,
4 | StyleSheet,
5 | View,
6 | Text,
7 | ViewStyle,
8 | useColorScheme,
9 | useWindowDimensions,
10 | } from "react-native";
11 | import { Ionicons } from "@expo/vector-icons";
12 | import { BlurView } from "expo-blur";
13 | import Animated, {
14 | FadeIn,
15 | FadeOut,
16 | LinearTransition,
17 | } from "react-native-reanimated";
18 | import { HEADER_PROPS } from "@/types";
19 |
20 | const HeaderButton = ({
21 | containerStyle,
22 | children,
23 | onPress,
24 | }: {
25 | containerStyle?: ViewStyle;
26 | children: React.ReactNode;
27 | onPress?: () => void;
28 | }) => {
29 | const tint = useColorScheme() || undefined;
30 | return (
31 |
32 |
33 |
34 | {children}
35 |
36 |
37 | );
38 | };
39 |
40 | const Header = ({ tintColor, canSelect, toggleCanSelect }: HEADER_PROPS) => {
41 | const { width } = useWindowDimensions();
42 | return (
43 |
48 |
52 |
58 | toggleCanSelect()}
60 | containerStyle={styles.headerButtonContainer}
61 | >
62 |
63 | {canSelect ? "Cancel" : "Select"}
64 |
65 |
66 |
67 | {!canSelect && (
68 |
73 |
74 |
79 |
80 |
81 | )}
82 |
83 |
84 | );
85 | };
86 |
87 | const styles = StyleSheet.create({
88 | headerContainer: {
89 | flexDirection: "row",
90 | gap: 5,
91 | justifyContent: "flex-end",
92 | alignItems: "center",
93 | },
94 | elevation: {
95 | zIndex: 1,
96 | },
97 | headerButtonContainer: {
98 | backgroundColor: "rgba(100, 100, 100, 0.2)",
99 | borderRadius: 20,
100 | overflow: "hidden",
101 | height: 28,
102 | justifyContent: "center",
103 | alignItems: "center",
104 | paddingHorizontal: 10,
105 | },
106 | buttonContainer: {
107 | backgroundColor: "rgba(100, 100, 100, 0.2)",
108 | borderRadius: 20,
109 | overflow: "hidden",
110 | height: 28,
111 | aspectRatio: 1,
112 | justifyContent: "center",
113 | alignItems: "center",
114 | },
115 | text: {
116 | fontSize: 14,
117 | fontWeight: "500",
118 | },
119 | });
120 |
121 | export default Header;
122 |
--------------------------------------------------------------------------------
/components/List/index.tsx:
--------------------------------------------------------------------------------
1 | import { IMAGE_WIDTH } from "@/constants";
2 | import { LIST_PROPS } from "@/types";
3 | import { useTheme } from "@react-navigation/native";
4 | import { Image } from "expo-image";
5 | import { Pressable, StyleSheet, Text, View } from "react-native";
6 | import Animated, {
7 | LinearTransition,
8 | ZoomIn,
9 | ZoomOut,
10 | } from "react-native-reanimated";
11 |
12 | const AnimatedImage = Animated.createAnimatedComponent(Image);
13 |
14 | const List = ({ item, selectImage, activeImageIndex }: LIST_PROPS) => {
15 | const theme = useTheme();
16 | const isActive = activeImageIndex.includes(item);
17 | const findIndex = activeImageIndex.indexOf(item);
18 |
19 | return (
20 | selectImage(item)}>
21 |
24 | {isActive && (
25 |
30 |
31 | {findIndex + 1}
32 |
33 |
34 | )}
35 |
47 |
48 |
49 | );
50 | };
51 |
52 | const styles = StyleSheet.create({
53 | itemContainer: {
54 | aspectRatio: 1,
55 | position: "relative",
56 | width: IMAGE_WIDTH,
57 | },
58 | closeIcon: {
59 | right: 5,
60 | top: 5,
61 | backgroundColor: "dodgerblue",
62 | position: "absolute",
63 | zIndex: 1,
64 | minWidth: 25,
65 | height: 25,
66 | borderRadius: 50,
67 | borderWidth: 2,
68 | justifyContent: "center",
69 | alignItems: "center",
70 | },
71 | numberStyle: {
72 | fontSize: 13,
73 | fontWeight: "500",
74 | },
75 | imageStyle: {
76 | width: IMAGE_WIDTH,
77 | aspectRatio: 1,
78 | flex: 1,
79 | },
80 | });
81 |
82 | export default List;
83 |
--------------------------------------------------------------------------------
/components/ListFooter/index.tsx:
--------------------------------------------------------------------------------
1 | import { MIDDLE_MARGIN } from "@/constants";
2 | import { useTheme } from "@react-navigation/native";
3 | import { Text, View } from "react-native";
4 |
5 | const ListFooterComponent = ({ imageLength }: { imageLength: number }) => {
6 | const theme = useTheme();
7 | return (
8 |
15 |
23 | {imageLength} Wallpapers
24 |
25 |
26 | );
27 | };
28 |
29 | export default ListFooterComponent;
30 |
--------------------------------------------------------------------------------
/components/ListHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import { useHeaderHeight } from "@react-navigation/elements";
2 | import { LinearGradient } from "expo-linear-gradient";
3 | import Animated, {
4 | Extrapolation,
5 | SharedValue,
6 | interpolate,
7 | useAnimatedStyle,
8 | withTiming,
9 | } from "react-native-reanimated";
10 |
11 | const ListHeaderComponent = ({
12 | translationY,
13 | }: {
14 | translationY: SharedValue;
15 | }) => {
16 | const headerSize = useHeaderHeight();
17 | const animatedStyle = useAnimatedStyle(() => {
18 | let translateValue = interpolate(
19 | translationY.value,
20 | [-125, -130],
21 | [1, 0],
22 | Extrapolation.CLAMP
23 | );
24 | return {
25 | opacity: withTiming(translateValue),
26 | };
27 | });
28 |
29 | return (
30 |
43 |
47 |
48 | );
49 | };
50 |
51 | export default ListHeaderComponent;
52 |
--------------------------------------------------------------------------------
/constants/index.tsx:
--------------------------------------------------------------------------------
1 | import { Dimensions } from "react-native";
2 | const width = Dimensions.get("window").width;
3 |
4 | const COLUMN_SIZE = width / 3;
5 | const MIDDLE_MARGIN = 5;
6 | const EDGE_MARGIN = 0;
7 | const IMAGE_SPACING = (2 * MIDDLE_MARGIN + 2 * EDGE_MARGIN) / 3;
8 | const IMAGE_WIDTH = COLUMN_SIZE - IMAGE_SPACING;
9 | const COLUMNS = 3;
10 |
11 | export {
12 | COLUMNS,
13 | COLUMN_SIZE,
14 | MIDDLE_MARGIN,
15 | EDGE_MARGIN,
16 | IMAGE_SPACING,
17 | IMAGE_WIDTH,
18 | };
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "expo-drag-selection",
3 | "main": "expo-router/entry",
4 | "version": "1.0.0",
5 | "scripts": {
6 | "start": "expo start",
7 | "reset-project": "node ./scripts/reset-project.js",
8 | "android": "expo start --android",
9 | "ios": "expo start --ios",
10 | "web": "expo start --web",
11 | "test": "jest --watchAll",
12 | "lint": "expo lint"
13 | },
14 | "jest": {
15 | "preset": "jest-expo"
16 | },
17 | "dependencies": {
18 | "@expo/vector-icons": "^14.0.0",
19 | "@react-navigation/elements": "^1.3.30",
20 | "@react-navigation/native": "^6.0.2",
21 | "expo": "~51.0.2",
22 | "expo-blur": "~13.0.2",
23 | "expo-constants": "~16.0.1",
24 | "expo-font": "~12.0.4",
25 | "expo-image": "~1.12.9",
26 | "expo-linear-gradient": "~13.0.2",
27 | "expo-linking": "~6.3.1",
28 | "expo-media-library": "~16.0.3",
29 | "expo-router": "~3.5.11",
30 | "expo-splash-screen": "~0.27.4",
31 | "expo-status-bar": "~1.12.1",
32 | "expo-system-ui": "~3.0.4",
33 | "expo-web-browser": "~13.0.3",
34 | "react": "18.2.0",
35 | "react-dom": "18.2.0",
36 | "react-native": "0.74.1",
37 | "react-native-gesture-handler": "~2.16.1",
38 | "react-native-reanimated": "~3.10.1",
39 | "react-native-safe-area-context": "4.10.1",
40 | "react-native-screens": "3.31.1",
41 | "react-native-web": "~0.19.10"
42 | },
43 | "devDependencies": {
44 | "@babel/core": "^7.20.0",
45 | "@types/jest": "^29.5.12",
46 | "@types/react": "~18.2.45",
47 | "@types/react-test-renderer": "^18.0.7",
48 | "jest": "^29.2.1",
49 | "jest-expo": "~51.0.1",
50 | "react-test-renderer": "18.2.0",
51 | "typescript": "~5.3.3"
52 | },
53 | "private": true
54 | }
55 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true,
5 | "paths": {
6 | "@/*": [
7 | "./*"
8 | ]
9 | }
10 | },
11 | "include": [
12 | "**/*.ts",
13 | "**/*.tsx",
14 | ".expo/types/**/*.ts",
15 | "expo-env.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/types/index.ts:
--------------------------------------------------------------------------------
1 | export interface IMAGE_TYPE {
2 | author: string;
3 | download_url: string;
4 | height: number;
5 | id: string;
6 | url: string;
7 | width: number;
8 | }
9 |
10 | export interface STATE_TYPE {
11 | imageData: IMAGE_TYPE[];
12 | refreshing: boolean;
13 | activeIndex: IMAGE_TYPE[];
14 | canSelect: boolean;
15 | }
16 |
17 | export interface LIST_PROPS {
18 | item: IMAGE_TYPE;
19 | index: number;
20 | selectImage: (item: IMAGE_TYPE) => void;
21 | activeImageIndex: IMAGE_TYPE[];
22 | }
23 |
24 | export interface HEADER_PROPS {
25 | tintColor: string;
26 | canSelect: boolean;
27 | toggleCanSelect: () => void;
28 | }
29 |
--------------------------------------------------------------------------------