.
675 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "notificationapp",
4 | "slug": "notificationapp",
5 | "privacy": "public",
6 | "platforms": [
7 | "ios",
8 | "android",
9 | "web"
10 | ],
11 | "version": "1.0.0",
12 | "orientation": "portrait",
13 | "icon": "./assets/icon.png",
14 | "splash": {
15 | "image": "./assets/splash.png",
16 | "resizeMode": "contain",
17 | "backgroundColor": "#FFFFFF"
18 | },
19 | "updates": {
20 | "fallbackToCacheTimeout": 0
21 | },
22 | "assetBundlePatterns": [
23 | "**/*"
24 | ],
25 | "ios": {
26 | "supportsTablet": true
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/assets/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/assets/back.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/assets/icon.png
--------------------------------------------------------------------------------
/assets/left-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/assets/left-arrow.png
--------------------------------------------------------------------------------
/assets/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/assets/plus.png
--------------------------------------------------------------------------------
/assets/right-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/assets/right-arrow.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | function getAliasesFromTsConfig() {
2 | const tsConfig = require('./tsconfig.json');
3 | const paths = tsConfig.compilerOptions.paths;
4 | let alias = {};
5 | Object.keys(paths).forEach((key) => {
6 | alias[key] = `./${paths[key][0]}`;
7 | });
8 | return alias;
9 | }
10 |
11 | module.exports = function (api) {
12 | api.cache(true);
13 |
14 | const plugins = [
15 | [
16 | 'module-resolver',
17 | {
18 | alias: getAliasesFromTsConfig(),
19 | extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
20 | root: ['./src']
21 | }
22 | ]
23 | ];
24 |
25 | const presets = ['module:metro-react-native-babel-preset'];
26 |
27 | return {
28 | env: {
29 | development: {
30 | plugins: [
31 | ...plugins,
32 | [
33 | 'transform-remove-console',
34 | { exclude: ['disableYellowBox', 'error', 'info', 'log'] }
35 | ]
36 | ],
37 | presets: presets
38 | },
39 | production: {
40 | plugins: [
41 | ...plugins,
42 | '@babel/plugin-transform-runtime',
43 | '@babel/plugin-transform-react-inline-elements',
44 | ['transform-remove-console', { exclude: ['error'] }]
45 | ],
46 | presets: presets
47 | }
48 | },
49 | plugins,
50 | presets
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject",
9 | "lint": "eslint .",
10 | "lint:fix": "eslint . --fix"
11 | },
12 | "dependencies": {
13 | "@react-native-async-storage/async-storage": "~1.15.0",
14 | "@react-native-community/datetimepicker": "3.5.2",
15 | "@react-native-community/masked-view": "0.1.10",
16 | "@react-navigation/native": "^5.9.4",
17 | "@react-navigation/stack": "^5.14.5",
18 | "expo": "^42.0.0",
19 | "expo-calendar": "~9.2.2",
20 | "expo-constants": "~11.0.1",
21 | "expo-localization": "~10.2.0",
22 | "immer": "^9.0.5",
23 | "moment": "^2.29.1",
24 | "react": "16.13.1",
25 | "react-dom": "16.13.1",
26 | "react-native": "https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz",
27 | "react-native-calendar-strip": "^2.2.3",
28 | "react-native-calendars": "^1.1264.0",
29 | "react-native-gesture-handler": "~1.10.2",
30 | "react-native-get-random-values": "~1.7.0",
31 | "react-native-modal-datetime-picker": "^10.0.0",
32 | "react-native-reanimated": "~2.2.0",
33 | "react-native-safe-area-context": "^3.2.0",
34 | "react-native-screens": "~3.4.0",
35 | "react-native-web": "^0.17.1",
36 | "uuid": "^8.3.2",
37 | "zustand": "^3.5.6"
38 | },
39 | "devDependencies": {
40 | "@babel/core": "^7.14.6",
41 | "@babel/parser": "^7.12.13",
42 | "@babel/plugin-transform-react-inline-elements": "^7.9.0",
43 | "@babel/plugin-transform-runtime": "^7.9.0",
44 | "@babel/preset-typescript": "^7.14.5",
45 | "@babel/runtime": "^7.7.2",
46 | "@react-native-community/eslint-config": "^2.0.0",
47 | "@testing-library/react-native": "^7.2.0",
48 | "@types/jest": "^26.0.24",
49 | "@types/node": "^16.3.1",
50 | "@types/react": "^17.0.14",
51 | "@types/react-native": "^0.64.11",
52 | "@types/react-navigation": "^3.4.0",
53 | "@types/url-parse": "^1.4.3",
54 | "@typescript-eslint/eslint-plugin": "^4.24.0",
55 | "@typescript-eslint/parser": "^4.24.0",
56 | "ast-parser": "^0.0.6",
57 | "babel-core": "7.0.0-bridge.0",
58 | "babel-eslint": "^10.1.0",
59 | "babel-jest": "^26.6.3",
60 | "babel-plugin-lodash": "^3.3.4",
61 | "babel-plugin-module-resolver": "^4.0.0",
62 | "babel-plugin-transform-remove-console": "^6.9.4",
63 | "babel-preset-expo": "8.4.0",
64 | "eslint": "^7.30.0",
65 | "eslint-config-satya164": "^3.1.6",
66 | "eslint-import-resolver-babel-module": "^5.1.2",
67 | "jest": "^27.0.6",
68 | "jest-expo": "^42.0.1",
69 | "metro-react-native-babel-preset": "^0.66.1",
70 | "prettier": "^2.3.2",
71 | "react-test-renderer": "17.0.1",
72 | "typescript": "^4.3.5"
73 | },
74 | "private": true
75 | }
76 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## calendar Event App
2 |
3 | This app will save your calendar event in your native iOS and android calendar.
4 |
5 | - build using [expo](https://expo.io).
6 |
7 | ## Getting Started
8 |
9 | ### Installation
10 |
11 | ```
12 | yarn install
13 | ```
14 |
15 | ## Video
16 |
17 |
18 |

19 |
20 |
21 | ### Screenshots
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/screenshots/screenshot1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/screenshots/screenshot1.png
--------------------------------------------------------------------------------
/screenshots/screenshot2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/screenshots/screenshot2.png
--------------------------------------------------------------------------------
/screenshots/screenshot3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/screenshots/screenshot3.png
--------------------------------------------------------------------------------
/screenshots/screenshot4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/screenshots/screenshot4.png
--------------------------------------------------------------------------------
/screenshots/screenshot5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skdev24/calendar-event-app/338b55a92cca0800a2c024e8880a67c8c1627ab5/screenshots/screenshot5.png
--------------------------------------------------------------------------------
/src/components/app-wrapper.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { useComponent } from '@calendar/hooks';
3 | import { View } from 'react-native';
4 | import { useStore } from '@calendar/store';
5 |
6 | const MainView = ({ children, backgroundColor = '#FFFFFF', ...rest }) => {
7 | const init = useStore((store) => store.init);
8 | useEffect(() => {
9 | init();
10 | }, []);
11 | return (
12 |
19 | {children}
20 |
21 | );
22 | };
23 |
24 | export default function AppView(props) {
25 | const SafeView = useComponent(MainView, props);
26 | return ;
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import AppWrapper from './app-wrapper';
2 | import Task from './task';
3 |
4 | export { AppWrapper, Task };
5 |
--------------------------------------------------------------------------------
/src/components/task.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Modal,
4 | Platform,
5 | Pressable,
6 | StyleSheet,
7 | Text,
8 | View
9 | } from 'react-native';
10 |
11 | const styles = StyleSheet.create({
12 | cardMain: {
13 | position: 'absolute',
14 | top: 100,
15 | width: 327,
16 | alignSelf: 'center',
17 | zIndex: 1000,
18 | elevation: 1000,
19 | paddingBottom: 54
20 | },
21 | card: {
22 | width: 327,
23 | borderRadius: 20,
24 | backgroundColor: '#ffffff',
25 | alignSelf: 'center'
26 | },
27 | container: {
28 | flex: 1,
29 | backgroundColor: 'rgba(0,0,0,0.5)'
30 | },
31 | btnContainer: ({ pressed }) => ({
32 | position: 'absolute',
33 | alignSelf: 'center',
34 | bottom: 0,
35 | right: 0,
36 | left: 0,
37 | backgroundColor: '#FFFFFF',
38 | height: 44,
39 | borderRadius: 20,
40 | justifyContent: 'center',
41 | alignItems: 'center',
42 | opacity: pressed ? 0.5 : 1
43 | }),
44 | textContainer: { textAlign: 'center', fontSize: 17, fontWeight: '500' }
45 | });
46 |
47 | // = ({ isModalVisible, children }) =>
48 | export default class Task extends React.Component {
49 | render() {
50 | const { isModalVisible, children, setModalVisible } = this.props;
51 | return (
52 | setModalVisible(false)}
57 | >
58 |
70 |
71 | {children}
72 | setModalVisible(false)}
75 | >
76 | Cancel
77 |
78 |
79 |
80 |
81 | );
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/hooks/index.js:
--------------------------------------------------------------------------------
1 | import useComponent from './useComponent';
2 | import useKeyboardHeight from './useKeyboardHeight';
3 |
4 | export { useComponent, useKeyboardHeight };
5 |
--------------------------------------------------------------------------------
/src/hooks/useComponent.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 |
3 | export default function useComponent(Component, props) {
4 | const propsRef = useRef(props);
5 | propsRef.current = props;
6 | useEffect(() => {
7 | propsRef.current = null;
8 | }, []);
9 | return useRef(() => {
10 | const props = propsRef.current;
11 | if (props === null) {
12 | throw new Error(
13 | 'The returned component must be rendered in the same renderer phase as the hook'
14 | );
15 | }
16 | return ;
17 | }).current;
18 | }
19 |
--------------------------------------------------------------------------------
/src/hooks/useKeyboardHeight.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react';
2 | import { Keyboard, Platform } from 'react-native';
3 |
4 | const useKeyboardHeight = (platforms = ['ios', 'android']) => {
5 | const [keyboardHeight, setKeyboardHeight] = useState(0);
6 |
7 | const showSubscription = useRef(null);
8 | const hideSubscription = useRef(null);
9 |
10 | useEffect(() => {
11 | if (isEventRequired(platforms)) {
12 | showSubscription.current = Keyboard.addListener(
13 | 'keyboardDidShow',
14 | keyboardDidShow
15 | );
16 | hideSubscription.current = Keyboard.addListener(
17 | 'keyboardDidHide',
18 | keyboardDidHide
19 | );
20 |
21 | // cleanup function
22 | return () => {
23 | showSubscription.current?.remove();
24 | hideSubscription.current?.remove();
25 | };
26 | } else {
27 | return () => {};
28 | }
29 | }, []);
30 |
31 | const isEventRequired = (platforms) => {
32 | try {
33 | return (
34 | platforms?.map((p) => p?.toLowerCase()).indexOf(Platform.OS) !== -1 ||
35 | !platforms
36 | );
37 | } catch (ex) {
38 | // Damn
39 | }
40 |
41 | return false;
42 | };
43 |
44 | const keyboardDidShow = (frames) => {
45 | setKeyboardHeight(frames.endCoordinates.height);
46 | };
47 |
48 | const keyboardDidHide = () => {
49 | setKeyboardHeight(0);
50 | };
51 |
52 | return keyboardHeight;
53 | };
54 |
55 | export default useKeyboardHeight;
56 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Platform, StatusBar } from 'react-native';
3 | import { SafeAreaProvider } from 'react-native-safe-area-context';
4 | import { AppWrapper } from '@calendar/components';
5 | import { CalendarNavigation } from '@calendar/navigation';
6 | import * as Calendar from 'expo-calendar';
7 |
8 | class App extends Component {
9 | async componentDidMount() {
10 | await this._askForCalendarPermissions();
11 | await this._askForReminderPermissions();
12 |
13 | StatusBar.pushStackEntry({
14 | animated: true,
15 | barStyle: 'dark-content'
16 | });
17 | }
18 |
19 | _askForCalendarPermissions = async () => {
20 | const { status } = await Calendar.requestCalendarPermissionsAsync();
21 | if (status === 'granted') {
22 | const calendars = await Calendar.getCalendarsAsync(
23 | Calendar.EntityTypes.EVENT
24 | );
25 | console.log('Here are all your calendars:');
26 | console.log({ calendars });
27 | }
28 | };
29 |
30 | _askForReminderPermissions = async () => {
31 | if (Platform.OS === 'android') {
32 | return true;
33 | }
34 |
35 | const { status } = await Calendar.requestRemindersPermissionsAsync();
36 | if (status === 'granted') {
37 | const calendars = await Calendar.getRemindersPermissionsAsync(
38 | Calendar.EntityTypes.REMINDER
39 | );
40 | console.log('Here are all your calendars:');
41 | console.log({ calendars });
42 | }
43 | };
44 |
45 | render = () => (
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
54 | export default App;
55 |
--------------------------------------------------------------------------------
/src/navigation/calendar-navigation.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import { NavigationContainer } from '@react-navigation/native';
3 | import { createStackNavigator } from '@react-navigation/stack';
4 | import { nativeStackConfig } from './nativeStackConfig';
5 |
6 | import Routes from './routes';
7 | import { CreateTask, Home } from '@calendar/screens';
8 | import { useStore } from '@calendar/store';
9 |
10 | const Stack = createStackNavigator();
11 |
12 | function MainNavigatorWrapper() {
13 | return (
14 |
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | const AppContainer = React.forwardRef((props, ref) => (
22 |
23 |
24 |
25 | ));
26 |
27 | AppContainer.displayName = 'AppContainer';
28 |
29 | export default React.memo(AppContainer);
30 |
--------------------------------------------------------------------------------
/src/navigation/index.js:
--------------------------------------------------------------------------------
1 | import CalendarNavigation from './calendar-navigation';
2 | import Routes from './routes';
3 |
4 | export { CalendarNavigation, Routes };
5 |
--------------------------------------------------------------------------------
/src/navigation/nativeStackConfig.js:
--------------------------------------------------------------------------------
1 | export const nativeStackConfig = {
2 | mode: 'modal',
3 | cardStyle: {
4 | backgroundColor: 'transparent'
5 | },
6 | screenOptions: {
7 | gestureEnabled: true,
8 | headerShown: false,
9 | contentStyle: {
10 | backgroundColor: 'transparent'
11 | },
12 | showDragIndicator: false,
13 | springDamping: 0.8,
14 | stackPresentation: 'modal',
15 | transitionDuration: 0.35
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/navigation/routes.js:
--------------------------------------------------------------------------------
1 | const Routes = {
2 | HOME: 'home',
3 | CREATE_TASK: 'CreateTask'
4 | };
5 |
6 | export default Routes;
7 |
--------------------------------------------------------------------------------
/src/screens/CreateTask/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from 'react';
2 | import {
3 | Alert,
4 | Dimensions,
5 | Image,
6 | ScrollView,
7 | StyleSheet,
8 | Switch,
9 | Text,
10 | TextInput,
11 | TouchableOpacity,
12 | View
13 | } from 'react-native';
14 | import { CalendarList } from 'react-native-calendars';
15 | import moment from 'moment';
16 | import * as Calendar from 'expo-calendar';
17 | import * as Localization from 'expo-localization';
18 | import DateTimePicker from 'react-native-modal-datetime-picker';
19 | import { v4 as uuidv4 } from 'uuid';
20 | import { useKeyboardHeight } from '@calendar/hooks';
21 | import { useStore } from '@calendar/store';
22 | import { Routes } from '@calendar/navigation';
23 | import { SafeAreaView } from 'react-native-safe-area-context';
24 |
25 | const { width: vw } = Dimensions.get('window');
26 | // moment().format('YYYY/MM/DD')
27 |
28 | const styles = StyleSheet.create({
29 | createTaskButton: {
30 | width: 252,
31 | height: 48,
32 | alignSelf: 'center',
33 | marginTop: 40,
34 | borderRadius: 5,
35 | justifyContent: 'center'
36 | },
37 | separator: {
38 | height: 0.5,
39 | width: '100%',
40 | backgroundColor: '#979797',
41 | alignSelf: 'center',
42 | marginVertical: 20
43 | },
44 | notes: {
45 | color: '#9CAAC4',
46 | fontSize: 16,
47 | fontWeight: '600'
48 | },
49 | notesContent: {
50 | height: 0.5,
51 | width: '100%',
52 | backgroundColor: '#979797',
53 | alignSelf: 'center',
54 | marginVertical: 20
55 | },
56 | learn: {
57 | height: 23,
58 | width: 51,
59 | backgroundColor: '#F8D557',
60 | justifyContent: 'center',
61 | borderRadius: 5
62 | },
63 | design: {
64 | height: 23,
65 | width: 59,
66 | backgroundColor: '#62CCFB',
67 | justifyContent: 'center',
68 | borderRadius: 5,
69 | marginRight: 7
70 | },
71 | readBook: {
72 | height: 23,
73 | width: 83,
74 | backgroundColor: '#4CD565',
75 | justifyContent: 'center',
76 | borderRadius: 5,
77 | marginRight: 7
78 | },
79 | title: {
80 | height: 25,
81 | borderColor: '#5DD976',
82 | borderLeftWidth: 1,
83 | paddingLeft: 8,
84 | fontSize: 19
85 | },
86 | taskContainer: {
87 | height: 400,
88 | width: 327,
89 | alignSelf: 'center',
90 | borderRadius: 20,
91 | shadowColor: '#2E66E7',
92 | backgroundColor: '#ffffff',
93 | shadowOffset: {
94 | width: 3,
95 | height: 3
96 | },
97 | shadowRadius: 20,
98 | shadowOpacity: 0.2,
99 | elevation: 5,
100 | padding: 22
101 | },
102 | calenderContainer: {
103 | marginTop: 30,
104 | width: 350,
105 | height: 350,
106 | alignSelf: 'center'
107 | },
108 | newTask: {
109 | alignSelf: 'center',
110 | fontSize: 20,
111 | width: 120,
112 | height: 25,
113 | textAlign: 'center'
114 | },
115 | backButton: {
116 | flexDirection: 'row',
117 | marginTop: 20,
118 | width: '100%',
119 | alignItems: 'center'
120 | },
121 | container: {
122 | flex: 1,
123 | backgroundColor: '#eaeef7'
124 | }
125 | });
126 |
127 | export default function CreateTask({ navigation, route }) {
128 | const { updateTodo } = useStore((state) => ({
129 | updateTodo: state.updateTodo
130 | }));
131 |
132 | const keyboardHeight = useKeyboardHeight();
133 |
134 | const createNewCalendar = route.params?.createNewCalendar ?? (() => null);
135 | const updateCurrentTask = route.params?.updateCurrentTask ?? (() => null);
136 | const currentDate = route.params?.currentDate ?? (() => null);
137 |
138 | const [selectedDay, setSelectedDay] = useState({
139 | [`${moment().format('YYYY')}-${moment().format('MM')}-${moment().format(
140 | 'DD'
141 | )}`]: {
142 | selected: true,
143 | selectedColor: '#2E66E7'
144 | }
145 | });
146 | const [currentDay, setCurrentDay] = useState(moment().format());
147 | const [taskText, setTaskText] = useState('');
148 | const [notesText, setNotesText] = useState('');
149 | const [visibleHeight, setVisibleHeight] = useState(
150 | Dimensions.get('window').height
151 | );
152 | const [isAlarmSet, setAlarmSet] = useState(false);
153 | const [alarmTime, setAlarmTime] = useState(moment().format());
154 | const [isDateTimePickerVisible, setDateTimePickerVisible] = useState(false);
155 |
156 | useEffect(() => {
157 | if (keyboardHeight > 0) {
158 | setVisibleHeight(Dimensions.get('window').height - keyboardHeight);
159 | } else if (keyboardHeight === 0) {
160 | setVisibleHeight(Dimensions.get('window').height);
161 | }
162 | }, [keyboardHeight]);
163 |
164 | const handleAlarmSet = () => {
165 | setAlarmSet(!isAlarmSet);
166 | };
167 |
168 | const synchronizeCalendar = async () => {
169 | const calendarId = await createNewCalendar();
170 | try {
171 | const createEventId = await addEventsToCalendar(calendarId);
172 | handleCreateEventData(createEventId);
173 | } catch (e) {
174 | Alert.alert(e.message);
175 | }
176 | };
177 |
178 | const addEventsToCalendar = async (calendarId) => {
179 | const event = {
180 | title: taskText,
181 | notes: notesText,
182 | startDate: moment(alarmTime).add(0, 'm').toDate(),
183 | endDate: moment(alarmTime).add(5, 'm').toDate(),
184 | timeZone: Localization.timezone
185 | };
186 |
187 | try {
188 | const createEventAsyncResNew = await Calendar.createEventAsync(
189 | calendarId.toString(),
190 | event
191 | );
192 | return createEventAsyncResNew;
193 | } catch (error) {
194 | console.log(error);
195 | }
196 | };
197 |
198 | const showDateTimePicker = () => setDateTimePickerVisible(true);
199 |
200 | const hideDateTimePicker = () => setDateTimePickerVisible(false);
201 |
202 | const handleCreateEventData = async (createEventId) => {
203 | const creatTodo = {
204 | key: uuidv4(),
205 | date: `${moment(currentDay).format('YYYY')}-${moment(currentDay).format(
206 | 'MM'
207 | )}-${moment(currentDay).format('DD')}`,
208 | todoList: [
209 | {
210 | key: uuidv4(),
211 | title: taskText,
212 | notes: notesText,
213 | alarm: {
214 | time: alarmTime,
215 | isOn: isAlarmSet,
216 | createEventAsyncRes: createEventId
217 | },
218 | color: `rgb(${Math.floor(
219 | Math.random() * Math.floor(256)
220 | )},${Math.floor(Math.random() * Math.floor(256))},${Math.floor(
221 | Math.random() * Math.floor(256)
222 | )})`
223 | }
224 | ],
225 | markedDot: {
226 | date: currentDay,
227 | dots: [
228 | {
229 | key: uuidv4(),
230 | color: '#2E66E7',
231 | selectedDotColor: '#2E66E7'
232 | }
233 | ]
234 | }
235 | };
236 | navigation.navigate(Routes.HOME);
237 | await updateTodo(creatTodo);
238 | updateCurrentTask(currentDate);
239 | };
240 |
241 | const handleDatePicked = (date) => {
242 | const selectedDatePicked = currentDay;
243 | const hour = moment(date).hour();
244 | const minute = moment(date).minute();
245 | const newModifiedDay = moment(selectedDatePicked).hour(hour).minute(minute);
246 | setAlarmTime(newModifiedDay);
247 | hideDateTimePicker();
248 | };
249 |
250 | return (
251 |
252 |
260 |
261 |
262 |
267 |
272 |
273 | navigation.navigate(Routes.HOME)}
275 | style={{ marginRight: vw / 2 - 120, marginLeft: 20 }}
276 | >
277 |
282 |
283 |
284 | New Task
285 |
286 |
287 | {
299 | setSelectedDay({
300 | [day.dateString]: {
301 | selected: true,
302 | selectedColor: '#2E66E7'
303 | }
304 | });
305 | setCurrentDay(day.dateString);
306 | setAlarmTime(day.dateString);
307 | }}
308 | monthFormat="yyyy MMMM"
309 | hideArrows
310 | markingType="custom"
311 | theme={{
312 | selectedDayBackgroundColor: '#2E66E7',
313 | selectedDayTextColor: '#ffffff',
314 | todayTextColor: '#2E66E7',
315 | backgroundColor: '#eaeef7',
316 | calendarBackground: '#eaeef7',
317 | textDisabledColor: '#d9dbe0'
318 | }}
319 | markedDates={selectedDay}
320 | />
321 |
322 |
323 |
329 |
336 | Suggestion
337 |
338 |
339 |
340 |
341 | Read book
342 |
343 |
344 |
345 |
346 | Design
347 |
348 |
349 |
350 |
351 | Learn
352 |
353 |
354 |
355 |
356 |
357 | Notes
358 |
368 |
369 |
370 |
371 |
378 | Times
379 |
380 | showDateTimePicker()}
382 | style={{
383 | height: 25,
384 | marginTop: 3
385 | }}
386 | >
387 |
388 | {moment(alarmTime).format('h:mm A')}
389 |
390 |
391 |
392 |
393 |
400 |
401 |
408 | Alarm
409 |
410 |
416 |
417 | {moment(alarmTime).format('h:mm A')}
418 |
419 |
420 |
421 |
422 |
423 |
424 | {
434 | if (isAlarmSet) {
435 | await synchronizeCalendar();
436 | }
437 | if (!isAlarmSet) {
438 | handleCreateEventData();
439 | }
440 | }}
441 | >
442 |
449 | ADD YOUR TASK
450 |
451 |
452 |
453 |
454 |
455 |
456 | );
457 | }
458 |
--------------------------------------------------------------------------------
/src/screens/Home/index.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, useEffect, useState } from 'react';
2 | import {
3 | Alert,
4 | Dimensions,
5 | Image,
6 | Platform,
7 | ScrollView,
8 | StyleSheet,
9 | Switch,
10 | Text,
11 | TextInput,
12 | TouchableOpacity,
13 | View
14 | } from 'react-native';
15 | import moment from 'moment';
16 | import * as Calendar from 'expo-calendar';
17 | import * as Localization from 'expo-localization';
18 |
19 | import CalendarStrip from 'react-native-calendar-strip';
20 | import DateTimePicker from 'react-native-modal-datetime-picker';
21 | import { Task } from '@calendar/components';
22 | import { useStore } from '@calendar/store';
23 | import { SafeAreaView } from 'react-native-safe-area-context';
24 |
25 | const styles = StyleSheet.create({
26 | taskListContent: {
27 | height: 100,
28 | width: 327,
29 | alignSelf: 'center',
30 | borderRadius: 10,
31 | shadowColor: '#2E66E7',
32 | backgroundColor: '#ffffff',
33 | marginTop: 10,
34 | marginBottom: 10,
35 | shadowOffset: {
36 | width: 3,
37 | height: 3
38 | },
39 | shadowRadius: 5,
40 | shadowOpacity: 0.2,
41 | elevation: 3,
42 | flexDirection: 'row',
43 | justifyContent: 'space-between',
44 | alignItems: 'center'
45 | },
46 | viewTask: {
47 | position: 'absolute',
48 | bottom: 40,
49 | right: 17,
50 | height: 60,
51 | width: 60,
52 | backgroundColor: '#2E66E7',
53 | borderRadius: 30,
54 | justifyContent: 'center',
55 | alignItems: 'center',
56 | shadowColor: '#2E66E7',
57 | shadowOffset: {
58 | width: 0,
59 | height: 5
60 | },
61 | shadowRadius: 30,
62 | shadowOpacity: 0.5,
63 | elevation: 5,
64 | zIndex: 999
65 | },
66 | deleteButton: {
67 | backgroundColor: '#ff6347',
68 | width: 100,
69 | height: 38,
70 | alignSelf: 'center',
71 | marginTop: 40,
72 | borderRadius: 5,
73 | justifyContent: 'center'
74 | },
75 | updateButton: {
76 | backgroundColor: '#2E66E7',
77 | width: 100,
78 | height: 38,
79 | alignSelf: 'center',
80 | marginTop: 40,
81 | borderRadius: 5,
82 | justifyContent: 'center',
83 | marginRight: 20
84 | },
85 | separator: {
86 | height: 0.5,
87 | width: '100%',
88 | backgroundColor: '#979797',
89 | alignSelf: 'center',
90 | marginVertical: 20
91 | },
92 | notesContent: {
93 | height: 0.5,
94 | width: '100%',
95 | backgroundColor: '#979797',
96 | alignSelf: 'center',
97 | marginVertical: 20
98 | },
99 | learn: {
100 | height: 23,
101 | width: 51,
102 | backgroundColor: '#F8D557',
103 | justifyContent: 'center',
104 | borderRadius: 5
105 | },
106 | design: {
107 | height: 23,
108 | width: 59,
109 | backgroundColor: '#62CCFB',
110 | justifyContent: 'center',
111 | borderRadius: 5,
112 | marginRight: 7
113 | },
114 | readBook: {
115 | height: 23,
116 | width: 83,
117 | backgroundColor: '#4CD565',
118 | justifyContent: 'center',
119 | borderRadius: 5,
120 | marginRight: 7
121 | },
122 | title: {
123 | height: 25,
124 | borderColor: '#5DD976',
125 | borderLeftWidth: 1,
126 | paddingLeft: 8,
127 | fontSize: 19
128 | },
129 | taskContainer: {
130 | height: 475,
131 | width: 327,
132 | alignSelf: 'center',
133 | borderRadius: 20,
134 | shadowColor: '#2E66E7',
135 | backgroundColor: '#ffffff',
136 | shadowOffset: {
137 | width: 3,
138 | height: 3
139 | },
140 | shadowRadius: 20,
141 | shadowOpacity: 0.2,
142 | elevation: 5,
143 | padding: 22
144 | }
145 | });
146 |
147 | const datesWhitelist = [
148 | {
149 | start: moment(),
150 | end: moment().add(365, 'days') // total 4 days enabled
151 | }
152 | ];
153 |
154 | export default function Home({ navigation }) {
155 | const { updateSelectedTask, deleteSelectedTask, todo } = useStore(
156 | (state) => ({
157 | updateSelectedTask: state.updateSelectedTask,
158 | deleteSelectedTask: state.deleteSelectedTask,
159 | todo: state.todo
160 | })
161 | );
162 |
163 | const [todoList, setTodoList] = useState([]);
164 | const [markedDate, setMarkedDate] = useState([]);
165 | const [currentDate, setCurrentDate] = useState(
166 | `${moment().format('YYYY')}-${moment().format('MM')}-${moment().format(
167 | 'DD'
168 | )}`
169 | );
170 | const [isModalVisible, setModalVisible] = useState(false);
171 | const [selectedTask, setSelectedTask] = useState(null);
172 | const [isDateTimePickerVisible, setDateTimePickerVisible] = useState(false);
173 |
174 | useEffect(() => {
175 | handleDeletePreviousDayTask(todo);
176 | }, [todo, currentDate]);
177 |
178 | const handleDeletePreviousDayTask = async (oldTodo) => {
179 | try {
180 | if (oldTodo !== []) {
181 | const todayDate = `${moment().format('YYYY')}-${moment().format(
182 | 'MM'
183 | )}-${moment().format('DD')}`;
184 | const checkDate = moment(todayDate);
185 | await oldTodo.filter((item) => {
186 | const currDate = moment(item.date);
187 | const checkedDate = checkDate.diff(currDate, 'days');
188 | if (checkedDate > 0) {
189 | item.todoList.forEach(async (listValue) => {
190 | try {
191 | await Calendar.deleteEventAsync(
192 | listValue.alarm.createEventAsyncRes.toString()
193 | );
194 | } catch (error) {
195 | console.log(error);
196 | }
197 | });
198 | return false;
199 | }
200 | return true;
201 | });
202 |
203 | // await AsyncStorage.setItem('TODO', JSON.stringify(updatedList));
204 | updateCurrentTask(currentDate);
205 | }
206 | } catch (error) {
207 | // Error retrieving data
208 | }
209 | };
210 |
211 | const handleModalVisible = () => {
212 | setModalVisible(!isModalVisible);
213 | };
214 |
215 | const updateCurrentTask = async (currentDate) => {
216 | try {
217 | if (todo !== [] && todo) {
218 | const markDot = todo.map((item) => item.markedDot);
219 | const todoLists = todo.filter((item) => {
220 | if (currentDate === item.date) {
221 | return true;
222 | }
223 | return false;
224 | });
225 | setMarkedDate(markDot);
226 | if (todoLists.length !== 0) {
227 | setTodoList(todoLists[0].todoList);
228 | } else {
229 | setTodoList([]);
230 | }
231 | }
232 | } catch (error) {
233 | console.log('updateCurrentTask', error.message);
234 | }
235 | };
236 |
237 | const showDateTimePicker = () => setDateTimePickerVisible(true);
238 |
239 | const hideDateTimePicker = () => setDateTimePickerVisible(false);
240 |
241 | const handleDatePicked = (date) => {
242 | let prevSelectedTask = JSON.parse(JSON.stringify(selectedTask));
243 | const selectedDatePicked = prevSelectedTask.alarm.time;
244 | const hour = moment(date).hour();
245 | const minute = moment(date).minute();
246 | let newModifiedDay = moment(selectedDatePicked).hour(hour).minute(minute);
247 | prevSelectedTask.alarm.time = newModifiedDay;
248 | setSelectedTask(prevSelectedTask);
249 | hideDateTimePicker();
250 | };
251 |
252 | const handleAlarmSet = () => {
253 | let prevSelectedTask = JSON.parse(JSON.stringify(selectedTask));
254 | prevSelectedTask.alarm.isOn = !prevSelectedTask.alarm.isOn;
255 | setSelectedTask(prevSelectedTask);
256 | };
257 |
258 | const updateAlarm = async () => {
259 | const calendarId = await createNewCalendar();
260 | const event = {
261 | title: selectedTask.title,
262 | notes: selectedTask.notes,
263 | startDate: moment(selectedTask?.alarm.time).add(0, 'm').toDate(),
264 | endDate: moment(selectedTask?.alarm.time).add(5, 'm').toDate(),
265 | timeZone: Localization.timezone
266 | };
267 |
268 | if (!selectedTask?.alarm.createEventAsyncRes) {
269 | try {
270 | const createEventAsyncRes = await Calendar.createEventAsync(
271 | calendarId.toString(),
272 | event
273 | );
274 | let updateTask = JSON.parse(JSON.stringify(selectedTask));
275 | updateTask.alarm.createEventAsyncRes = createEventAsyncRes;
276 | setSelectedTask(updateTask);
277 | } catch (error) {
278 | console.log(error);
279 | }
280 | } else {
281 | try {
282 | await Calendar.updateEventAsync(
283 | selectedTask?.alarm.createEventAsyncRes.toString(),
284 | event
285 | );
286 | } catch (error) {
287 | console.log(error);
288 | }
289 | }
290 | };
291 |
292 | const deleteAlarm = async () => {
293 | try {
294 | if (selectedTask?.alarm.createEventAsyncRes) {
295 | await Calendar.deleteEventAsync(
296 | selectedTask?.alarm.createEventAsyncRes
297 | );
298 | }
299 | let updateTask = JSON.parse(JSON.stringify(selectedTask));
300 | updateTask.alarm.createEventAsyncRes = '';
301 | setSelectedTask(updateTask);
302 | } catch (error) {
303 | console.log('deleteAlarm', error.message);
304 | }
305 | };
306 |
307 | const getEvent = async () => {
308 | if (selectedTask?.alarm.createEventAsyncRes) {
309 | try {
310 | await Calendar.getEventAsync(
311 | selectedTask?.alarm.createEventAsyncRes.toString()
312 | );
313 | } catch (error) {
314 | console.log(error);
315 | }
316 | }
317 | };
318 |
319 | const createNewCalendar = async () => {
320 | const defaultCalendarSource =
321 | Platform.OS === 'ios'
322 | ? await Calendar.getDefaultCalendarAsync(Calendar.EntityTypes.EVENT)
323 | : { isLocalAccount: true, name: 'Google Calendar' };
324 |
325 | const newCalendar = {
326 | title: 'Personal',
327 | entityType: Calendar.EntityTypes.EVENT,
328 | color: '#2196F3',
329 | sourceId: defaultCalendarSource?.sourceId || undefined,
330 | source: defaultCalendarSource,
331 | name: 'internal',
332 | accessLevel: Calendar.CalendarAccessLevel.OWNER,
333 | ownerAccount: 'personal'
334 | };
335 |
336 | let calendarId = null;
337 |
338 | try {
339 | calendarId = await Calendar.createCalendarAsync(newCalendar);
340 | } catch (e) {
341 | Alert.alert(e.message);
342 | }
343 |
344 | return calendarId;
345 | };
346 |
347 | return (
348 |
349 | {selectedTask !== null && (
350 |
351 |
359 |
360 | {
363 | let prevSelectedTask = JSON.parse(JSON.stringify(selectedTask));
364 | prevSelectedTask.title = text;
365 | setSelectedTask(prevSelectedTask);
366 | }}
367 | value={selectedTask.title}
368 | placeholder="What do you need to do?"
369 | />
370 |
377 | Suggestion
378 |
379 |
380 |
381 |
382 | Read book
383 |
384 |
385 |
386 |
387 | Design
388 |
389 |
390 |
391 | Learn
392 |
393 |
394 |
395 |
396 |
403 | Notes
404 |
405 | {
412 | let prevSelectedTask = JSON.parse(
413 | JSON.stringify(selectedTask)
414 | );
415 | prevSelectedTask.notes = text;
416 | setSelectedTask(prevSelectedTask);
417 | }}
418 | value={selectedTask.notes}
419 | placeholder="Enter notes about the task."
420 | />
421 |
422 |
423 |
424 |
431 | Times
432 |
433 | showDateTimePicker()}
435 | style={{
436 | height: 25,
437 | marginTop: 3
438 | }}
439 | >
440 |
441 | {moment(selectedTask?.alarm?.time || moment()).format(
442 | 'h:mm A'
443 | )}
444 |
445 |
446 |
447 |
448 |
455 |
456 |
463 | Alarm
464 |
465 |
471 |
472 | {moment(selectedTask?.alarm?.time || moment()).format(
473 | 'h:mm A'
474 | )}
475 |
476 |
477 |
478 |
482 |
483 |
490 | {
492 | handleModalVisible();
493 | console.log('isOn', selectedTask?.alarm.isOn);
494 | if (selectedTask?.alarm.isOn) {
495 | await updateAlarm();
496 | } else {
497 | await deleteAlarm();
498 | }
499 | await updateSelectedTask({
500 | date: currentDate,
501 | todo: selectedTask
502 | });
503 | updateCurrentTask(currentDate);
504 | }}
505 | style={styles.updateButton}
506 | >
507 |
514 | UPDATE
515 |
516 |
517 | {
519 | handleModalVisible();
520 | deleteAlarm();
521 | await deleteSelectedTask({
522 | date: currentDate,
523 | todo: selectedTask
524 | });
525 | updateCurrentTask(currentDate);
526 | }}
527 | style={styles.deleteButton}
528 | >
529 |
536 | DELETE
537 |
538 |
539 |
540 |
541 |
542 | )}
543 |
548 | undefined is not an object (evaluating 'datesList[_this.state.numVisibleDays - 1].date')
584 | // temp: https://github.com/BugiDev/react-native-calendar-strip/issues/303#issuecomment-864510769
585 | markedDates={markedDate}
586 | selectedDate={currentDate}
587 | onDateSelected={(date) => {
588 | const selectedDate = `${moment(date).format('YYYY')}-${moment(
589 | date
590 | ).format('MM')}-${moment(date).format('DD')}`;
591 | updateCurrentTask(selectedDate);
592 | setCurrentDate(selectedDate);
593 | }}
594 | />
595 |
597 | navigation.navigate('CreateTask', {
598 | updateCurrentTask: updateCurrentTask,
599 | currentDate,
600 | createNewCalendar: createNewCalendar
601 | })
602 | }
603 | style={styles.viewTask}
604 | >
605 |
612 |
613 |
619 |
624 | {todoList.map((item) => (
625 | {
627 | setSelectedTask(item);
628 | setModalVisible(true);
629 | getEvent();
630 | }}
631 | key={item.key}
632 | style={styles.taskListContent}
633 | >
634 |
639 |
645 |
654 |
661 | {item.title}
662 |
663 |
664 |
665 |
671 | {`${moment(item.alarm.time).format('YYYY')}/${moment(
678 | item.alarm.time
679 | ).format('MM')}/${moment(item.alarm.time).format(
680 | 'DD'
681 | )}`}
682 |
688 | {item.notes}
689 |
690 |
691 |
692 |
693 |
701 |
702 | ))}
703 |
704 |
705 |
706 |
707 | );
708 | }
709 |
--------------------------------------------------------------------------------
/src/screens/index.js:
--------------------------------------------------------------------------------
1 | export { default as Home } from './Home';
2 | export { default as CreateTask } from './CreateTask';
3 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import useStore from './store';
2 |
3 | export { useStore };
4 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import create from 'zustand';
2 | import produce from 'immer';
3 | import AsyncStorage from '@react-native-async-storage/async-storage';
4 | // Turn the set method into an immer proxy
5 | const immer = (config) => (set, get, api) =>
6 | config(
7 | (fn) => {
8 | const state = typeof fn === 'function' ? fn : () => fn;
9 | set(produce(state));
10 | },
11 | get,
12 | api
13 | );
14 |
15 | const zustandCreateStore = (children) => create(immer(children));
16 |
17 | const storeStates = {
18 | todo: []
19 | };
20 |
21 | const storeMethods = (set, get) => ({
22 | init: async () => {
23 | try {
24 | // await AsyncStorage.clear();
25 | const todo = await AsyncStorage.getItem('TODO');
26 | if (todo !== null) {
27 | set({ todo: JSON.parse(todo) });
28 | }
29 | } catch (error) {
30 | // Error saving data
31 | }
32 | },
33 | updateTodo: async (item) =>
34 | new Promise(async (resolve) => {
35 | const datePresent = get().todo.find((data) => {
36 | if (data.date === item.date) {
37 | return true;
38 | }
39 | return false;
40 | });
41 |
42 | if (datePresent) {
43 | const updatedTodo = get().todo.map((data) => {
44 | if (datePresent.date === data.date) {
45 | return { ...data, todoList: [...data.todoList, ...item.todoList] };
46 | }
47 | return data;
48 | });
49 |
50 | try {
51 | set({ todo: updatedTodo });
52 | await AsyncStorage.setItem('TODO', JSON.stringify(updatedTodo));
53 | } catch (error) {
54 | // Error saving data
55 | }
56 | } else {
57 | const newTodo = [...get().todo, item];
58 |
59 | try {
60 | set({ todo: newTodo });
61 | resolve();
62 | await AsyncStorage.setItem('TODO', JSON.stringify(newTodo));
63 | } catch (error) {
64 | // Error saving data
65 | }
66 | }
67 | }),
68 | deleteTodo: () => {},
69 | updateSelectedTask: async (item) =>
70 | new Promise(async (resolve) => {
71 | const previousTodo = get().todo;
72 | const newTodo = previousTodo.map((data) => {
73 | if (item.date === data.date) {
74 | const previousTodoList = [...data.todoList];
75 | const newTodoList = previousTodoList.map((list) => {
76 | if (list.key === item.todo.key) {
77 | return item.todo;
78 | }
79 | return list;
80 | });
81 | return { ...data, todoList: newTodoList };
82 | }
83 | return data;
84 | });
85 | try {
86 | set({ todo: newTodo });
87 | resolve();
88 | await AsyncStorage.setItem('TODO', JSON.stringify(newTodo));
89 | } catch (error) {
90 | // Error saving data
91 | }
92 | }),
93 | deleteSelectedTask: async (item) =>
94 | new Promise(async (resolve) => {
95 | const previousTodo = get().todo;
96 | const newTodo = previousTodo.map((data) => {
97 | if (item.date === data.date) {
98 | const previousTodoList = [...data.todoList];
99 | const newTodoList = previousTodoList.filter((list) => {
100 | if (list.key === item.todo.key) {
101 | return false;
102 | }
103 | return true;
104 | });
105 |
106 | return { ...data, todoList: newTodoList };
107 | }
108 | return data;
109 | });
110 | const checkForEmpty = newTodo.filter((data) => {
111 | if (data.todoList.length === 0) {
112 | return false;
113 | }
114 | return true;
115 | });
116 | try {
117 | set({ todo: checkForEmpty });
118 | resolve();
119 | await AsyncStorage.setItem('TODO', JSON.stringify(checkForEmpty));
120 | } catch (error) {
121 | // Error saving data
122 | }
123 | })
124 | });
125 |
126 | const useStore = zustandCreateStore((set, get) => ({
127 | ...storeStates,
128 | ...storeMethods(set, get)
129 | }));
130 |
131 | export default useStore;
132 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "sourceMap": true,
5 | "outDir": "./.build",
6 | "rootDir": "./src",
7 | "noImplicitAny": true,
8 | "strictFunctionTypes": true,
9 | "noImplicitThis": true,
10 | "alwaysStrict": true,
11 | "allowJs": true,
12 | "allowSyntheticDefaultImports": true,
13 | "esModuleInterop": true,
14 | "isolatedModules": true,
15 | "jsx": "react-native",
16 | "lib": [
17 | "es6"
18 | ],
19 | "moduleResolution": "node",
20 | "noEmit": true,
21 | "strict": true,
22 | "target": "esnext",
23 | "paths": {
24 | "@calendar/navigation": [
25 | "./src/navigation"
26 | ],
27 | "@calendar/screens": [
28 | "./src/screens"
29 | ],
30 | "@calendar/store": [
31 | "./src/store"
32 | ],
33 | "@calendar/components": [
34 | "./src/components"
35 | ],
36 | "@calendar/hooks": [
37 | "./src/hooks"
38 | ]
39 | }
40 | },
41 | "types": [
42 | "react",
43 | "react-native"
44 | ],
45 | "exclude": ["App.tsx", "babel.config.js", "jest.config.js", ".eslintrc.js", "./bin/**/**"],
46 | "extends": "expo/tsconfig.base"
47 | }
48 |
--------------------------------------------------------------------------------