├── .eslintrc
├── .expo-shared
└── assets.json
├── .gitignore
├── App.js
├── AppNavigator.js
├── Notifications
├── cancelNotification.js
├── scheduleReminderNotification.js
└── useNotifications.js
├── app.json
├── assets
├── adaptive-icon.png
├── favicon.png
├── icon.png
└── splash.png
├── babel.config.js
├── components
├── ActionButton.js
├── FolderCard.js
├── Modal.js
├── NoteCard.js
├── NoteCardText.js
├── NoteOptionsActionSheet.js
├── NoteReminderModal.js
├── Notes.js
└── NotesPageHeader.js
├── context
└── context.jsx
├── eas.json
├── package-lock.json
├── screens
├── AddNote.js
├── FolderNotes.js
├── Folders.js
├── Home.js
├── LabelsManager.js
├── NoteLabelsManager.js
├── NotesSelector.js
├── Trash.js
├── UpdateNote.js
└── index.js
├── style
└── theme.js
└── utils
├── dateformat.js
└── storage.js
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "react-native"
4 | ],
5 | "extends": [
6 | "eslint:recommended",
7 | "plugin:react/recommended",
8 | "plugin:react-native/all",
9 | "plugin:react-hooks/recommended"
10 | ],
11 | "parser": "@babel/eslint-parser",
12 | "env": {
13 | "es6": true,
14 | "react-native/react-native": true
15 | },
16 | "parserOptions": {
17 | "ecmaFeatures": {
18 | "jsx": true
19 | },
20 | "ecmaVersion": "latest",
21 | "sourceType": "module"
22 | },
23 | "rules": {
24 | "react-native/no-unused-styles": 0,
25 | "react-native/sort-styles": 0,
26 | "react-native/split-platform-components": 0,
27 | "react-native/no-inline-styles": 0,
28 | "react-native/no-color-literals": 0,
29 | "react-native/no-raw-text": 2,
30 | "react/jsx-uses-react": "warn",
31 | "react/jsx-uses-vars": "warn",
32 | "no-unused-vars": "warn",
33 | "react/prop-types": 0,
34 | "react-hooks/rules-of-hooks": "warn",
35 | "react-hooks/exhaustive-deps": "warn"
36 | }
37 | }
--------------------------------------------------------------------------------
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import { GlobalContextProvider } from './context/context';
2 | import React from 'react'
3 | import useNotifications from './Notifications/useNotifications';
4 | import { Snackbar } from '@ouroboros/react-native-snackbar';
5 | import AppNavigator from './AppNavigator';
6 | import { View } from 'react-native';
7 | import { StatusBar } from 'expo-status-bar';
8 |
9 | export default function App() {
10 | const navRef = useNotifications();
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 | );
23 | }
--------------------------------------------------------------------------------
/AppNavigator.js:
--------------------------------------------------------------------------------
1 | import { NavigationContainer } from '@react-navigation/native';
2 | import * as screens from './screens';
3 | import React from 'react'
4 | import { createDrawerNavigator, DrawerItemList } from '@react-navigation/drawer';
5 | import { SafeAreaView } from 'react-native-safe-area-context';
6 | import { ScrollView, Text } from 'react-native';
7 | import { createSharedElementStackNavigator } from 'react-navigation-shared-element';
8 | import { CardStyleInterpolators } from '@react-navigation/stack';
9 |
10 | const Stack = createSharedElementStackNavigator();
11 | const Drawer = createDrawerNavigator();
12 |
13 | const DrawerContent = (props) => {
14 | return (
15 | <>
16 |
17 |
18 | Notes App
19 |
20 |
21 |
22 | >
23 | );
24 | }
25 |
26 | const AppNavigator = ({ navRef }) => {
27 | return (
28 |
29 |
32 | (
33 |
34 |
35 |
36 |
37 |
38 |
39 | )} />
40 |
41 |
42 |
43 |
44 |
46 |
47 |
48 | )
49 | }
50 |
51 | export default AppNavigator
--------------------------------------------------------------------------------
/Notifications/cancelNotification.js:
--------------------------------------------------------------------------------
1 | import * as Notifications from 'expo-notifications';
2 | import { Alert } from 'react-native';
3 |
4 | export async function cancelNotification(notifId) {
5 | try {
6 | await Notifications.cancelScheduledNotificationAsync(notifId);
7 | }
8 | catch (err) {
9 | Alert.alert("Error!")
10 | }
11 | }
--------------------------------------------------------------------------------
/Notifications/scheduleReminderNotification.js:
--------------------------------------------------------------------------------
1 | import * as Notifications from 'expo-notifications';
2 | import { Alert } from 'react-native';
3 |
4 | export default async function scheduleReminderNotification({ note, dateTime }) {
5 | try {
6 | const id = await Notifications.scheduleNotificationAsync({
7 | content: {
8 | title: "Reminder",
9 | body: note.text.slice(0, 80) + "...",
10 | data: { navigateTo: "UpdateNote", params: { id: note.id } }
11 | },
12 | trigger: dateTime
13 | });
14 | return id;
15 | }
16 | catch (err) {
17 | console.log(err);
18 | Alert.alert("error");
19 | }
20 | }
--------------------------------------------------------------------------------
/Notifications/useNotifications.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import * as Notifications from 'expo-notifications';
3 | import { Platform } from "react-native";
4 |
5 | Notifications.setNotificationHandler({
6 | handleNotification: async () => ({
7 | shouldShowAlert: true,
8 | shouldPlaySound: true,
9 | shouldSetBadge: false,
10 | }),
11 | });
12 |
13 | const useNotifications = () => {
14 | // const [expoPushToken, setExpoPushToken] = useState('');
15 | const [notification, setNotification] = useState(false);
16 | const notificationListener = useRef();
17 | const responseListener = useRef();
18 | const navRef = useRef();
19 |
20 | useEffect(() => {
21 | // registerForPushNotificationsAsync().then(token => setExpoPushToken(token));
22 |
23 | // This listener is fired whenever a notification is received while the app is foregrounded
24 | notificationListener.current = Notifications.addNotificationReceivedListener(notification => {
25 | setNotification(notification);
26 | });
27 |
28 | // This listener is fired whenever a user taps on or interacts with a notification (works when app is foregrounded, backgrounded, or killed)
29 | responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
30 | const { data } = response.notification.request.content;
31 | let { navigateTo, params } = data;
32 | navigateTo = navigateTo || "Home";
33 | navRef.current.navigate(navigateTo, params);
34 | });
35 |
36 | return () => {
37 | Notifications.removeNotificationSubscription(notificationListener.current);
38 | Notifications.removeNotificationSubscription(responseListener.current);
39 | };
40 | }, []);
41 |
42 | return navRef;
43 |
44 | }
45 |
46 |
47 | async function registerForPushNotificationsAsync() {
48 | let token;
49 | const isDevice = true;
50 | if (isDevice) {
51 | const { status: existingStatus } = await Notifications.getPermissionsAsync();
52 | let finalStatus = existingStatus;
53 | if (existingStatus !== 'granted') {
54 | const { status } = await Notifications.requestPermissionsAsync();
55 | finalStatus = status;
56 | }
57 | if (finalStatus !== 'granted') {
58 | alert('Failed to get push token for push notification!');
59 | return;
60 | }
61 | token = (await Notifications.getExpoPushTokenAsync()).data;
62 | console.log(token);
63 | } else {
64 | alert('Must use physical device for Push Notifications');
65 | }
66 |
67 | if (Platform.OS === 'android') {
68 | Notifications.setNotificationChannelAsync('default', {
69 | name: 'default',
70 | importance: Notifications.AndroidImportance.MAX,
71 | vibrationPattern: [0, 250, 250, 250],
72 | lightColor: '#FF231F7C',
73 | });
74 | }
75 |
76 | return token;
77 | }
78 |
79 | export default useNotifications;
80 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "Notes app",
4 | "slug": "react-native-notes-app",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "updates": {
15 | "fallbackToCacheTimeout": 0
16 | },
17 | "assetBundlePatterns": [
18 | "**/*"
19 | ],
20 | "ios": {
21 | "supportsTablet": true
22 | },
23 | "android": {
24 | "package": "com.notes",
25 | "versionCode": 1,
26 | "adaptiveIcon": {
27 | "foregroundImage": "./assets/adaptive-icon.png",
28 | "backgroundColor": "#1d9bff"
29 | }
30 | },
31 | "web": {
32 | "favicon": "./assets/favicon.png"
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/react-native-notes-app/685a0b90541dda5c84fbd8d1aac04942dc08ce2e/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/react-native-notes-app/685a0b90541dda5c84fbd8d1aac04942dc08ce2e/assets/favicon.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/react-native-notes-app/685a0b90541dda5c84fbd8d1aac04942dc08ce2e/assets/icon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koatane-dev/react-native-notes-app/685a0b90541dda5c84fbd8d1aac04942dc08ce2e/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | plugins: [
6 | "react-native-reanimated/plugin",
7 | ],
8 | };
9 | };
10 |
--------------------------------------------------------------------------------
/components/ActionButton.js:
--------------------------------------------------------------------------------
1 | import { Pressable, StyleSheet } from 'react-native'
2 | import React from 'react'
3 | import theme from '../style/theme';
4 | import Icon from 'react-native-vector-icons/Ionicons';
5 | import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
6 | const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
7 |
8 | const ActionButton = ({ style = {}, onPress, iconName = "add-outline", isVisible = true }) => {
9 | const animatedStyles = useAnimatedStyle(() => ({
10 | transform: [{ scale: withTiming(isVisible ? 1 : 0, { duration: 200 }) }]
11 | }), [isVisible]);
12 |
13 | return (
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default ActionButton
21 |
22 | const styles = StyleSheet.create({
23 | buttonStyle: {
24 | position: "absolute",
25 | bottom: 20,
26 | right: 20,
27 | width: 50,
28 | height: 50,
29 | backgroundColor: theme.PRIMARY_COLOR,
30 | color: 'white',
31 | borderRadius: 50,
32 | justifyContent: "center",
33 | alignItems: "center",
34 | }
35 | })
36 |
37 |
--------------------------------------------------------------------------------
/components/FolderCard.js:
--------------------------------------------------------------------------------
1 | import { View, Text, Pressable } from 'react-native'
2 | import React from 'react'
3 | import { convertToXTimeAgo } from '../utils/dateformat'
4 | import Icon from 'react-native-vector-icons/Ionicons';
5 | import theme from '../style/theme';
6 |
7 | const FolderCard = ({ folder, drag, isActive, handlePress }) => {
8 | return (
9 |
20 | handlePress(folder.id)}
22 | onLongPress={() => drag()}
23 | android_ripple={{ color: "#bbb", radius: 200 }}
24 | delayLongPress={100}
25 | >
26 |
27 |
28 | {folder.name}
29 |
30 | {folder.notes.length} note{folder.notes.length != 1 && 's'}
31 | {convertToXTimeAgo(folder.createdAt)}
32 |
33 |
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | export default FolderCard
--------------------------------------------------------------------------------
/components/Modal.js:
--------------------------------------------------------------------------------
1 | import { View, Modal as RNModal, Pressable, StyleSheet } from 'react-native'
2 | import React from 'react'
3 |
4 | const Modal = ({ children, visible, onRequestClose, style = {} }) => {
5 | return (
6 |
7 | e.target == e.currentTarget && onRequestClose()}>
8 |
9 | {children}
10 |
11 |
12 |
13 | )
14 | }
15 |
16 | export default Modal
17 |
18 |
19 | const styles = StyleSheet.create({
20 | centeredView: {
21 | flex: 1,
22 | justifyContent: "center",
23 | alignItems: "center",
24 | backgroundColor: "rgba(0, 0, 0, 0.2)"
25 | },
26 | modalView: {
27 | backgroundColor: "white",
28 | borderRadius: 5,
29 | padding: 10,
30 | width: "80%",
31 | shadowColor: "#000",
32 | shadowOffset: {
33 | width: 0,
34 | height: 2
35 | },
36 | shadowOpacity: 0.25,
37 | shadowRadius: 4,
38 | elevation: 5
39 | }
40 | })
--------------------------------------------------------------------------------
/components/NoteCard.js:
--------------------------------------------------------------------------------
1 | import { Pressable, StyleSheet, View, Dimensions } from 'react-native'
2 | import React from 'react'
3 | import { PanGestureHandler, GestureHandlerRootView } from 'react-native-gesture-handler';
4 | import Animated, { runOnJS, useAnimatedGestureHandler, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
5 | import theme from '../style/theme';
6 | import NoteCardText from './NoteCardText';
7 | import { SharedElement } from 'react-navigation-shared-element';
8 |
9 | // "isActive" is used in the situation of dragging the note and to check if currently it is being dragged
10 | const NoteCard = ({ note, isActive = false, onPress = () => { }, onLongPress = () => { }, moveNoteToTrash, isAddedInSelection = false }) => {
11 |
12 | const { width: SCREEN_WIDTH } = Dimensions.get("window");
13 | const TRANSLATE_X_THRESHOLD = -SCREEN_WIDTH * 0.4;
14 | const translateX = useSharedValue(0);
15 |
16 | const panGesture = useAnimatedGestureHandler({
17 | onActive: e => {
18 | translateX.value = e.translationX;
19 | },
20 | onEnd: () => {
21 | const shouldBeRemoved = translateX.value < TRANSLATE_X_THRESHOLD;
22 | if (shouldBeRemoved) {
23 | translateX.value = withTiming(-SCREEN_WIDTH, { duration: 100 }, (isFinished) => {
24 | if (isFinished) runOnJS(moveNoteToTrash)(note.id);
25 | });
26 | } else {
27 | translateX.value = withTiming(0);
28 | }
29 | }
30 | });
31 |
32 | const rStyle = useAnimatedStyle(() => ({
33 | transform: [{
34 | translateX: translateX.value,
35 | }, {
36 | scale: withTiming(isActive ? 1.04 : 1, { duration: 100 })
37 | }]
38 | }));
39 |
40 |
41 | return (
42 |
43 |
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
64 |
65 | export default NoteCard
66 |
67 |
68 | const styles = ({ isAddedInSelection }) => StyleSheet.create({
69 | noteCard: [{
70 | borderRadius: 5,
71 | backgroundColor: "#fff",
72 | borderWidth: 2,
73 | borderColor: "#fff"
74 | },
75 | isAddedInSelection ? {
76 | borderColor: theme.PRIMARY_COLOR,
77 | } : {
78 | borderBottomColor: "#e4e4e4",
79 | }]
80 | })
--------------------------------------------------------------------------------
/components/NoteCardText.js:
--------------------------------------------------------------------------------
1 | import { View, Text, ScrollView } from 'react-native'
2 | import React from 'react'
3 | import Icon from 'react-native-vector-icons/Ionicons';
4 | import { convertToXTimeAgo } from '../utils/dateformat';
5 | import moment from 'moment';
6 |
7 | const NoteCardText = ({ note }) => {
8 | const isReminderTimePassed = new Date(note.reminder?.dateTime).getTime() < new Date().getTime();
9 |
10 | return (
11 |
12 |
13 | {note.color && (
14 |
15 | )}
16 | {convertToXTimeAgo(note.updatedAt)}
17 | {note.isBookmarked && (
18 |
19 | )}
20 | {note.isPinned && (
21 |
22 | )}
23 |
24 |
25 | {note.labels.length > 0 && (
26 |
27 | {note.labels?.map(label => (
28 | {label}
29 | ))}
30 |
31 | )}
32 |
33 | {note.reminder?.dateTime && (
34 |
35 |
36 |
37 | {moment(note.reminder?.dateTime).format("lll")}
38 |
39 |
40 | )}
41 |
42 | {note.text}
43 |
44 | )
45 | }
46 |
47 | export default NoteCardText
--------------------------------------------------------------------------------
/components/NoteOptionsActionSheet.js:
--------------------------------------------------------------------------------
1 | import { View, Text, StyleSheet, ScrollView, Pressable, ToastAndroid, Alert, Share } from 'react-native'
2 | import React, { useState } from 'react'
3 | import ActionSheet, { SheetManager } from "react-native-actions-sheet";
4 | import Icon from 'react-native-vector-icons/Ionicons';
5 | import { getData, storeData } from '../utils/storage';
6 | import * as Clipboard from 'expo-clipboard';
7 | import { useNavigation } from '@react-navigation/native';
8 | import { useGlobalContext } from '../context/context';
9 | import NoteReminderModal from './NoteReminderModal';
10 | import { addMessage } from '@ouroboros/react-native-snackbar';
11 |
12 | const NoteOptionsActionSheet = ({ noteId, formData }) => {
13 | const navigation = useNavigation();
14 | const { notes, setNotes } = useGlobalContext();
15 | const [noteReminderModal, setNoteReminderModal] = useState(false);
16 |
17 | const note = notes.find(note => note.id === noteId);
18 | const noteColors = ["lightseagreen", "skyblue", "lightcoral", "lightpink", "lightgreen", "lightblue", "orange", "palegreen"];
19 | const newOrEditMode = note.reminder?.dateTime ? "edit" : "new";
20 |
21 | const changeNoteColor = async color => {
22 | try {
23 | const newNotesArr = notes.map(note => {
24 | if (note.id !== noteId) return note;
25 | return { ...note, color };
26 | })
27 | await storeData("notes", newNotesArr);
28 | setNotes(newNotesArr);
29 | }
30 | catch (err) {
31 | Alert.alert("Error", "Some error is there!!");
32 | }
33 | }
34 |
35 | const copyToClipboard = async () => {
36 | await Clipboard.setStringAsync(formData.text);
37 | ToastAndroid.show("Note's text copied", ToastAndroid.SHORT);
38 | }
39 |
40 | const shareNote = async () => {
41 | await Share.share({ message: formData.text });
42 | }
43 |
44 | const restoreNote = async () => {
45 | try {
46 | const trashNotes = await getData("trashNotes");
47 | const note = trashNotes.find(note => note.id === noteId);
48 | const newNotesArr = [...notes];
49 | await storeData("notes", newNotesArr);
50 |
51 | const newTrashNotesArr = trashNotes.filter(trashNote => trashNote.id !== note.id);
52 | await storeData("trashNotes", newTrashNotesArr);
53 | setNotes(newNotesArr);
54 | }
55 | catch (err) {
56 | Alert.alert("Error", "Some error is there!!");
57 | }
58 | }
59 |
60 |
61 | const moveNoteToTrash = async () => {
62 | try {
63 | const trashNotes = (await getData("trashNotes")) || [];
64 | const newTrashNotesArr = [...trashNotes, note];
65 | await storeData("trashNotes", newTrashNotesArr);
66 |
67 | const newNotesArr = notes.filter(note => note.id !== noteId);
68 | await storeData("notes", newNotesArr);
69 | navigation.navigate("Home");
70 | setNotes(newNotesArr);
71 |
72 | addMessage({
73 | text: "Note moved to trash",
74 | duration: 5000,
75 | action: {
76 | text: "Undo",
77 | onPress: restoreNote
78 | }
79 | });
80 |
81 | }
82 | catch (err) {
83 | Alert.alert("Error", "Some error is there!!");
84 | }
85 | }
86 |
87 |
88 | const cloneNote = async () => {
89 | try {
90 | const newNote = {
91 | ...note,
92 | id: Math.floor(Math.random() * 10000),
93 | text: formData.text,
94 | createdAt: new Date(),
95 | updatedAt: new Date(),
96 | }
97 | const newNotesArr = [newNote, ...notes];
98 | await storeData("notes", newNotesArr);
99 | setNotes(newNotesArr);
100 | navigation.navigate("Home");
101 | ToastAndroid.show("Note cloned successfully", ToastAndroid.SHORT);
102 | }
103 | catch (err) {
104 | Alert.alert("Error", "Some error is there!!");
105 | }
106 | }
107 |
108 | const togglePin = async () => {
109 | try {
110 | const newNotesArr = notes.map(note => {
111 | if (note.id !== noteId) return note;
112 | return { ...note, isPinned: !note.isPinned };
113 | })
114 | await storeData("notes", newNotesArr);
115 | setNotes(newNotesArr);
116 | }
117 | catch (err) {
118 | Alert.alert("Error", "Some error is there!!");
119 | }
120 | }
121 |
122 | const showAddReminderModal = () => {
123 | SheetManager.hide("noteOptionsActionSheet");
124 | setNoteReminderModal(true);
125 | }
126 |
127 |
128 |
129 | return (
130 | <>
131 |
132 |
133 |
134 | {/* Colors */}
135 |
136 | changeNoteColor(null)} style={{ marginHorizontal: 5, borderRadius: 50, width: 35, height: 35, backgroundColor: "white", justifyContent: "center", alignItems: "center", borderWidth: 1, borderColor: "black" }}>
137 |
138 | {!note.color && (
139 |
140 | )}
141 |
142 |
143 | {noteColors.map(color => (
144 | changeNoteColor(color)} key={color} style={{ marginHorizontal: 5, borderRadius: 50, width: 35, height: 35, backgroundColor: color, justifyContent: "center", alignItems: "center" }}>
145 | {note.color === color && (
146 |
147 | )}
148 |
149 | ))}
150 |
151 |
152 |
153 |
154 | {/* Labels */}
155 |
156 | {note.labels?.map(label => (
157 | {label}
158 | ))}
159 | { SheetManager.hide("noteOptionsActionSheet"); navigation.navigate("NoteLabels", { noteId }); }} style={{ marginLeft: 14, backgroundColor: "#eee", paddingVertical: 5, paddingHorizontal: 8, borderRadius: 3 }} android_ripple={{ color: "#bbb", radius: 200 }} >
160 | + Manage labels
161 |
162 |
163 |
164 |
165 |
166 | {[
167 | { onPress: copyToClipboard, iconName: "clipboard-outline", title: "Copy to Clipboard" },
168 | { onPress: shareNote, iconName: "share-social-outline", title: "Share" },
169 | { onPress: moveNoteToTrash, iconName: "trash-outline", title: "Delete" },
170 | { onPress: cloneNote, iconName: "copy-outline", title: "Make a copy" },
171 | { onPress: togglePin, iconName: note.isPinned ? "pin" : "pin-outline", title: note.isPinned ? "Unpin" : "Pin" },
172 | { onPress: showAddReminderModal, iconName: "alarm-outline", title: newOrEditMode === "edit" ? "Edit reminder" : "Add a reminder" },
173 | ]
174 | .map(({ onPress, iconName, title }) => (
175 |
176 |
177 | {title}
178 |
179 | ))}
180 |
181 |
182 |
183 |
184 | >
185 | )
186 | }
187 |
188 | export default NoteOptionsActionSheet
189 |
190 | const styles = StyleSheet.create({
191 | sheetItem: {
192 | flexDirection: "row",
193 | alignItems: "center",
194 | padding: 15,
195 | }
196 | });
197 |
--------------------------------------------------------------------------------
/components/NoteReminderModal.js:
--------------------------------------------------------------------------------
1 | import { Alert, Pressable, Text, View } from 'react-native'
2 | import React, { useEffect, useState } from 'react'
3 | import RNDateTimePicker from '@react-native-community/datetimepicker';
4 | import scheduleReminderNotification from '../Notifications/scheduleReminderNotification';
5 | import moment from 'moment/moment';
6 | import { useGlobalContext } from '../context/context';
7 | import { storeData } from '../utils/storage';
8 | import { cancelNotification } from '../Notifications/cancelNotification';
9 | import Modal from './Modal';
10 |
11 | const NoteReminderModal = ({ noteReminderModal, setNoteReminderModal, noteId }) => {
12 | const { notes, setNotes } = useGlobalContext();
13 | const [dateTimePickerMode, setDateTimePickerMode] = useState(null);
14 | const [inputDateTime, setInputDateTime] = useState(new Date());
15 | const note = notes.find(note => note.id === noteId);
16 | const newOrEditMode = note.reminder?.dateTime ? "edit" : "new";
17 |
18 | useEffect(() => {
19 | if (!noteReminderModal) return;
20 | if (newOrEditMode === "edit") {
21 | setInputDateTime(new Date(note.reminder?.dateTime));
22 | }
23 | else {
24 | setInputDateTime(new Date());
25 | }
26 | }, [noteReminderModal, note, newOrEditMode]);
27 |
28 |
29 | const onDateTimeInputChange = (e, selectedDate) => {
30 | const currentDate = selectedDate;
31 | setDateTimePickerMode(null);
32 | setInputDateTime(currentDate);
33 | };
34 |
35 | const setOrChangeReminderForNote = async () => {
36 | try {
37 | if (newOrEditMode === "edit") {
38 | await cancelNotification(note.reminder.notifId);
39 | }
40 | const notifId = await scheduleReminderNotification({ note, dateTime: inputDateTime });
41 | const newNotesArr = notes.map(note => {
42 | if (note.id !== noteId) return note;
43 | return { ...note, reminder: { dateTime: inputDateTime, notifId } };
44 | });
45 | await storeData("notes", newNotesArr);
46 | setNotes(newNotesArr);
47 | setNoteReminderModal(false);
48 | }
49 | catch (err) {
50 | Alert.alert("Error", "Some error is there!!");
51 | }
52 | }
53 |
54 | const deleteReminderForNote = async () => {
55 | try {
56 | await cancelNotification(note.reminder.notifId);
57 | const newNotesArr = notes.map(note => {
58 | if (note.id !== noteId) return note;
59 | return { ...note, reminder: undefined };
60 | });
61 | await storeData("notes", newNotesArr);
62 | setNotes(newNotesArr);
63 | setNoteReminderModal(false);
64 | }
65 | catch (err) {
66 | Alert.alert("Error", "Some error is there!!");
67 | }
68 | }
69 |
70 |
71 | return (
72 | <>
73 | setNoteReminderModal(false)}>
74 |
75 | Set Reminder
76 |
77 | setDateTimePickerMode("date")} style={{ width: "90%", paddingVertical: 15, paddingHorizontal: 10, borderBottomWidth: 1, borderBottomColor: "#ddd" }} android_ripple={{ color: "#bbb", radius: 150 }}>
78 | {moment(inputDateTime).format("LL")}
79 |
80 |
81 | setDateTimePickerMode("time")} style={{ width: "90%", paddingVertical: 15, paddingHorizontal: 10, borderBottomWidth: 1, borderBottomColor: "#ddd" }} android_ripple={{ color: "#bbb", radius: 150 }}>
82 | {moment(inputDateTime).format("LT")}
83 |
84 |
85 |
86 | {newOrEditMode === "edit" && (
87 |
88 | Delete
89 |
90 | )}
91 |
92 | setNoteReminderModal(false)} style={{ padding: 10 }} android_ripple={{ color: "#bbb", radius: 60 }}>
93 | Cancel
94 |
95 |
96 |
97 | Set
98 |
99 |
100 |
101 |
102 |
103 | {dateTimePickerMode && (
104 |
109 | )}
110 | >
111 | )
112 | }
113 |
114 | export default NoteReminderModal
--------------------------------------------------------------------------------
/components/Notes.js:
--------------------------------------------------------------------------------
1 | import { Alert, BackHandler, Text, ToastAndroid, View } from 'react-native'
2 | import { useIsFocused, useNavigation } from '@react-navigation/native';
3 | import { useGlobalContext } from '../context/context';
4 | import theme from '../style/theme';
5 | import { useEffect } from 'react';
6 | import React from 'react';
7 | import NoteCard from './NoteCard';
8 | import { getData, storeData } from '../utils/storage';
9 | import DraggableFlatList from 'react-native-draggable-flatlist'
10 |
11 |
12 | const Notes = ({ selectedNotes, setSelectedNotes, filteredNotes, isSearchMode, setIsSearchMode }) => {
13 | const { notes, setNotes } = useGlobalContext();
14 | const navigation = useNavigation();
15 | const isFocussed = useIsFocused();
16 |
17 | useEffect(() => {
18 | const backAction = () => {
19 | if (!isFocussed) return;
20 | if (selectedNotes.length > 0) {
21 | setSelectedNotes([]);
22 | return true;
23 | }
24 | if (isSearchMode) {
25 | setIsSearchMode(false);
26 | return true;
27 | }
28 | return false;
29 | };
30 | BackHandler.addEventListener("hardwareBackPress", backAction);
31 | return () => BackHandler.removeEventListener("hardwareBackPress", backAction);
32 | }, [isFocussed, selectedNotes, setSelectedNotes, isSearchMode, setIsSearchMode]);
33 |
34 |
35 | const handlePress = (id) => {
36 | if (selectedNotes.length == 0) {
37 | navigation.navigate("UpdateNote", { id });
38 | }
39 | else {
40 | if (selectedNotes.includes(id)) {
41 | setSelectedNotes(selectedNotes.filter(selectedNoteId => selectedNoteId !== id));
42 | }
43 | else {
44 | setSelectedNotes([...selectedNotes, id]);
45 | }
46 | }
47 | }
48 |
49 | const handleLongPress = (id) => {
50 | if (selectedNotes.includes(id)) return;
51 | setSelectedNotes([...selectedNotes, id]);
52 | }
53 |
54 |
55 | const moveNoteToTrash = async (noteId) => {
56 | try {
57 | const note = notes.find(note => note.id === noteId);
58 | const trashNotes = (await getData("trashNotes")) || [];
59 | const newTrashNotesArr = [...trashNotes, note];
60 | await storeData("trashNotes", newTrashNotesArr);
61 |
62 | const newNotesArr = notes.filter(note => note.id !== noteId);
63 | await storeData("notes", newNotesArr);
64 | setNotes(newNotesArr);
65 | setSelectedNotes([]);
66 | ToastAndroid.show("Note moved to trash", ToastAndroid.SHORT);
67 | }
68 | catch (err) {
69 | console.log(err);
70 | Alert.alert("Error", "Some error is there!!");
71 | }
72 | }
73 |
74 | const changeOrderOfNotes = async ({ data }) => {
75 | try {
76 | const newNotesArr = data;
77 | if (newNotesArr.length === notes.length && JSON.stringify(newNotesArr) === JSON.stringify(notes)) return;
78 | setNotes(newNotesArr);
79 | await storeData("notes", newNotesArr);
80 | }
81 | catch (err) {
82 | console.log(err);
83 | Alert.alert("Error", "Some error is there!!");
84 | }
85 | }
86 |
87 |
88 | const getDraggableFlatList = notes => {
89 | const notesCopy = [...notes.filter(note => note.isPinned), ...notes.filter(note => !note.isPinned)];
90 | return note.id}
95 | renderItem={({ item: note, drag, isActive }) => (
96 | handlePress(note.id),
99 | onLongPress: () => { handleLongPress(note.id); drag(); },
100 | }}
101 | isAddedInSelection={selectedNotes.includes(note.id)}
102 | />
103 | )}
104 | onDragEnd={changeOrderOfNotes}
105 | autoscrollThreshold={100}
106 | />
107 | }
108 |
109 |
110 | return (
111 |
112 |
113 | {notes.length === 0 && (
114 |
115 |
116 | No note.. Tap + icon to add new note
117 |
118 |
119 | )}
120 |
121 | {notes.length > 0 && (
122 | <>
123 | {!isSearchMode ? (
124 | <>
125 | {notes.length} note{notes.length > 1 && 's'}
126 |
127 | {getDraggableFlatList(notes)}
128 |
129 | >
130 | ) : (
131 | <>
132 | {filteredNotes === null ? (
133 | <>
134 | {notes.length} note{notes.length > 1 && 's'}
135 | {getDraggableFlatList(notes)}
136 | >
137 | ) : filteredNotes.length === 0 ? (
138 | No note found..
139 | ) : (
140 | <>
141 | {filteredNotes.length} note{filteredNotes.length > 1 && 's'} found
142 | {getDraggableFlatList(filteredNotes)}
143 | >
144 | )}
145 | >
146 | )}
147 | >
148 | )}
149 |
150 |
151 | )
152 | }
153 |
154 | export default Notes
--------------------------------------------------------------------------------
/components/NotesPageHeader.js:
--------------------------------------------------------------------------------
1 | import { View, Text, Pressable, Alert, TextInput, ToastAndroid } from 'react-native'
2 | import React, { useEffect } from 'react'
3 | import Icon from 'react-native-vector-icons/Ionicons';
4 | import { useGlobalContext } from '../context/context';
5 | import { getData, storeData } from '../utils/storage';
6 | import { getDefaultHeaderHeight } from '@react-navigation/elements';
7 | import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context';
8 | import { useNavigation } from '@react-navigation/native';
9 |
10 | const NotesPageHeader = ({ selectedNotes, setSelectedNotes, setFilteredNotes, isSearchMode, setIsSearchMode, searchValue, setSearchValue }) => {
11 |
12 | const frame = useSafeAreaFrame();
13 | const insets = useSafeAreaInsets();
14 | const headerHeight = getDefaultHeaderHeight(frame, false, insets.top);
15 | const { notes, setNotes } = useGlobalContext();
16 | const navigation = useNavigation();
17 |
18 | // useFocusEffect(useCallback(() => {
19 | // return () => setIsSearchMode(false);
20 | // }, [setIsSearchMode]));
21 |
22 | useEffect(() => {
23 | if (searchValue === "") setFilteredNotes(null);
24 | else setFilteredNotes(notes.filter(note => note.text.toLowerCase().includes(searchValue.toLowerCase())));
25 | }, [searchValue, notes, setFilteredNotes]);
26 |
27 | const handleDeleteMany = async () => {
28 | try {
29 | const notesToTrash = selectedNotes.map(selectedId => notes.find(note => note.id === selectedId));
30 | const trashNotes = (await getData("trashNotes")) || [];
31 | const newTrashNotesArr = [...trashNotes, ...notesToTrash];
32 | await storeData("trashNotes", newTrashNotesArr);
33 |
34 | const newNotesArr = notes.filter(note => !selectedNotes.includes(note.id));
35 | await storeData("notes", newNotesArr);
36 | setNotes(newNotesArr);
37 | ToastAndroid.show("Note moved to trash", ToastAndroid.SHORT);
38 | }
39 | catch (err) {
40 | Alert.alert("Error", "Some error is there!!");
41 | }
42 | }
43 |
44 | const handleCloseSelection = () => {
45 | setSelectedNotes([]);
46 | }
47 |
48 | const handleSearchBtnClick = () => {
49 | setIsSearchMode(true);
50 | }
51 |
52 | const handleCloseSearch = () => {
53 | setIsSearchMode(false);
54 | }
55 |
56 | const handleTextChange = (text) => {
57 | setSearchValue(text);
58 | }
59 |
60 |
61 | return (
62 |
63 |
64 | {!isSearchMode ? (
65 | <>
66 | {selectedNotes.length == 0 ? (
67 |
68 | navigation.openDrawer()}>
69 |
70 |
71 | Notes
72 |
73 |
74 |
75 |
76 | ) : (
77 |
78 |
79 |
80 |
81 | {selectedNotes.length} selected
82 |
83 |
84 |
85 |
86 | )}
87 | >
88 | ) : (
89 |
90 |
91 |
92 |
93 |
94 | setSearchValue("")} android_ripple={{ color: "#ccc", radius: 20 }}>
95 |
96 |
97 |
98 | )}
99 |
100 |
101 | )
102 | }
103 |
104 | export default NotesPageHeader
--------------------------------------------------------------------------------
/context/context.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from "react";
2 | import { getData } from "../utils/storage";
3 | import React from 'react'
4 |
5 | const GlobalContext = createContext();
6 | export const GlobalContextProvider = ({ children }) => {
7 | const [notes, setNotes] = useState([]);
8 | const [labels, setLabels] = useState([]);
9 |
10 | useEffect(() => {
11 | const fetchLocalStorageData = async () => {
12 | const notes = await getData("notes");
13 | if (notes) setNotes(notes);
14 | const labels = await getData("labels");
15 | if (labels) setLabels(labels);
16 | }
17 | fetchLocalStorageData();
18 | }, []);
19 |
20 | return {children}
21 | }
22 |
23 | export const useGlobalContext = () => {
24 | return useContext(GlobalContext);
25 | }
--------------------------------------------------------------------------------
/eas.json:
--------------------------------------------------------------------------------
1 | {
2 | "cli": {
3 | "version": ">= 0.60.0"
4 | },
5 | "build": {
6 | "development": {
7 | "developmentClient": true,
8 | "distribution": "internal"
9 | },
10 | "preview": {
11 | "android": {
12 | "buildType": "apk"
13 | }
14 | },
15 | "preview2": {
16 | "android": {
17 | "gradleCommand": ":app:assembleRelease"
18 | }
19 | },
20 | "preview3": {
21 | "developmentClient": true
22 | },
23 | "production": {}
24 | },
25 | "submit": {
26 | "production": {}
27 | }
28 | }
--------------------------------------------------------------------------------
/screens/AddNote.js:
--------------------------------------------------------------------------------
1 | import { Alert, BackHandler, TextInput, View } from 'react-native'
2 | import React, { useCallback, useEffect, useState } from 'react'
3 | import { useNavigation } from '@react-navigation/native';
4 | import { storeData } from '../utils/storage';
5 | import { useGlobalContext } from '../context/context';
6 | import ActionButton from '../components/ActionButton';
7 |
8 | const AddNote = () => {
9 | const navigation = useNavigation();
10 | const { notes, setNotes } = useGlobalContext();
11 |
12 | const [formData, setFormData] = useState({
13 | text: ""
14 | });
15 |
16 |
17 | useEffect(() => {
18 | const backAction = () => {
19 | if (formData.text === "") return false;
20 | Alert.alert("Your note has not been saved.", "Do you want to save it?", [{ text: "Yes", onPress: saveNote }, { text: "No", onPress: navigation.goBack }], { cancelable: true });
21 | return true;
22 | }
23 | BackHandler.addEventListener("hardwareBackPress", backAction);
24 | return () => BackHandler.removeEventListener("hardwareBackPress", backAction);
25 | }, [formData, navigation, saveNote]);
26 |
27 |
28 |
29 | const handleChange = (name, value) => {
30 | setFormData({ ...formData, [name]: value });
31 | }
32 |
33 | const saveNote = useCallback(async () => {
34 | try {
35 | if (formData.text === "") return;
36 | const newNote = {
37 | id: Math.floor(Math.random() * 10000),
38 | text: formData.text,
39 | createdAt: new Date(),
40 | updatedAt: new Date(),
41 | labels: []
42 | }
43 | const newNotesArr = [newNote, ...notes];
44 | await storeData("notes", newNotesArr);
45 | setNotes(newNotesArr);
46 | navigation.navigate("Home");
47 | }
48 | catch (err) {
49 | Alert.alert("Error", "Some error is there!!");
50 | }
51 | }, [formData, navigation, notes, setNotes]);
52 |
53 |
54 | return (
55 |
56 |
57 | handleChange("text", text)} multiline={true} style={{ padding: 20, fontSize: 16, color: "#555" }} placeholder="Your note" autoFocus />
58 |
59 |
60 |
61 |
62 | )
63 | }
64 |
65 | export default AddNote
--------------------------------------------------------------------------------
/screens/FolderNotes.js:
--------------------------------------------------------------------------------
1 | import { View, Text, Alert, ActivityIndicator, FlatList, Pressable, TextInput } from 'react-native'
2 | import React, { useCallback, useEffect, useRef, useState } from 'react'
3 | import { useNavigation, useRoute } from '@react-navigation/native'
4 | import { getData, storeData } from '../utils/storage';
5 | import theme from '../style/theme';
6 | import NoteCard from '../components/NoteCard';
7 | import ActionButton from '../components/ActionButton';
8 | import { useGlobalContext } from '../context/context';
9 | import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context';
10 | import { getDefaultHeaderHeight } from '@react-navigation/elements';
11 | import Icon from 'react-native-vector-icons/Ionicons';
12 |
13 | const FolderNotes = () => {
14 | const [folder, setFolder] = useState({});
15 | const navigation = useNavigation();
16 | const route = useRoute();
17 | const { folderId } = route.params;
18 | const [loading, setLoading] = useState(true);
19 | const { notes } = useGlobalContext();
20 | const [folderNameEditMode, setFolderNameEditMode] = useState(false);
21 | const [folderNameEditInput, setFolderNameEditInput] = useState('');
22 | const folderNameRef = useRef(null);
23 |
24 | useEffect(() => {
25 | setFolderNameEditInput(folder.name);
26 | }, [folder]);
27 |
28 | const fetchFolderWithNotes = useCallback(async () => {
29 | try {
30 | const folders = await getData("folders");
31 | const folder = folders.find(folder => folder.id === folderId);
32 | folder.notes = folder.notes.map(noteId => notes.find(note => note.id === noteId));
33 | setFolder(folder);
34 | setFolderNameEditInput(folder.name);
35 | setLoading(false);
36 | }
37 | catch (err) {
38 | console.log(err);
39 | Alert.alert("Error", "Folder couldn't be fetched");
40 | }
41 | }, [folderId, notes]);
42 |
43 |
44 | useEffect(() => {
45 | fetchFolderWithNotes();
46 | }, [fetchFolderWithNotes]);
47 |
48 | const handlePress = (noteId) => {
49 | navigation.navigate("UpdateNote", { id: noteId });
50 | }
51 |
52 |
53 | const getFlatList = folderNotes => {
54 | return note.id}
59 | renderItem={({ item: note }) => handlePress(note.id) }} />}
60 | />
61 | }
62 |
63 | const onNotesSelected = async selectedNotes => {
64 | try {
65 | setLoading(true);
66 | const folders = await getData("folders");
67 | const newFoldersArr = folders.map(folder => {
68 | if (folder.id !== folderId) return folder;
69 | return { ...folder, notes: selectedNotes };
70 | });
71 | await storeData("folders", newFoldersArr);
72 | fetchFolderWithNotes();
73 | setLoading(false);
74 | }
75 | catch (err) {
76 | console.log(err);
77 | Alert.alert("Error", "Notes couldn't be added into folder");
78 | }
79 | }
80 |
81 |
82 | const frame = useSafeAreaFrame();
83 | const insets = useSafeAreaInsets();
84 | const headerHeight = getDefaultHeaderHeight(frame, false, insets.top);
85 |
86 | const handleFolderNameFocus = () => {
87 | setFolderNameEditMode(true);
88 | }
89 |
90 | const handleBackClick = () => {
91 | if (folderNameEditMode) {
92 | setFolderNameEditInput(folder.name);
93 | setFolderNameEditMode(false);
94 | }
95 | else {
96 | navigation.goBack();
97 | }
98 | }
99 |
100 | const changeFolderName = async () => {
101 | try {
102 | if (folderNameEditInput === folder.name) return;
103 | const folders = await getData("folders");
104 | const newFoldersArr = folders.map(folder => {
105 | if (folder.id !== folderId) return folder;
106 | return { ...folder, name: folderNameEditInput };
107 | });
108 | await storeData("folders", newFoldersArr);
109 | setFolderNameEditMode(false);
110 | setFolder({ ...folder, name: folderNameEditInput });
111 | }
112 | catch (err) {
113 | console.log(err);
114 | Alert.alert("Error", "Couldn't change folder name");
115 | }
116 | }
117 |
118 | useEffect(() => {
119 | if (folderNameEditMode === false) folderNameRef?.current?.blur();
120 | }, [folderNameEditMode]);
121 |
122 |
123 | if (loading) {
124 | return (
125 |
126 |
127 |
128 | );
129 | }
130 |
131 | return (
132 |
133 |
134 | {/* Header */}
135 |
136 |
137 |
138 |
139 |
140 | setFolderNameEditInput(value)}
146 | onFocus={handleFolderNameFocus}
147 | onSubmitEditing={changeFolderName}
148 | />
149 | {folderNameEditMode && (
150 |
151 |
152 |
153 | )}
154 |
155 |
156 |
157 | {/* Body */}
158 |
159 |
160 | {folder.notes.length === 0 && (
161 |
162 |
163 | No notes in this folder.. {"\n\n"} Tap + icon to add notes in this folder
164 |
165 |
166 | )}
167 |
168 | {folder.notes.length > 0 && (
169 | <>
170 | {folder.notes.length} note{folder.notes.length > 1 && 's'}
171 | {getFlatList(folder.notes)}
172 | >
173 | )}
174 |
175 | navigation.navigate("NotesSelector", { onNotesSelected, unSelectableNotes: folder.notes.map(note => note.id) })} />
176 |
177 |
178 | )
179 | }
180 |
181 | export default FolderNotes
--------------------------------------------------------------------------------
/screens/Folders.js:
--------------------------------------------------------------------------------
1 | import { View, Text, TextInput, Pressable, Alert, ToastAndroid, ActivityIndicator } from 'react-native'
2 | import React, { useCallback, useState } from 'react'
3 | import ActionButton from '../components/ActionButton';
4 | import Modal from '../components/Modal';
5 | import { getData, storeData } from '../utils/storage';
6 | import DraggableFlatList from 'react-native-draggable-flatlist';
7 | import theme from '../style/theme';
8 | import FolderCard from '../components/FolderCard';
9 | import { useFocusEffect, useNavigation } from '@react-navigation/native';
10 |
11 | const Folders = () => {
12 | const [folders, setFolders] = useState([]);
13 | const [createFolderModal, setCreateFolderModal] = useState(false);
14 | const [FolderNameInput, setFolderNameInput] = useState("");
15 | const navigation = useNavigation();
16 | const [loading, setLoading] = useState(true);
17 |
18 | useFocusEffect(useCallback(() => {
19 | const fetchFolders = async () => {
20 | try {
21 | const folders = (await getData("folders")) || [];
22 | setFolders(folders);
23 | setLoading(false);
24 | }
25 | catch (err) {
26 | console.log(err);
27 | Alert.alert("Error", "Folders couldn't be fetched");
28 | }
29 | }
30 | fetchFolders();
31 | }, []));
32 |
33 | const closeModal = () => {
34 | setCreateFolderModal(false);
35 | setFolderNameInput("");
36 | }
37 |
38 | const createFolder = async () => {
39 | try {
40 | if (FolderNameInput === "") return;
41 | const newFolder = {
42 | id: Math.floor(Math.random() * 10000),
43 | name: FolderNameInput,
44 | notes: [],
45 | createdAt: new Date(),
46 | };
47 |
48 | const newFoldersArr = [newFolder, ...folders];
49 | await storeData("folders", newFoldersArr);
50 | setFolders(newFoldersArr);
51 | ToastAndroid.show("Folder created", ToastAndroid.SHORT);
52 | closeModal();
53 | }
54 | catch (err) {
55 | console.log(err);
56 | Alert.alert("Error", "Some error is there!!");
57 | }
58 | }
59 |
60 | const changeOrderOfFolders = async ({ data }) => {
61 | try {
62 | const newFoldersArr = data;
63 | if (newFoldersArr.length === folders.length && JSON.stringify(newFoldersArr.map(folder => folder.id)) === JSON.stringify(folders.map(folder => folder.id))) return;
64 | setFolders(newFoldersArr);
65 | await storeData("folders", newFoldersArr);
66 | }
67 | catch (err) {
68 | console.log(err);
69 | Alert.alert("Error", "Some error is there!!");
70 | }
71 | }
72 |
73 | const handlePress = folderId => {
74 | navigation.navigate("FolderNotes", { folderId });
75 | }
76 |
77 | const getDraggableFlatList = folders => {
78 | return folder.id}
84 | renderItem={({ item: folder, drag, isActive }) => }
85 | onDragEnd={changeOrderOfFolders}
86 | autoscrollThreshold={100}
87 | />
88 | }
89 |
90 |
91 |
92 | if (loading) {
93 | return (
94 |
95 |
96 |
97 | );
98 | }
99 |
100 | return (
101 |
102 |
103 | {folders.length === 0 && (
104 |
105 |
106 | No folders present.. Tap + icon to create new folder
107 |
108 |
109 | )}
110 |
111 | {folders.length > 0 && (
112 | <>
113 | {folders.length} folder{folders.length > 1 && 's'}
114 | {getDraggableFlatList(folders)}
115 | >
116 | )}
117 |
118 |
119 |
120 | setCreateFolderModal(true)} />
121 |
122 |
123 | New folder
124 | setFolderNameInput(text)}
127 | placeholder='Folder name'
128 | style={{ backgroundColor: "#eee", paddingHorizontal: 15, paddingVertical: 5, borderRadius: 2 }}
129 | />
130 |
131 |
132 |
138 | Create
139 |
140 |
141 |
146 | Cancel
147 |
148 |
149 |
150 |
151 |
152 | )
153 | }
154 |
155 | export default Folders
--------------------------------------------------------------------------------
/screens/Home.js:
--------------------------------------------------------------------------------
1 | import { View } from 'react-native'
2 | import React, { useEffect, useState } from 'react'
3 | import Notes from '../components/Notes'
4 | import ActionButton from '../components/ActionButton'
5 | import NotesPageHeader from '../components/NotesPageHeader'
6 | import { useNavigation } from '@react-navigation/native'
7 |
8 | const Home = () => {
9 |
10 | const [selectedNotes, setSelectedNotes] = useState([]);
11 | const [isSearchMode, setIsSearchMode] = useState(false);
12 | const [searchValue, setSearchValue] = useState("");
13 | const [filteredNotes, setFilteredNotes] = useState(null);
14 | const navigation = useNavigation();
15 | useEffect(() => {
16 | if (!isSearchMode) setSearchValue("");
17 | }, [isSearchMode]);
18 |
19 | return (
20 | <>
21 |
22 |
23 |
24 | navigation.navigate("AddNote")} />
25 |
26 | >
27 | )
28 | }
29 |
30 | export default Home
--------------------------------------------------------------------------------
/screens/LabelsManager.js:
--------------------------------------------------------------------------------
1 | import { View, Text, TextInput, Pressable, ToastAndroid, Alert, ScrollView } from 'react-native'
2 | import React, { useEffect, useState } from 'react'
3 | import { useGlobalContext } from '../context/context';
4 | import theme from '../style/theme';
5 | import { storeData } from '../utils/storage';
6 | import Modal from '../components/Modal';
7 |
8 | const LabelsManager = () => {
9 | const [labelSearchInput, setlabelSearchInput] = useState("");
10 | const { notes, setNotes, labels, setLabels } = useGlobalContext();
11 |
12 | const [modalLabel, setModalLabel] = useState(null);
13 | const [editLabelInput, setEditLabelInput] = useState("");
14 |
15 | useEffect(() => {
16 | if (modalLabel) setEditLabelInput(modalLabel);
17 | }, [modalLabel]);
18 |
19 | const filteredLabels = labelSearchInput === "" ? labels : labels.filter(label => label.includes(labelSearchInput));
20 |
21 | const createLabel = async () => {
22 | try {
23 | if (labelSearchInput === "") return;
24 | const newLabelsArr = [...labels, labelSearchInput];
25 | await storeData("labels", newLabelsArr);
26 | setLabels(newLabelsArr);
27 | ToastAndroid.show("Label added", ToastAndroid.SHORT);
28 | setlabelSearchInput("");
29 | }
30 | catch (err) {
31 | console.log(err);
32 | Alert.alert("Error", "Some error is there!!");
33 | }
34 | }
35 |
36 | const saveLabel = async () => {
37 | try {
38 | if (!editLabelInput) return;
39 | if (modalLabel === editLabelInput) {
40 | setModalLabel(null);
41 | return;
42 | }
43 |
44 | const newLabelsArr = labels.map(label => {
45 | if (label !== modalLabel) return label;
46 | return editLabelInput;
47 | });
48 | await storeData("labels", newLabelsArr);
49 | setLabels(newLabelsArr);
50 |
51 | const newNotesArr = notes.map(note => {
52 | if (!note.labels) return note;
53 | note.labels = note.labels.map(label => {
54 | if (label !== modalLabel) return label;
55 | return editLabelInput;
56 | });
57 | return note;
58 | });
59 | await storeData("notes", newNotesArr);
60 | setNotes(newNotesArr);
61 |
62 | ToastAndroid.show("Label changed", ToastAndroid.SHORT);
63 | setModalLabel(null);
64 | }
65 | catch (err) {
66 | console.log(err);
67 | Alert.alert("Error", "Some error is there!!");
68 | }
69 | }
70 |
71 | const deleteLabel = async () => {
72 | try {
73 | const newLabelsArr = labels.filter(label => label !== modalLabel);
74 | await storeData("labels", newLabelsArr);
75 | setLabels(newLabelsArr);
76 |
77 | const newNotesArr = notes.map(note => {
78 | if (!note.labels) return note;
79 | note.labels = note.labels.filter(label => label !== modalLabel);
80 | return note;
81 | });
82 | await storeData("notes", newNotesArr);
83 | setNotes(newNotesArr);
84 |
85 | ToastAndroid.show("Label deleted from all such notes", ToastAndroid.SHORT);
86 | setModalLabel(null);
87 | }
88 | catch (err) {
89 | console.log(err);
90 | Alert.alert("Error", "Some error is there!!");
91 | }
92 | }
93 |
94 | return (
95 |
96 | setlabelSearchInput(text)}
99 | placeholder='Search or create label...'
100 | style={{ color: "#666", fontWeight: "500", fontSize: 17, backgroundColor: "white", paddingVertical: 10, paddingHorizontal: 15, borderBottomWidth: 1, borderBottomColor: '#ddd', borderTopColor: "#ddd", borderTopWidth: 1 }}
101 | />
102 |
103 | {labels.length} total
104 |
105 | {labelSearchInput !== "" && !labels.includes(labelSearchInput) && (
106 |
107 | + Create label `{labelSearchInput}`
108 |
109 | )}
110 |
111 |
112 | {filteredLabels.map(label => (
113 | setModalLabel(label)}
115 | style={{ margin: 10, paddingVertical: 5, paddingHorizontal: 10, borderRadius: 3, backgroundColor: "#0193fe" }}
116 | android_ripple={{ color: theme.PRIMARY_COLOR, radius: 200 }}
117 | >
118 | {label}
119 |
120 | ))}
121 |
122 |
123 |
124 |
125 | setModalLabel(null)}>
126 |
127 |
128 | setEditLabelInput(text)} />
133 |
134 |
135 |
141 | Save
142 |
143 |
144 |
149 | Delete
150 |
151 |
152 |
153 |
154 |
155 | )
156 | }
157 |
158 | export default LabelsManager
159 |
--------------------------------------------------------------------------------
/screens/NoteLabelsManager.js:
--------------------------------------------------------------------------------
1 | import { View, Text, TextInput, Pressable, Alert, ToastAndroid, BackHandler, ScrollView } from 'react-native'
2 | import React, { useEffect, useState } from 'react'
3 | import theme from '../style/theme';
4 | import { useGlobalContext } from '../context/context';
5 | import { storeData } from '../utils/storage';
6 | import { useNavigation, useRoute } from '@react-navigation/native';
7 | import ActionButton from '../components/ActionButton';
8 |
9 | const NoteLabelsManager = () => {
10 |
11 | const navigation = useNavigation();
12 | const route = useRoute();
13 | const { notes, setNotes, labels, setLabels } = useGlobalContext();
14 | const [labelInput, setlabelInput] = useState("");
15 |
16 | const noteId = route.params.noteId;
17 | const note = notes.find(note => note.id === noteId);
18 | const [tempMarkedLabels, setTempMarkedLabels] = useState(note.labels);
19 |
20 | useEffect(() => {
21 | setTempMarkedLabels(note.labels);
22 | }, [note.labels]);
23 |
24 | const filteredLabels = labelInput === "" ? labels : labels.filter(label => label.includes(labelInput));
25 | const reorderedLabels = [...filteredLabels.filter(label => note.labels.includes(label)), ...filteredLabels.filter(label => !note.labels.includes(label))];
26 |
27 |
28 | const areLabelsModified = (() => {
29 | if (tempMarkedLabels.length !== note.labels.length) return true;
30 | return tempMarkedLabels.some(tempMarkedLabel => !note.labels.includes(tempMarkedLabel));
31 | })();
32 |
33 |
34 | useEffect(() => {
35 | const backAction = () => {
36 | if (!areLabelsModified) return false;
37 | Alert.alert("Discard changes", "", [{ text: "Cancel" }, { text: "Discard", onPress: navigation.goBack }], { cancelable: true });
38 | return true;
39 | }
40 | BackHandler.addEventListener("hardwareBackPress", backAction);
41 | return () => BackHandler.removeEventListener("hardwareBackPress", backAction);
42 | }, [areLabelsModified, navigation]);
43 |
44 |
45 |
46 | const handleLabelInputChange = (text) => {
47 | setlabelInput(text);
48 | }
49 |
50 |
51 | const createAndAddLabel = async () => {
52 | try {
53 | if (labelInput === "") return;
54 | const newLabelsArr = [...labels, labelInput];
55 | await storeData("labels", newLabelsArr);
56 | setLabels(newLabelsArr);
57 |
58 | const newNotesArr = notes.map(note => {
59 | if (note.id !== noteId) return note;
60 | return { ...note, labels: [...(note.labels || []), labelInput] };
61 | });
62 | await storeData("notes", newNotesArr);
63 | setNotes(newNotesArr);
64 | ToastAndroid.show("Label added", ToastAndroid.SHORT);
65 | setlabelInput("");
66 | }
67 | catch (err) {
68 | console.log(err);
69 | Alert.alert("Error", "Some error is there!!");
70 | }
71 | }
72 |
73 |
74 | const changeLabelsOfNote = async () => {
75 | try {
76 | const newNotesArr = notes.map(note => {
77 | if (note.id !== noteId) return note;
78 | return { ...note, labels: tempMarkedLabels };
79 | });
80 | await storeData("notes", newNotesArr);
81 | setNotes(newNotesArr);
82 | setlabelInput("");
83 | navigation.goBack();
84 | }
85 | catch (err) {
86 | console.log(err);
87 | Alert.alert("Error", "Some error is there!!");
88 | }
89 | }
90 |
91 |
92 | const toggleTempLabelForNote = label => {
93 | if (tempMarkedLabels.includes(label)) setTempMarkedLabels(tempMarkedLabels.filter(tempLabel => tempLabel !== label));
94 | else setTempMarkedLabels([...tempMarkedLabels, label]);
95 | }
96 |
97 |
98 | return (
99 |
100 |
101 |
102 | {labels.length} total, {tempMarkedLabels.length} selected
103 |
104 | {labelInput !== "" && !labels.includes(labelInput) && (
105 |
106 | + Create and add label `{labelInput}`
107 |
108 | )}
109 |
110 |
111 | {reorderedLabels.map(label => (
112 | toggleTempLabelForNote(label)}
114 | style={{ margin: 10, paddingVertical: 5, paddingHorizontal: 10, borderRadius: 3, backgroundColor: tempMarkedLabels.includes(label) ? "#0193fe" : "#dff6ff" }}
115 | android_ripple={{ color: theme.PRIMARY_COLOR, radius: 200 }}
116 | >
117 |
118 | {label}
119 |
120 |
121 | ))}
122 |
123 |
124 |
125 |
126 | )
127 | }
128 |
129 | export default NoteLabelsManager
130 |
--------------------------------------------------------------------------------
/screens/NotesSelector.js:
--------------------------------------------------------------------------------
1 | import { View, Text, FlatList, Pressable } from 'react-native'
2 | import React, { useState } from 'react'
3 | import ActionButton from '../components/ActionButton'
4 | import { useGlobalContext } from '../context/context';
5 | import NoteCardText from '../components/NoteCardText';
6 | import { useNavigation, useRoute } from '@react-navigation/native';
7 | import theme from '../style/theme';
8 | import Icon from 'react-native-vector-icons/Ionicons';
9 |
10 |
11 | const NoteSelectorCard = ({ note, onPress, isAddedInSelection }) => {
12 | return (
13 |
22 | {isAddedInSelection && (
23 |
24 |
25 |
26 | )}
27 |
28 |
29 | );
30 | }
31 |
32 |
33 | const NotesSelector = () => {
34 | const [selectedNotes, setSelectedNotes] = useState([]);
35 | const { notes } = useGlobalContext();
36 | const navigation = useNavigation();
37 | const route = useRoute();
38 | const { unSelectableNotes = [], onNotesSelected } = route.params;
39 | const selectableNotes = notes.filter(note => !unSelectableNotes.includes(note.id));
40 |
41 | const handlePress = (id) => {
42 | if (selectedNotes.includes(id)) {
43 | setSelectedNotes(selectedNotes.filter(selectedNoteId => selectedNoteId !== id));
44 | }
45 | else {
46 | setSelectedNotes([...selectedNotes, id]);
47 | }
48 | }
49 |
50 | const getFlatList = notes => {
51 | return note.id}
56 | renderItem={({ item: note }) => (
57 | handlePress(note.id)}
60 | isAddedInSelection={selectedNotes.includes(note.id)}
61 | />
62 | )}
63 | />
64 | }
65 |
66 | const onDone = () => {
67 | onNotesSelected(selectedNotes);
68 | navigation.goBack();
69 | }
70 |
71 |
72 |
73 | return (
74 |
75 | {selectedNotes.length} selected
76 | {getFlatList(selectableNotes)}
77 |
78 | 0} />
79 |
80 | )
81 | }
82 |
83 | export default NotesSelector
--------------------------------------------------------------------------------
/screens/Trash.js:
--------------------------------------------------------------------------------
1 | import { View, Text, Alert, ToastAndroid, ScrollView, StyleSheet, Pressable } from 'react-native'
2 | import React, { useCallback, useState } from 'react'
3 | import { getData, storeData } from '../utils/storage';
4 | import { useFocusEffect } from '@react-navigation/native';
5 | import theme from '../style/theme';
6 | import { FlatList } from 'react-native-gesture-handler';
7 | import Icon from 'react-native-vector-icons/Ionicons';
8 | import { convertToXTimeAgo } from '../utils/dateformat';
9 | import { useGlobalContext } from '../context/context';
10 | import Modal from '../components/Modal';
11 |
12 |
13 | const TrashNoteCard = ({ note, setNoteModalId }) => {
14 | return (
15 | setNoteModalId(note.id)}
17 | android_ripple={{ color: "#bbb", radius: 200 }}
18 | style={styles.noteCard}
19 | delayLongPress={100}
20 | >
21 |
22 |
23 | {note.color && (
24 |
25 | )}
26 | {convertToXTimeAgo(note.updatedAt)}
27 | {note.isBookmarked && (
28 |
29 | )}
30 | {note.isPinned && (
31 |
32 | )}
33 |
34 |
35 | {note.labels.length > 0 && (
36 |
37 | {note.labels?.map(label => (
38 | {label}
39 | ))}
40 |
41 | )}
42 |
43 | {note.reminder?.dateTime && (
44 | + Reminder
45 | )}
46 |
47 | {note.text}
48 |
49 |
50 | );
51 | }
52 |
53 |
54 | const Trash = () => {
55 | const [trashNotes, setTrashNotes] = useState([]);
56 | const { notes, setNotes } = useGlobalContext();
57 | const [noteModalId, setNoteModalId] = useState(null);
58 |
59 | useFocusEffect(useCallback(() => {
60 | const fetchTrash = async () => {
61 | try {
62 | const trashNotes = (await getData("trashNotes")) || [];
63 | setTrashNotes(trashNotes);
64 | }
65 | catch (err) {
66 | Alert.alert("Error", "Some error is there!!");
67 | }
68 | }
69 | fetchTrash();
70 | }, []));
71 |
72 | const deleteNotePermanently = async noteId => {
73 | try {
74 | const newTrashNotesArr = trashNotes.filter(trashNote => trashNote.id !== noteId);
75 | await storeData("trashNotes", newTrashNotesArr);
76 | setTrashNotes(newTrashNotesArr);
77 | setNoteModalId(null);
78 | ToastAndroid.show("Note permanently deleted", ToastAndroid.SHORT);
79 | }
80 | catch (err) {
81 | Alert.alert("Error", "Some error is there!!");
82 | }
83 | }
84 |
85 | const emptyTrash = async () => {
86 | try {
87 | await storeData("trashNotes", []);
88 | setTrashNotes([]);
89 | ToastAndroid.show("Trash is empty now", ToastAndroid.SHORT);
90 | }
91 | catch (err) {
92 | Alert.alert("Error", "Some error is there!!");
93 | }
94 | }
95 |
96 | const handleEmptyTrashRequest = () => {
97 | Alert.alert(
98 | "Are you sure you want to empty the trash?", "You won't be able to restore these notes later.",
99 | [{ text: "Cancel" }, { text: "OK", onPress: emptyTrash }],
100 | { cancelable: true }
101 | );
102 | }
103 |
104 |
105 | const restoreNote = async noteId => {
106 | try {
107 | const note = trashNotes.find(note => note.id === noteId);
108 | const newNotesArr = [...notes, note];
109 | await storeData("notes", newNotesArr);
110 | setNotes(newNotesArr);
111 |
112 | const newTrashNotesArr = trashNotes.filter(trashNote => trashNote.id !== noteId);
113 | await storeData("trashNotes", newTrashNotesArr);
114 | setTrashNotes(newTrashNotesArr);
115 | setNoteModalId(null);
116 | ToastAndroid.show("Note restored", ToastAndroid.SHORT);
117 | }
118 | catch (err) {
119 | Alert.alert("Error", "Some error is there!!");
120 | }
121 | }
122 |
123 | const restoreAllNotes = async () => {
124 | try {
125 | const newNotesArr = [...notes, ...trashNotes];
126 | await storeData("notes", newNotesArr);
127 | setNotes(newNotesArr);
128 | await storeData("trashNotes", []);
129 | setTrashNotes([]);
130 | ToastAndroid.show("All notes restored", ToastAndroid.SHORT);
131 | }
132 | catch (err) {
133 | Alert.alert("Error", "Some error is there!!");
134 | }
135 | }
136 |
137 | const handleRestoreRequest = () => {
138 | Alert.alert(
139 | "Restore all notes", null,
140 | [{ text: "Cancel" }, { text: "OK", onPress: restoreAllNotes }],
141 | { cancelable: true }
142 | );
143 | }
144 |
145 |
146 | return (
147 |
148 | {trashNotes.length === 0 && (
149 |
150 |
151 | Trash is empty
152 |
153 |
154 | )}
155 |
156 | {trashNotes.length > 0 && (
157 | <>
158 |
159 | {trashNotes.length} note{trashNotes.length > 1 && 's'} in trash
160 |
165 | Restore
166 |
167 |
172 | Empty
173 |
174 |
175 |
176 | note.id}
180 | renderItem={({ item: note }) => }
181 | />
182 | >
183 |
184 | )}
185 |
186 | setNoteModalId(null)}>
187 |
188 | restoreNote(noteModalId)}
190 | style={{ paddingVertical: 10 }}
191 | android_ripple={{ color: "#bbb", radius: 200 }}>
192 | Restore
193 |
194 |
195 | deleteNotePermanently(noteModalId)}
197 | style={{ paddingVertical: 10 }}
198 | android_ripple={{ color: "#bbb", radius: 200 }}>
199 | Delete permanently
200 |
201 |
202 |
203 |
204 | )
205 | }
206 |
207 | export default Trash
208 |
209 | const styles = StyleSheet.create({
210 | noteCard: {
211 | marginBottom: 20,
212 | marginHorizontal: 10,
213 | borderRadius: 3,
214 | paddingHorizontal: 15,
215 | paddingTop: 10,
216 | paddingBottom: 15,
217 | backgroundColor: "white",
218 | borderBottomWidth: 2,
219 | borderBottomColor: "#e4e4e4",
220 | }
221 | })
--------------------------------------------------------------------------------
/screens/UpdateNote.js:
--------------------------------------------------------------------------------
1 | import { Alert, Pressable, Text, TextInput, View, ToastAndroid, BackHandler } from 'react-native'
2 | import React, { useCallback, useEffect, useState } from 'react'
3 | import { useIsFocused, useNavigation, useRoute } from '@react-navigation/native';
4 | import { storeData } from '../utils/storage';
5 | import { useGlobalContext } from '../context/context';
6 | import Icon from 'react-native-vector-icons/Ionicons';
7 | import { convertToXTimeAgo } from '../utils/dateformat';
8 | import { SheetManager } from "react-native-actions-sheet";
9 | import NoteOptionsActionSheet from '../components/NoteOptionsActionSheet';
10 | import moment from 'moment';
11 | import NoteReminderModal from '../components/NoteReminderModal';
12 | import ActionButton from '../components/ActionButton';
13 | import { SharedElement } from 'react-navigation-shared-element';
14 |
15 | const UpdateNote = () => {
16 | const navigation = useNavigation();
17 | const route = useRoute();
18 | const isFocussed = useIsFocused();
19 | const { notes, setNotes } = useGlobalContext();
20 | const [noteReminderModal, setNoteReminderModal] = useState(false);
21 |
22 | const noteId = route.params.id;
23 | const note = notes.find(note => note.id === noteId);
24 | const [formData, setFormData] = useState({ text: note.text });
25 |
26 | useEffect(() => {
27 | const backAction = () => {
28 | if (!isFocussed) return false;
29 | if (formData.text === "" || formData.text === note?.text) return false;
30 | Alert.alert("Your changes have not been saved.", "Do you want to save it?", [{ text: "Yes", onPress: updateNote }, { text: "No", onPress: navigation.goBack }], { cancelable: true });
31 | return true;
32 | }
33 | BackHandler.addEventListener("hardwareBackPress", backAction);
34 | return () => BackHandler.removeEventListener("hardwareBackPress", backAction);
35 | }, [isFocussed, formData, navigation, updateNote, note?.text]);
36 |
37 |
38 | const handleChange = (name, value) => {
39 | setFormData({ ...formData, [name]: value });
40 | }
41 |
42 | const updateNote = useCallback(async () => {
43 | try {
44 | if (formData.text === "") return;
45 | const newNotesArr = notes.map(note => {
46 | if (note.id !== noteId) return note;
47 | return { ...note, text: formData.text, updatedAt: new Date() };
48 | })
49 | await storeData("notes", newNotesArr);
50 | setNotes(newNotesArr);
51 | navigation.navigate("Home");
52 | ToastAndroid.show("Note saved", ToastAndroid.SHORT);
53 | }
54 | catch (err) {
55 | Alert.alert("Error", "Some error is there!!");
56 | }
57 | }, [formData, notes, setNotes, navigation, noteId]);
58 |
59 |
60 | const toggleBookmark = async () => {
61 | try {
62 | const newNotesArr = notes.map(note => {
63 | if (note.id !== noteId) return note;
64 | return { ...note, isBookmarked: !note.isBookmarked };
65 | })
66 | await storeData("notes", newNotesArr);
67 | setNotes(newNotesArr);
68 | }
69 | catch (err) {
70 | Alert.alert("Error", "Some error is there!!");
71 | }
72 | }
73 |
74 | const updatedAt = convertToXTimeAgo(note.updatedAt);
75 | const isReminderTimePassed = new Date(note.reminder?.dateTime).getTime() < new Date().getTime();
76 |
77 |
78 | return (
79 |
80 |
81 |
82 |
83 | {note.labels?.map(label => (
84 | {label}
85 | ))}
86 |
87 |
88 | {note.reminder?.dateTime && (
89 | setNoteReminderModal(true)} style={{ flexDirection: "row", alignSelf: "flex-start", margin: 10, marginTop: 0, paddingVertical: 5, paddingHorizontal: 10, backgroundColor: "#eee", borderRadius: 3, }} android_ripple={{ color: "#bbb", radius: 200 }}>
90 |
91 |
92 | {moment(note.reminder.dateTime).format("lll")}
93 |
94 |
95 | )}
96 |
97 | handleChange("text", text)}
100 | multiline={true}
101 | style={{ paddingHorizontal: 20, paddingBottom: 100, fontSize: 16, color: "#555" }}
102 | placeholder="Your note"
103 | />
104 |
105 |
106 |
107 |
108 |
109 | Edited {updatedAt}
110 |
111 |
112 |
113 | SheetManager.show("noteOptionsActionSheet")} android_ripple={{ color: "#ccc", radius: 30 }}>
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | {note.reminder?.dateTime && (
122 |
123 | )}
124 |
125 | )
126 | }
127 |
128 |
129 | UpdateNote.sharedElements = route => {
130 | return [
131 | {
132 | id: `note.${route.params.id}`,
133 | animation: "fade"
134 | }
135 | ];
136 | }
137 |
138 | export default UpdateNote
--------------------------------------------------------------------------------
/screens/index.js:
--------------------------------------------------------------------------------
1 | export { default as AddNote } from "./AddNote"
2 | export { default as Folders } from "./Folders"
3 | export { default as FolderNotes } from "./FolderNotes"
4 | export { default as Home } from "./Home"
5 | export { default as LabelsManager } from "./LabelsManager"
6 | export { default as NoteLabelsManager } from "./NoteLabelsManager"
7 | export { default as NotesSelector } from "./NotesSelector"
8 | export { default as Trash } from "./Trash"
9 | export { default as UpdateNote } from "./UpdateNote"
--------------------------------------------------------------------------------
/style/theme.js:
--------------------------------------------------------------------------------
1 | export default {
2 | PRIMARY_COLOR: '#1d9bff'
3 | };
--------------------------------------------------------------------------------
/utils/dateformat.js:
--------------------------------------------------------------------------------
1 | const convertToFullDate = (dateString) => {
2 | return new Date(dateString).toLocaleString("en-IN", { day: "numeric", month: "short", year: "numeric", hour: "numeric", minute: "numeric" });
3 | }
4 |
5 | const convertToXTimeAgo = (dateString) => {
6 | const seconds = Math.floor((new Date() - new Date(dateString)) / 1000);
7 | const mins = Math.floor(seconds / 60);
8 | const hours = Math.floor(mins / 60);
9 | const days = Math.floor(hours / 24);
10 | const months = Math.floor(days / 30);
11 | const years = Math.floor(days / 365);
12 |
13 | if (years > 0) {
14 | return `${years} ${years === 1 ? "yr" : "yrs"} ago`;
15 | }
16 | if (months > 0) {
17 | return `${months} ${months === 1 ? "month" : "months"} ago`;
18 | }
19 | if (days > 0) {
20 | return `${days} ${days === 1 ? "day" : "days"} ago`;
21 | }
22 | if (hours > 0) {
23 | return `${hours} ${hours === 1 ? "hr" : "hrs"} ago`;
24 | }
25 | if (mins > 0) {
26 | return `${mins} ${mins === 1 ? "min" : "mins"} ago`;
27 | }
28 | return "just now";
29 | }
30 |
31 | export { convertToFullDate as convertToAbsoluteDate, convertToXTimeAgo }
--------------------------------------------------------------------------------
/utils/storage.js:
--------------------------------------------------------------------------------
1 | import AsyncStorage from '@react-native-async-storage/async-storage';
2 |
3 | const storeData = async (key, value) => {
4 | try {
5 | const jsonValue = JSON.stringify(value);
6 | await AsyncStorage.setItem(key, jsonValue);
7 | return Promise.resolve();
8 | } catch (e) {
9 | return Promise.reject(e);
10 | }
11 | }
12 |
13 | const getData = async (key) => {
14 | try {
15 | const jsonValue = await AsyncStorage.getItem(key)
16 | return jsonValue != null ? JSON.parse(jsonValue) : null;
17 | } catch (e) {
18 | return Promise.reject(e);
19 | }
20 | }
21 |
22 | export { storeData, getData };
--------------------------------------------------------------------------------