├── .expo-shared
└── assets.json
├── .gitignore
├── .jshintrc
├── App.js
├── LICENSE
├── README.md
├── app.json
├── assets
├── favicon.png
├── icon.png
├── screenshots
│ ├── SS2.png
│ ├── Screenshot_20201116-210330.jpg
│ ├── Screenshot_20201116-210519.jpg
│ ├── Screenshot_20201116-212047.jpg
│ └── Screenshot_20201116-212200.jpg
└── splash.png
├── babel.config.js
├── debug.log
├── package-lock.json
├── package.json
└── src
├── components
└── Spinner.js
├── navigation
├── AppStack.js
├── AuthStack.js
├── AuthUserProvider.js
├── MoreStack.js
├── Router.js
├── TasksNavigator.js
└── ThemeProvider.js
├── screens
├── AddTask
│ ├── AddTask.js
│ └── Components
│ │ └── AddTaskHeader.js
├── EditTask.js
├── Login.js
├── More
│ ├── About.js
│ ├── More.js
│ └── Settings.js
├── Signup.js
├── TaskItem
│ └── TaskItem.js
└── TaskList
│ ├── Components
│ ├── BottomSheet.js
│ ├── FullCard.js
│ ├── Header.js
│ └── TaskCard.js
│ └── Tasks.js
├── theming
├── colors.js
└── themes.js
└── utils
├── firebase.js
└── priority.js
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p8
6 | *.p12
7 | *.key
8 | *.mobileprovision
9 | *.orig.*
10 | web-build/
11 |
12 | # macOS
13 | .DS_Store
14 |
15 | firebaseConfig.js
16 |
17 | shigoto.txt
18 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 9
3 | }
4 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Navigator from "./src/navigation/Router";
3 | import { AuthUserProvider } from "./src/navigation/AuthUserProvider";
4 | import { ThemeProvider } from "./src/navigation/ThemeProvider";
5 |
6 | const App = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | };
15 | export default App;
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Rajarshee Chatterjee
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Task Manager
2 |
3 | [](https://expo.io/)
4 | 
5 |
6 | A cross-platform task manager application built with React Native (Expo CLI)
7 |
8 | ## Download
9 |
10 | Get the app from our [releases page](https://github.com/rajarsheechatterjee/task-manager/releases).
11 |
12 | ## Screeshots
13 |
14 | 
15 |
16 | ## Features
17 |
18 | - [x] Signup and login with your account
19 | - [x] Add, Read, Edit and Delete tasks
20 | - [x] Sort and filter tasks as per your need
21 | - [x] Send emails to multiple collaborators on a task
22 | - [x] Works on both IOS and Android devices
23 | - [x] All data is stored on cloud storage
24 | - [x] Light and dark themes
25 |
26 | ## Todo (In Progress)
27 |
28 | - [ ] Build a web client to sync tasks across devices
29 |
30 | ## Get Started
31 |
32 | #### 1. Clone the Repo
33 |
34 | On the terminal run the following commands
35 |
36 | ```sh
37 | $ git clone https://github.com/rajarsheechatterjee/task-manager.git
38 |
39 | $ cd task-manager
40 |
41 | $ npm OR expo install (to install all dependencies )
42 |
43 | $ expo start
44 |
45 | ```
46 |
47 | ## License
48 |
49 | [MIT ](https://github.com/rajarsheechatterjee/task-manager
50 | /blob/master/LICENSE)
51 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "Shigoto",
4 | "slug": "shigoto",
5 | "version": "0.3.6",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "splash": {
9 | "image": "./assets/splash.png",
10 | "resizeMode": "cover",
11 | "backgroundColor": "#118086"
12 | },
13 | "updates": {
14 | "checkAutomatically": "ON_ERROR_RECOVERY",
15 | "fallbackToCacheTimeout": 0
16 | },
17 | "assetBundlePatterns": ["**/*"],
18 |
19 | "ios": {
20 | "bundleIdentifier": "com.rajarsheechatterjee.shigoto",
21 | "buildNumber": "1.0.0",
22 | "supportsTablet": true
23 | },
24 | "android": {
25 | "package": "com.rajarsheechatterjee.shigoto",
26 | "versionCode": 10,
27 | "useNextNotificationsApi": true
28 | },
29 | "web": {
30 | "favicon": "./assets/favicon.png"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/favicon.png
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/icon.png
--------------------------------------------------------------------------------
/assets/screenshots/SS2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/screenshots/SS2.png
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_20201116-210330.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/screenshots/Screenshot_20201116-210330.jpg
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_20201116-210519.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/screenshots/Screenshot_20201116-210519.jpg
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_20201116-212047.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/screenshots/Screenshot_20201116-212047.jpg
--------------------------------------------------------------------------------
/assets/screenshots/Screenshot_20201116-212200.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/screenshots/Screenshot_20201116-212200.jpg
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rajarsheechatterjee/task-manager/0be042404faf1995ec4625b8b09694a1fbf44efa/assets/splash.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/debug.log:
--------------------------------------------------------------------------------
1 | [1014/170257.453:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
2 | [1015/202736.032:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
3 | [1018/195935.964:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
4 | [1019/170508.099:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
5 | [1020/072058.242:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
6 | [1022/205957.920:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
7 | [1102/202839.345:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
8 | [1110/163424.567:ERROR:directory_reader_win.cc(43)] FindFirstFile: The system cannot find the path specified. (0x3)
9 |
--------------------------------------------------------------------------------
/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 | },
10 | "dependencies": {
11 | "@expo/vector-icons": "^10.2.1",
12 | "@react-native-async-storage/async-storage": "^1.13.1",
13 | "@react-native-community/async-storage": "^1.12.1",
14 | "@react-native-community/datetimepicker": "^3.0.0",
15 | "@react-native-community/masked-view": "^0.1.10",
16 | "@react-native-community/netinfo": "^5.9.7",
17 | "@react-native-firebase/app": "^8.4.2",
18 | "@react-navigation/bottom-tabs": "^5.9.0",
19 | "@react-navigation/material-bottom-tabs": "^5.2.17",
20 | "@react-navigation/native": "^5.8.9",
21 | "@react-navigation/stack": "^5.12.6",
22 | "expo": "^39.0.0",
23 | "expo-mail-composer": "~8.4.0",
24 | "expo-notifications": "~0.7.2",
25 | "expo-sms": "~8.3.1",
26 | "expo-status-bar": "~1.0.2",
27 | "expo-updates": "~0.3.3",
28 | "firebase": "^7.9.0",
29 | "moment": "^2.28.0",
30 | "react": "16.13.1",
31 | "react-dom": "16.13.1",
32 | "react-native": "https://github.com/expo/react-native/archive/sdk-39.0.0.tar.gz",
33 | "react-native-elements": "^2.3.2",
34 | "react-native-gesture-handler": "~1.7.0",
35 | "react-native-material-ripple": "^0.9.1",
36 | "react-native-modal-datetime-picker": "^8.9.3",
37 | "react-native-paper": "^4.2.0",
38 | "react-native-reanimated": "~1.13.0",
39 | "react-native-safe-area-context": "3.1.4",
40 | "react-native-screens": "^2.15.0",
41 | "react-native-svg": "^12.1.0",
42 | "react-native-tab-view": "^2.15.2",
43 | "react-native-vector-icons": "^7.1.0",
44 | "react-native-web": "~0.13.7",
45 | "react-native-windows": "^1.0.0",
46 | "react-navigation-collapsible": "^5.8.1",
47 | "react-navigation-transitions": "^1.0.12",
48 | "rn-sliding-up-panel": "^2.4.3"
49 | },
50 | "devDependencies": {
51 | "@babel/core": "^7.8.6",
52 | "babel-preset-expo": "^8.3.0"
53 | },
54 | "private": true
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ActivityIndicator, StyleSheet, View, StatusBar } from "react-native";
3 |
4 | import { ThemeContext } from "../navigation/ThemeProvider";
5 |
6 | export default function Spinner() {
7 | const { theme } = useContext(ThemeContext);
8 | StatusBar.setBarStyle("light-content");
9 |
10 | return (
11 |
17 |
18 |
19 | );
20 | }
21 |
22 | const styles = StyleSheet.create({
23 | container: {
24 | flex: 1,
25 | justifyContent: "center",
26 | alignItems: "center",
27 | },
28 | });
29 |
--------------------------------------------------------------------------------
/src/navigation/AppStack.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createStackNavigator } from "@react-navigation/stack";
3 |
4 | import TaskList from "./TasksNavigator";
5 | import AddTask from "../screens/AddTask/AddTask";
6 | import More from "./MoreStack";
7 |
8 | const Stack = createStackNavigator();
9 |
10 | const AppStack = () => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default AppStack;
21 |
--------------------------------------------------------------------------------
/src/navigation/AuthStack.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createStackNavigator } from "@react-navigation/stack";
3 |
4 | import Login from "../screens/Login";
5 | import Signup from "../screens/Signup";
6 | const Stack = createStackNavigator();
7 |
8 | const AuthStack = () => {
9 | return (
10 |
14 |
15 |
16 |
17 | );
18 | };
19 |
20 | export default AuthStack;
21 |
--------------------------------------------------------------------------------
/src/navigation/AuthUserProvider.js:
--------------------------------------------------------------------------------
1 | import React, { useState, createContext } from "react";
2 |
3 | export const AuthUserContext = createContext({});
4 |
5 | export const AuthUserProvider = ({ children }) => {
6 | const [user, setUser] = useState(null);
7 |
8 | return (
9 |
10 | {children}
11 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/src/navigation/MoreStack.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "react-native-gesture-handler";
3 | import { createStackNavigator } from "@react-navigation/stack";
4 | import Colors from "../theming/colors";
5 |
6 | import More from "../screens/More/More";
7 | import About from "../screens/More/About";
8 | import Settings from "../screens/More/Settings";
9 |
10 | const Stack = createStackNavigator();
11 |
12 | function StackNavigator() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | export default StackNavigator;
23 |
--------------------------------------------------------------------------------
/src/navigation/Router.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useContext } from "react";
2 | import { NavigationContainer } from "@react-navigation/native";
3 |
4 | import AppStack from "./AppStack";
5 | import AuthStack from "./AuthStack";
6 |
7 | import { AuthUserContext } from "./AuthUserProvider";
8 | import { auth } from "../utils/firebase";
9 |
10 | import Spinner from "../components/Spinner";
11 |
12 | const Router = () => {
13 | const { user, setUser } = useContext(AuthUserContext);
14 | const [isLoading, setIsLoading] = useState(true);
15 |
16 | useEffect(() => {
17 | const unsubscribeAuth = auth.onAuthStateChanged(async (authUser) => {
18 | try {
19 | await (authUser ? setUser(authUser) : setUser(null));
20 | setIsLoading(false);
21 | } catch (error) {
22 | console.log(error);
23 | }
24 | });
25 |
26 | return unsubscribeAuth;
27 | }, []);
28 |
29 | if (isLoading) {
30 | return ;
31 | }
32 |
33 | return (
34 |
35 | {user ? : }
36 |
37 | );
38 | };
39 |
40 | export default Router;
41 |
--------------------------------------------------------------------------------
/src/navigation/TasksNavigator.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "react-native-gesture-handler";
3 | import { createStackNavigator } from "@react-navigation/stack";
4 | import Colors from "../theming/colors";
5 |
6 | import TaskList from "../screens/TaskList/Tasks";
7 | import TaskItem from "../screens/TaskItem/TaskItem";
8 | import EditTask from "../screens/EditTask";
9 |
10 | const Stack = createStackNavigator();
11 |
12 | function StackNavigator() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | export default StackNavigator;
23 |
--------------------------------------------------------------------------------
/src/navigation/ThemeProvider.js:
--------------------------------------------------------------------------------
1 | import React, { useState, createContext } from "react";
2 | import { lightTheme, darkTheme } from "../theming/themes";
3 | import AsyncStorage from "@react-native-async-storage/async-storage";
4 | export const ThemeContext = createContext({});
5 |
6 | export const ThemeProvider = ({ children }) => {
7 | const [darkMode, setDarkMode] = useState(false);
8 |
9 | AsyncStorage.getItem("@theme").then((value) =>
10 | setDarkMode(JSON.parse(value))
11 | );
12 |
13 | const handleToggleDarkMode = (previousVal) => {
14 | setDarkMode(!previousVal);
15 | AsyncStorage.setItem("@theme", JSON.stringify(!previousVal));
16 | };
17 |
18 | return (
19 | handleToggleDarkMode(darkMode),
22 | theme: darkMode ? darkTheme : lightTheme,
23 | }}
24 | >
25 | {children}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/screens/AddTask/AddTask.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import {
3 | StyleSheet,
4 | Text,
5 | View,
6 | TextInput,
7 | ToastAndroid,
8 | Animated,
9 | } from "react-native";
10 | import {
11 | TouchableRipple,
12 | Portal,
13 | Dialog,
14 | Provider,
15 | Button,
16 | TextInput as PaperInput,
17 | Chip,
18 | Switch,
19 | RadioButton,
20 | } from "react-native-paper";
21 | import NetInfo from "@react-native-community/netinfo";
22 |
23 | import moment from "moment";
24 | import BottomSheet from "rn-sliding-up-panel";
25 | import DateTimePickerModal from "react-native-modal-datetime-picker";
26 |
27 | import Appbar from "./Components/AddTaskHeader";
28 | import { addTask } from "../../utils/firebase";
29 | import Colors from "../../theming/colors";
30 | import * as MailComposer from "expo-mail-composer";
31 |
32 | import { ThemeContext } from "../../navigation/ThemeProvider";
33 |
34 | export default function AddTask({ navigation }) {
35 | const { theme } = useContext(ThemeContext);
36 |
37 | const [newTaskTitle, setNewTaskTitle] = useState("");
38 | const [newTaskContent, setNewTaskContent] = useState("");
39 | const [priority, setPriority] = useState(2);
40 |
41 | const [isVisible, setIsVisible] = useState(false);
42 | const [chosenDate, setChosenDate] = useState("");
43 |
44 | const [dialogVisible, setDialogVisible] = useState(false);
45 | const showDialog = () => setDialogVisible(true);
46 | const hideDialog = () => {
47 | setDialogVisible(false);
48 | setError(false);
49 | };
50 |
51 | const [email, setEmail] = useState();
52 | const [emails, setEmails] = useState([]);
53 | const [sendEmail, setSendEmail] = useState(false);
54 | const [error, setError] = useState(false);
55 | const onToggleSwitch = () => setSendEmail(!sendEmail);
56 | const addEmail = () => {
57 | const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
58 |
59 | if (re.test(String(email).toLowerCase())) {
60 | if (emails.length > 0) {
61 | const index = emails.indexOf(email);
62 | if (index > -1) {
63 | setError("Email already added");
64 | } else {
65 | setEmails((emails) => emails.concat(email));
66 | setEmail("");
67 | setDialogVisible(false);
68 | setError("");
69 | }
70 | } else {
71 | setEmails((emails) => emails.concat(email));
72 | setEmail("");
73 | setDialogVisible(false);
74 | setError("");
75 | }
76 | } else {
77 | setError("Enter a valid email");
78 | }
79 | };
80 |
81 | const handleAddTask = async () => {
82 | if (newTaskTitle === "") {
83 | ToastAndroid.show("Task title is empty", ToastAndroid.SHORT);
84 | } else {
85 | if (sendEmail && emails.length > 0) {
86 | await MailComposer.composeAsync({
87 | recipients: emails,
88 | subject: newTaskTitle,
89 | body: newTaskContent,
90 | });
91 | }
92 |
93 | await addTask(
94 | navigation,
95 | newTaskTitle,
96 | chosenDate,
97 | newTaskContent,
98 | priority,
99 | emails
100 | );
101 |
102 | ToastAndroid.show("Task Added", ToastAndroid.SHORT);
103 | clearFields();
104 |
105 | NetInfo.fetch().then((state) => {
106 | !state.isConnected &&
107 | ToastAndroid.show(
108 | "Cannot add task at this moment. Please check your internet connection",
109 | ToastAndroid.SHORT
110 | );
111 | });
112 | }
113 | };
114 |
115 | const clearFields = () => {
116 | setNewTaskTitle("");
117 | setNewTaskContent("");
118 | setChosenDate("");
119 | };
120 |
121 | // Date & time picker
122 | const handlePicker = (date) => {
123 | setChosenDate(date);
124 | setIsVisible(false);
125 | };
126 | const showPicker = () => setIsVisible(true);
127 | const hidePicker = () => setIsVisible(false);
128 |
129 | return (
130 |
131 | _panel.show()}
134 | showPicker={showPicker}
135 | handleAddTask={handleAddTask}
136 | newTaskTitle={newTaskTitle}
137 | clearFields={clearFields}
138 | theme={theme}
139 | showModal={showDialog}
140 | />
141 |
147 |
148 |
204 |
205 |
206 | setNewTaskTitle(text)}
211 | defaultValue={newTaskTitle}
212 | placeholderTextColor={theme.subTextColor}
213 | />
214 |
221 | {chosenDate !== ""
222 | ? moment(chosenDate).calendar()
223 | : "Reminder Time"}
224 |
225 | setNewTaskContent(text)}
231 | placeholder="Content"
232 | defaultValue={newTaskContent}
233 | multiline={true}
234 | placeholderTextColor={theme.subTextColor}
235 | />
236 |
249 | {emails.length > 0 ? (
250 | emails.map((item) => (
251 |
260 | {item}
261 |
262 | ))
263 | ) : (
264 |
274 | Add Collaborators
275 |
276 | )}
277 |
278 |
279 |
286 |
287 | (_panel = c)}
290 | draggableRange={{ top: 280, bottom: 55 }}
291 | snappingPoints={[55, 120, 280]}
292 | showBackdrop={false}
293 | >
294 |
300 |
301 |
307 | Collaborators
308 |
309 | setSendEmail(!sendEmail)}
315 | >
316 | <>
317 |
323 | Send Email to Collaborators
324 |
325 |
330 | >
331 |
332 |
333 |
339 | Priority
340 |
341 | setPriority(newValue)}
343 | value={priority}
344 | >
345 |
353 |
361 |
369 |
370 |
371 |
372 |
373 | );
374 | }
375 |
376 | const styles = StyleSheet.create({
377 | mainContainer: {
378 | flex: 1,
379 | padding: 10,
380 | },
381 | dateInput: { marginHorizontal: 10, paddingTop: 5 },
382 | titleInput: {
383 | fontSize: 30,
384 | fontWeight: "bold",
385 | paddingVertical: 15,
386 | marginHorizontal: 10,
387 | borderBottomWidth: 1.2,
388 | borderBottomColor: "#E8E8E8",
389 | },
390 | contentInput: {
391 | paddingTop: 10,
392 | marginHorizontal: 10,
393 | fontSize: 18,
394 | lineHeight: 29,
395 | },
396 | checkBox: {
397 | borderRadius: 10,
398 | borderWidth: 0,
399 | },
400 | bottomSheetContainer: {
401 | flex: 1,
402 | paddingTop: 20,
403 | paddingBottom: 8,
404 | borderTopLeftRadius: 15,
405 | borderTopRightRadius: 15,
406 | elevation: 10,
407 | },
408 | priorityHeading: {
409 | fontWeight: "bold",
410 | fontSize: 15,
411 | color: Colors.accentColor,
412 | paddingHorizontal: 20,
413 | // paddingBottom: 5,
414 | },
415 | setPriority: {
416 | flexDirection: "row",
417 | justifyContent: "space-between",
418 | alignItems: "center",
419 | paddingHorizontal: 20,
420 | },
421 | indicator: {
422 | position: "absolute",
423 | justifyContent: "center",
424 | alignSelf: "center",
425 | width: 40,
426 | height: 5,
427 | backgroundColor: "rgba(0,0,0,0.75)",
428 | borderRadius: 25,
429 | top: 7,
430 | },
431 | });
432 |
--------------------------------------------------------------------------------
/src/screens/AddTask/Components/AddTaskHeader.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Appbar } from "react-native-paper";
3 |
4 | const AddTaskHeader = ({
5 | navigation,
6 | showPicker,
7 | handleAddTask,
8 | newTaskTitle,
9 | clearFields,
10 | theme,
11 | showModal,
12 | }) => {
13 | return (
14 |
15 | {
17 | navigation.goBack();
18 | clearFields();
19 | }}
20 | />
21 |
22 |
23 |
24 |
29 |
30 | );
31 | };
32 |
33 | export default AddTaskHeader;
34 |
--------------------------------------------------------------------------------
/src/screens/EditTask.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import {
3 | StyleSheet,
4 | Text,
5 | View,
6 | TextInput,
7 | ToastAndroid,
8 | Animated,
9 | } from "react-native";
10 | import {
11 | Appbar,
12 | TouchableRipple,
13 | Switch,
14 | Portal,
15 | Dialog,
16 | Provider,
17 | Button,
18 | TextInput as PaperInput,
19 | Chip,
20 | RadioButton,
21 | } from "react-native-paper";
22 | import BottomSheet from "rn-sliding-up-panel";
23 | import * as MailComposer from "expo-mail-composer";
24 |
25 | import { ThemeContext } from "../navigation/ThemeProvider";
26 |
27 | import { updateTask } from "../utils/firebase";
28 |
29 | import moment from "moment";
30 | import DateTimePickerModal from "react-native-modal-datetime-picker";
31 |
32 | export default function EditTask({ route, navigation }) {
33 | const {
34 | id,
35 | taskTitle,
36 | taskTime,
37 | taskContent,
38 | isCompleted,
39 | priorityIs,
40 | collaborators,
41 | } = route.params;
42 |
43 | const { theme } = useContext(ThemeContext);
44 |
45 | const [newTaskTitle, setNewTaskTitle] = useState(taskTitle);
46 | const [newTaskContent, setNewTaskContent] = useState(taskContent);
47 | const [isChecked, setIsChecked] = useState(isCompleted);
48 |
49 | const [isVisible, setIsVisible] = useState(false);
50 | const [chosenDate, setChosenDate] = useState(taskTime);
51 |
52 | const [priority, setPriority] = useState(priorityIs);
53 |
54 | const handleIsCompleted = (isChecked) => setIsChecked(!isChecked);
55 |
56 | // Date & Time Picker
57 | const handlePicker = (date) => {
58 | setChosenDate(date);
59 | setIsVisible(false);
60 | };
61 | const showPicker = () => setIsVisible(true);
62 | const hidePicker = () => setIsVisible(false);
63 |
64 | // Collaborators Dialog
65 | const [dialogVisible, setDialogVisible] = useState(false);
66 | const [error, setError] = useState(false);
67 | const showDialog = () => setDialogVisible(true);
68 | const hideDialog = () => {
69 | setDialogVisible(false);
70 | setError(false);
71 | };
72 |
73 | // Validate and add email
74 | const [email, setEmail] = useState();
75 | const [emails, setEmails] = useState(collaborators);
76 | const [sendEmail, setSendEmail] = useState(false);
77 | const onToggleSwitch = () => setSendEmail(!sendEmail);
78 | const addEmail = () => {
79 | const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
80 |
81 | if (re.test(String(email).toLowerCase())) {
82 | if (emails.length > 0) {
83 | const index = emails.indexOf(email);
84 | if (index > -1) {
85 | setError("Email already added");
86 | } else {
87 | setEmails((emails) => emails.concat(email));
88 | setEmail("");
89 | setDialogVisible(false);
90 | setError("");
91 | }
92 | } else {
93 | setEmails((emails) => emails.concat(email));
94 | setEmail("");
95 | setDialogVisible(false);
96 | setError("");
97 | }
98 | } else {
99 | setError("Enter a valid email");
100 | }
101 | };
102 |
103 | const handleEditTask = async () => {
104 | if (newTaskTitle === "") {
105 | ToastAndroid.show("Task title is empty", ToastAndroid.SHORT);
106 | } else {
107 | if (sendEmail && emails.length > 0) {
108 | await MailComposer.composeAsync({
109 | recipients: emails,
110 | subject: "[Updated] " + newTaskTitle,
111 | body: newTaskContent,
112 | });
113 | }
114 |
115 | await updateTask(
116 | navigation,
117 | id,
118 | newTaskTitle,
119 | chosenDate,
120 | newTaskContent,
121 | priority,
122 | emails,
123 | isChecked
124 | );
125 | ToastAndroid.show("Task Updated", ToastAndroid.SHORT);
126 | }
127 | };
128 |
129 | return (
130 |
131 |
134 | {
136 | navigation.goBack();
137 | }}
138 | />
139 |
140 |
141 | {/* _panel.show()}
144 | /> */}
145 |
149 |
150 |
155 |
156 |
162 |
163 |
219 |
220 |
221 | setNewTaskTitle(text)}
226 | defaultValue={newTaskTitle}
227 | placeholderTextColor={theme.subTextColor}
228 | />
229 |
236 | {chosenDate !== ""
237 | ? moment(chosenDate).calendar()
238 | : "Reminder Time"}
239 |
240 | setNewTaskContent(text)}
246 | placeholder="Content"
247 | defaultValue={newTaskContent}
248 | multiline={true}
249 | spellCheck={false}
250 | placeholderTextColor={theme.subTextColor}
251 | />
252 |
253 |
266 | {emails.length > 0 ? (
267 | emails.map((item) => (
268 |
275 | {item}
276 |
277 | ))
278 | ) : (
279 |
287 | Add Collaborators
288 |
289 | )}
290 |
291 |
298 |
299 | (_panel = c)}
302 | draggableRange={{ top: 370, bottom: 60 }}
303 | snappingPoints={[60, 220, 370]}
304 | showBackdrop={false}
305 | >
306 |
312 |
313 |
319 | Collaborators
320 |
321 | setSendEmail(!sendEmail)}
327 | >
328 | <>
329 |
335 | Send updated email to collaborators
336 |
337 |
342 | >
343 |
344 |
350 | Completed
351 |
352 | handleIsCompleted(isChecked)}
358 | >
359 | <>
360 |
363 | Set completed
364 |
365 |
368 | handleIsCompleted(isChecked)
369 | }
370 | color={theme.colorAccentSecondary}
371 | />
372 | >
373 |
374 |
380 | Priority
381 |
382 | setPriority(newValue)}
384 | value={priority}
385 | >
386 |
394 |
402 |
410 |
411 |
412 |
413 |
414 | );
415 | }
416 |
417 | const styles = StyleSheet.create({
418 | mainContainer: {
419 | flex: 1,
420 | padding: 10,
421 | },
422 | dateInput: { marginHorizontal: 10, paddingTop: 5 },
423 | titleInput: {
424 | fontSize: 30,
425 | fontWeight: "bold",
426 | paddingVertical: 15,
427 | marginHorizontal: 10,
428 | borderBottomWidth: 1.2,
429 | borderBottomColor: "#E8E8E8",
430 | },
431 | contentInput: {
432 | paddingTop: 10,
433 | marginHorizontal: 10,
434 | fontSize: 18,
435 | lineHeight: 29,
436 | },
437 | checkBox: {
438 | borderRadius: 10,
439 | borderWidth: 0,
440 | },
441 | bottomSheetContainer: {
442 | flex: 1,
443 | paddingTop: 20,
444 | paddingBottom: 8,
445 | borderTopLeftRadius: 15,
446 | borderTopRightRadius: 15,
447 | elevation: 10,
448 | },
449 | priorityHeading: {
450 | fontWeight: "bold",
451 | fontSize: 15,
452 | paddingHorizontal: 20,
453 | paddingVertical: 5,
454 | },
455 | setPriority: {
456 | flexDirection: "row",
457 | justifyContent: "space-between",
458 | alignItems: "center",
459 | paddingHorizontal: 20,
460 | },
461 | indicator: {
462 | position: "absolute",
463 | justifyContent: "center",
464 | alignSelf: "center",
465 | width: 40,
466 | height: 5,
467 | backgroundColor: "rgba(0,0,0,0.75)",
468 | borderRadius: 25,
469 | top: 7,
470 | },
471 | setCompleted: {
472 | flexDirection: "row",
473 | justifyContent: "space-between",
474 | alignItems: "center",
475 | paddingHorizontal: 20,
476 | paddingVertical: 10,
477 | },
478 | });
479 |
--------------------------------------------------------------------------------
/src/screens/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | StyleSheet,
4 | Text,
5 | View,
6 | TextInput,
7 | TouchableHighlight,
8 | ActivityIndicator,
9 | } from "react-native";
10 | import { ScrollView } from "react-native-gesture-handler";
11 | import Icon from "react-native-vector-icons/FontAwesome";
12 | import Colors from "../theming/colors";
13 |
14 | import { loginUser } from "../utils/firebase";
15 |
16 | export default function LoginScreen({ navigation }) {
17 | const [email, setEmail] = useState("");
18 | const [password, setPassword] = useState("");
19 | const [loading, setLoading] = useState(false);
20 |
21 | const handleLogin = () => {
22 | loginUser(email, password);
23 | setLoading(true);
24 | setTimeout(function () {
25 | setLoading(false);
26 | }, 2000);
27 | };
28 |
29 | return (
30 |
31 |
32 |
33 | Login
34 | E-mail
35 | setEmail(email)}
46 | />
47 | Password
48 | setPassword(password)}
59 | />
60 | navigation.navigate("Signup")}
63 | >
64 | Dont have an account? Create one
65 |
66 |
75 |
80 |
81 |
82 |
83 |
87 |
93 |
94 |
95 |
96 |
97 | );
98 | }
99 |
100 | const styles = StyleSheet.create({
101 | wrapper: {
102 | display: "flex",
103 | flex: 1,
104 | backgroundColor: Colors.accentColor,
105 | },
106 | scrollViewWrapper: {
107 | marginTop: 70,
108 | flex: 1,
109 | },
110 | avoidView: {
111 | paddingLeft: 30,
112 | paddingRight: 30,
113 | paddingTop: 20,
114 | flex: 1,
115 | },
116 | loginHeader: {
117 | fontSize: 28,
118 | color: "white",
119 | fontWeight: "300",
120 | marginBottom: 40,
121 | },
122 | labelText: {
123 | fontWeight: "700",
124 | marginBottom: 10,
125 | fontSize: 14,
126 | color: "white",
127 | },
128 | buttonWrapper: {
129 | alignItems: "flex-end",
130 | right: 20,
131 | bottom: 20,
132 | paddingTop: 0,
133 | },
134 | button: {
135 | alignItems: "center",
136 | justifyContent: "center",
137 | borderRadius: 50,
138 | width: 60,
139 | height: 60,
140 | backgroundColor: "white",
141 | },
142 | icon: {
143 | marginRight: -2,
144 | marginTop: -2,
145 | },
146 | navigateText: {
147 | color: "white",
148 | fontSize: 15,
149 | textAlign: "center",
150 | },
151 | });
152 |
--------------------------------------------------------------------------------
/src/screens/More/About.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { Clipboard, ToastAndroid, Alert } from "react-native";
3 | import { List, Divider, Appbar } from "react-native-paper";
4 | import * as Linking from "expo-linking";
5 | import * as Updates from "expo-updates";
6 | import { ThemeContext } from "../../navigation/ThemeProvider";
7 |
8 | const AboutScreen = ({ navigation }) => {
9 | const { theme } = useContext(ThemeContext);
10 |
11 | const checkForUpdates = async () => {
12 | ToastAndroid.show("Searching for updates...", ToastAndroid.SHORT);
13 | const update = await Updates.checkForUpdateAsync();
14 | if (update.isAvailable) {
15 | Alert.alert(
16 | "New Version Available",
17 | "A new version is available for download",
18 | [
19 | {
20 | text: "Cancel",
21 | onPress: () => console.log("Cancel Pressed"),
22 | style: "cancel",
23 | },
24 | {
25 | text: "Download",
26 | onPress: async () => {
27 | await Updates.fetchUpdateAsync();
28 | await Updates.reloadAsync();
29 | },
30 | },
31 | ],
32 | { cancelable: false }
33 | );
34 | } else {
35 | ToastAndroid.show("No new updates available", ToastAndroid.SHORT);
36 | }
37 | };
38 |
39 | const handleCopyVersion = () => {
40 | Clipboard.setString("Version: Stable 0.3.6");
41 | ToastAndroid.show(
42 | "Copied to clipboard: Version: Stable 0.3.6",
43 | ToastAndroid.SHORT
44 | );
45 | };
46 |
47 | return (
48 | <>
49 |
52 | {
54 | navigation.goBack();
55 | }}
56 | />
57 |
58 |
59 |
67 |
74 |
80 |
85 |
89 | Linking.openURL(
90 | "https://github.com/rajarsheechatterjee/shigoto/commits/master"
91 | )
92 | }
93 | />
94 |
95 |
96 |
102 | Linking.openURL(
103 | "https://github.com/rajarsheechatterjee/shigoto"
104 | )
105 | }
106 | />
107 |
108 | >
109 | );
110 | };
111 |
112 | export default AboutScreen;
113 |
--------------------------------------------------------------------------------
/src/screens/More/More.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { Appbar, List } from "react-native-paper";
3 |
4 | import { ThemeContext } from "../../navigation/ThemeProvider";
5 |
6 | const MoreScreen = ({ navigation }) => {
7 | const { theme } = useContext(ThemeContext);
8 |
9 | return (
10 | <>
11 |
14 | {
16 | navigation.goBack();
17 | }}
18 | />
19 |
20 |
21 |
29 | (
33 |
37 | )}
38 | onPress={() => navigation.navigate("Settings")}
39 | />
40 | (
44 |
48 | )}
49 | onPress={() => navigation.navigate("About")}
50 | />
51 |
52 | >
53 | );
54 | };
55 |
56 | export default MoreScreen;
57 |
--------------------------------------------------------------------------------
/src/screens/More/Settings.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import { List, Divider, Appbar, Checkbox } from "react-native-paper";
3 | import AsyncStorage from "@react-native-async-storage/async-storage";
4 | import { addSampleData, currentUserEmail, logout } from "../../utils/firebase";
5 |
6 | import { ThemeContext } from "../../navigation/ThemeProvider";
7 |
8 | const SettingsScreen = ({ navigation }) => {
9 | const { toggleDarkMode, theme } = useContext(ThemeContext);
10 |
11 | const [checked, setChecked] = useState();
12 |
13 | AsyncStorage.getItem("@theme").then((value) =>
14 | setChecked(JSON.parse(value))
15 | );
16 |
17 | const handleCheckbox = () => {
18 | toggleDarkMode();
19 | setChecked(!checked);
20 | };
21 |
22 | const email = currentUserEmail();
23 |
24 | return (
25 | <>
26 |
29 | {
31 | navigation.goBack();
32 | }}
33 | />
34 |
35 |
36 |
43 |
50 | General
51 |
52 | (
58 |
62 | )}
63 | />
64 | (
69 |
73 | )}
74 | onPress={() => addSampleData(navigation)}
75 | />
76 |
77 |
84 | Theme
85 |
86 | handleCheckbox()}
90 | right={() => (
91 | handleCheckbox()}
94 | color={theme.colorAccentSecondary}
95 | />
96 | )}
97 | style={{ paddingRight: 20 }}
98 | />
99 |
100 |
107 | Account
108 |
109 | (
114 |
118 | )}
119 | />
120 | (
125 |
129 | )}
130 | onPress={() => logout()}
131 | />
132 |
133 | >
134 | );
135 | };
136 |
137 | export default SettingsScreen;
138 |
--------------------------------------------------------------------------------
/src/screens/Signup.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from "react";
2 | import {
3 | StyleSheet,
4 | Text,
5 | View,
6 | TextInput,
7 | TouchableHighlight,
8 | ActivityIndicator,
9 | } from "react-native";
10 | import { ScrollView } from "react-native-gesture-handler";
11 | import { useFocusEffect } from "@react-navigation/native";
12 |
13 | import Icon from "react-native-vector-icons/FontAwesome";
14 | import Colors from "../theming/colors";
15 |
16 | import { isLoggedIn, signupUser } from "../utils/firebase";
17 |
18 | export default function SignupScreen({ navigation }) {
19 | const [email, setEmail] = useState("");
20 | const [password, setPassword] = useState("");
21 | const [loading, setLoading] = useState(false);
22 |
23 | const handleSignup = () => {
24 | signupUser(email, password);
25 | setLoading(true);
26 | setTimeout(function () {
27 | setLoading(false);
28 | }, 3000);
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 | Sign Up
36 | E-mail
37 | setEmail(email)}
48 | />
49 | Password
50 | setPassword(password)}
61 | />
62 | navigation.navigate("Login")}
65 | >
66 | Already have an account? Login
67 |
68 |
77 |
82 |
83 |
84 |
85 |
89 |
95 |
96 |
97 |
98 |
99 | );
100 | }
101 |
102 | const styles = StyleSheet.create({
103 | wrapper: {
104 | display: "flex",
105 | flex: 1,
106 | backgroundColor: Colors.accentColor,
107 | },
108 | scrollViewWrapper: {
109 | marginTop: 70,
110 | flex: 1,
111 | },
112 | avoidView: {
113 | paddingLeft: 30,
114 | paddingRight: 30,
115 | paddingTop: 20,
116 | flex: 1,
117 | },
118 | loginHeader: {
119 | fontSize: 28,
120 | color: "white",
121 | fontWeight: "300",
122 | marginBottom: 40,
123 | },
124 | labelText: {
125 | fontWeight: "700",
126 | marginBottom: 10,
127 | fontSize: 14,
128 | color: "white",
129 | },
130 | buttonWrapper: {
131 | alignItems: "flex-end",
132 | right: 20,
133 | bottom: 20,
134 | paddingTop: 0,
135 | },
136 | button: {
137 | alignItems: "center",
138 | justifyContent: "center",
139 | borderRadius: 50,
140 | width: 60,
141 | height: 60,
142 | backgroundColor: "white",
143 | },
144 | icon: {
145 | marginRight: -2,
146 | marginTop: -2,
147 | },
148 | navigateText: {
149 | color: "white",
150 | fontSize: 15,
151 | textAlign: "center",
152 | },
153 | });
154 |
--------------------------------------------------------------------------------
/src/screens/TaskItem/TaskItem.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import { Text, View, StyleSheet, Clipboard, ToastAndroid } from "react-native";
3 | import {
4 | FAB,
5 | Portal,
6 | Provider,
7 | Appbar,
8 | Chip,
9 | Button,
10 | Dialog,
11 | Paragraph,
12 | } from "react-native-paper";
13 | import moment from "moment";
14 | import * as MailComposer from "expo-mail-composer";
15 |
16 | import { deleteTask } from "../../utils/firebase";
17 |
18 | import { ThemeContext } from "../../navigation/ThemeProvider";
19 |
20 | export default function TaskItem({ route, navigation }) {
21 | const {
22 | id,
23 | taskTitle,
24 | taskContent,
25 | createdAt,
26 | taskTime,
27 | collaborators,
28 | isCompleted,
29 | isUpdated,
30 | } = route.params;
31 |
32 | const { theme } = useContext(ThemeContext);
33 |
34 | // FAB
35 | const [open, setOpen] = useState(false);
36 | const onStateChange = () => setOpen(!open);
37 |
38 | const handleDivider = () => {
39 | if (taskTime !== "" || taskContent !== "" || collaborators.length > 0) {
40 | return { borderBottomWidth: 1, borderBottomColor: "#E8E8E8" };
41 | }
42 | };
43 |
44 | /**
45 | * Copy task details to clipboard
46 | */
47 | const handleCopy = () => {
48 | Clipboard.setString(
49 | `Title: ${taskTitle}, Content: ${taskContent}, Due At: ${taskTime}`
50 | );
51 | ToastAndroid.show("Copied task to clipboard", ToastAndroid.SHORT);
52 | };
53 |
54 | /**
55 | * Delete Dialog
56 | */
57 | const [visible, setVisible] = useState(false);
58 | const showDialog = () => setVisible(true);
59 | const hideDialog = () => setVisible(false);
60 | const handleDelete = (id) => {
61 | deleteTask(navigation, id);
62 | hideDialog();
63 | };
64 |
65 | return (
66 |
67 |
70 | {
72 | navigation.goBack();
73 | }}
74 | />
75 |
76 |
77 |
78 |
108 |
109 |
110 |
120 |
121 |
132 | {taskTitle}
133 |
134 |
135 | {taskTime !== "" && (
136 |
137 |
143 | Due {moment(taskTime.toDate()).calendar()}
144 |
145 |
146 | )}
147 | {taskContent !== "" && (
148 |
149 | 0 && {
157 | paddingBottom: 5,
158 | },
159 | ]}
160 | >
161 | {taskContent}
162 |
163 |
164 | )}
165 | {/*
166 |
167 | {isUpdated ? "Updated on " : "Created on"}
168 | {moment(createdAt.toDate()).calendar()}
169 |
170 | */}
171 | {collaborators.length > 0 && (
172 |
182 | {collaborators.map((item) => (
183 |
190 | {item}
191 |
192 | ))}
193 |
194 | )}
195 |
196 |
197 | {
210 | MailComposer.composeAsync({
211 | recipients: collaborators,
212 | subject: taskTitle,
213 | body: taskTime,
214 | });
215 | },
216 | style: {
217 | backgroundColor: theme.backgroundColor,
218 | },
219 | },
220 | // {
221 | // icon: "share-variant",
222 | // color: theme.colorAccentSecondary,
223 | // label: "Share",
224 | // onPress: () => handleCopy(),
225 | // style: {
226 | // backgroundColor: theme.backgroundColor,
227 | // },
228 | // },
229 | {
230 | icon: "trash-can-outline",
231 | color: theme.colorAccentSecondary,
232 | label: "Delete",
233 | onPress: () => showDialog(),
234 | style: {
235 | backgroundColor: theme.backgroundColor,
236 | },
237 | },
238 | {
239 | icon: "pencil",
240 | label: "Edit",
241 | color: theme.colorAccentSecondary,
242 | onPress: () =>
243 | navigation.navigate(
244 | "EditTask",
245 | route.params
246 | ),
247 | style: {
248 | backgroundColor: theme.backgroundColor,
249 | },
250 | },
251 | ]}
252 | onStateChange={onStateChange}
253 | />
254 |
255 |
256 |
257 | );
258 | }
259 |
260 | const styles = StyleSheet.create({
261 | mainContainer: {
262 | marginTop: 20,
263 | margin: 10,
264 | paddingHorizontal: 20,
265 | elevation: 2,
266 | paddingTop: 15,
267 | borderRadius: 15,
268 | },
269 | taskTitle: {
270 | fontWeight: "700",
271 | fontSize: 36,
272 | paddingTop: 5,
273 | paddingBottom: 10,
274 | },
275 | taskDate: {
276 | paddingVertical: 10,
277 | fontSize: 14,
278 | },
279 | createdDate: {
280 | marginTop: 5,
281 | fontSize: 14,
282 | },
283 | taskContent: {
284 | fontSize: 18,
285 | lineHeight: 29,
286 | paddingBottom: 15,
287 | },
288 | });
289 |
--------------------------------------------------------------------------------
/src/screens/TaskList/Components/BottomSheet.js:
--------------------------------------------------------------------------------
1 | import React, { useContext, useState } from "react";
2 | import { StyleSheet, View, Text, Dimensions } from "react-native";
3 | import AsyncStorage from "@react-native-async-storage/async-storage";
4 |
5 | import Colors from "../../../theming/colors";
6 |
7 | import {
8 | TouchableRipple,
9 | Checkbox,
10 | Chip,
11 | RadioButton,
12 | } from "react-native-paper";
13 | import SlidingUpPanel from "rn-sliding-up-panel";
14 | import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
15 | import { TabView, SceneMap, TabBar } from "react-native-tab-view";
16 |
17 | import { ThemeContext } from "../../../navigation/ThemeProvider";
18 |
19 | const initialLayout = { width: Dimensions.get("window").width };
20 |
21 | const BottomSheet = ({
22 | handleSorting,
23 | handleCompletedFilter,
24 | sorting,
25 | completedFilter,
26 | prioFilter,
27 | handlePriorityFilter,
28 | handleRef,
29 | handleDisplayMode,
30 | displayMode,
31 | }) => {
32 | const { theme } = useContext(ThemeContext);
33 |
34 | const { sortMode, sortOrder } = sorting;
35 |
36 | const [index, setIndex] = useState(0);
37 |
38 | const FirstRoute = () => (
39 |
40 | handleCompletedFilter()}
43 | >
44 | <>
45 |
51 | Completed
52 |
53 | handleCompletedFilter()}
56 | color={theme.colorAccentSecondary}
57 | uncheckedColor={theme.textColor}
58 | />
59 | >
60 |
61 |
62 | handlePriorityFilter(1)}
76 | selected={prioFilter === 1 ? true : false}
77 | >
78 | High
79 |
80 | handlePriorityFilter(2)}
94 | selected={prioFilter === 2 ? true : false}
95 | >
96 | Medium
97 |
98 | handlePriorityFilter(3)}
112 | selected={prioFilter === 3 ? true : false}
113 | >
114 | Low
115 |
116 |
117 |
118 | );
119 |
120 | const SecondRoute = () => (
121 |
122 | handleSorting("createdAt")}
125 | >
126 | <>
127 |
128 | Sort by created at
129 |
130 | {sortMode === "createdAt" && (
131 |
139 | )}
140 | >
141 |
142 | handleSorting("priorityIs")}
145 | >
146 | <>
147 |
148 | Sort by priority
149 |
150 | {sortMode === "priorityIs" && (
151 |
159 | )}
160 | >
161 |
162 | handleSorting("taskTime")}
165 | >
166 | <>
167 |
168 | Sort by due time
169 |
170 | {sortMode === "taskTime" && (
171 |
179 | )}
180 | >
181 |
182 |
183 | );
184 |
185 | const ThirdRoute = () => (
186 |
187 | {
189 | handleDisplayMode(value);
190 | AsyncStorage.setItem("@cardStyle", JSON.stringify(value));
191 | }}
192 | value={displayMode}
193 | >
194 |
195 |
203 |
211 |
212 |
213 |
214 | );
215 |
216 | const [routes] = useState([
217 | { key: "first", title: "Sort" },
218 | { key: "second", title: "Filter" },
219 | { key: "third", title: "Display" },
220 | ]);
221 |
222 | const renderScene = SceneMap({
223 | first: SecondRoute,
224 | second: FirstRoute,
225 | third: ThirdRoute,
226 | });
227 |
228 | const renderTabBar = (props) => (
229 |
242 | );
243 |
244 | return (
245 |
250 |
257 |
258 | );
259 | };
260 |
261 | export default BottomSheet;
262 |
263 | const styles = StyleSheet.create({
264 | bottomSheetContainer: {
265 | flex: 1,
266 | paddingTop: 20,
267 | paddingBottom: 8,
268 | borderTopLeftRadius: 15,
269 | borderTopRightRadius: 15,
270 | elevation: 16,
271 | },
272 | filterHeading: {
273 | fontWeight: "bold",
274 | fontSize: 15,
275 | paddingHorizontal: 20,
276 | paddingBottom: 5,
277 | },
278 | indicator: {
279 | backgroundColor: "rgba(0,0,0,0.75)",
280 | height: 5,
281 | width: 40,
282 | borderRadius: 4,
283 | alignSelf: "center",
284 | position: "absolute",
285 | marginTop: 7,
286 | },
287 | sortArrow: {
288 | paddingTop: 3,
289 | paddingRight: 5,
290 | },
291 | setSorting: {
292 | flexDirection: "row",
293 | justifyContent: "space-between",
294 | alignItems: "center",
295 | paddingHorizontal: 20,
296 |
297 | height: 50,
298 | },
299 | completedFilter: {
300 | flexDirection: "row",
301 | alignItems: "center",
302 | justifyContent: "space-between",
303 | paddingHorizontal: 20,
304 | paddingVertical: 10,
305 | },
306 | priorityFilters: {
307 | flexDirection: "row",
308 | paddingVertical: 5,
309 | paddingHorizontal: 20,
310 | },
311 | });
312 |
--------------------------------------------------------------------------------
/src/screens/TaskList/Components/FullCard.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import { StyleSheet, Text, View } from "react-native";
3 | import { TouchableRipple } from "react-native-paper";
4 |
5 | import moment from "moment";
6 | import { priorityColor } from "../../../utils/priority";
7 |
8 | import { ThemeContext } from "../../../navigation/ThemeProvider";
9 | import { color } from "react-native-reanimated";
10 |
11 | export default function TaskCard({
12 | taskItem,
13 | navigation,
14 | updateCompleted,
15 | onToggleSnackBar,
16 | handleSetTaskId,
17 | onDismissSnackBar,
18 | }) {
19 | const { theme } = useContext(ThemeContext);
20 |
21 | const { taskTitle, taskTime, taskContent, priorityIs } = taskItem;
22 |
23 | const [checked, setChecked] = useState(taskItem.isCompleted);
24 |
25 | const handleCompleted = () => {
26 | setChecked(!checked);
27 | updateCompleted(checked, taskItem.id);
28 | if (!checked) {
29 | onToggleSnackBar();
30 | handleSetTaskId(taskItem.id);
31 | } else {
32 | onDismissSnackBar();
33 | }
34 | };
35 | return (
36 |
37 | navigation.navigate("Task Item", taskItem)}
45 | onLongPress={() => {
46 | handleCompleted();
47 | }}
48 | >
49 |
50 |
66 | {taskTitle + " "}
67 |
77 |
78 | {taskTime !== "" && (
79 |
88 | {"Due " + moment(taskTime.toDate()).calendar()}
89 |
90 | )}
91 | {taskContent !== "" && (
92 |
101 | {taskContent}
102 |
103 | )}
104 |
105 |
106 |
107 | );
108 | }
109 |
110 | const styles = StyleSheet.create({
111 | mainContainer: {
112 | marginVertical: 5,
113 | marginHorizontal: 7,
114 | },
115 | taskListContainer: {
116 | borderRadius: 15,
117 | elevation: 2,
118 | },
119 | taskListView: {
120 | flex: 1,
121 | paddingVertical: 9,
122 | },
123 | taskItemTitle: {
124 | paddingTop: 10,
125 | paddingHorizontal: 20,
126 | fontSize: 18,
127 | fontWeight: "bold",
128 | },
129 | taskItemDate: {
130 | marginBottom: 5,
131 | paddingHorizontal: 20,
132 | fontSize: 14,
133 | color: "#767676",
134 | },
135 | taskContent: {
136 | marginBottom: 10,
137 | paddingHorizontal: 20,
138 | fontSize: 14,
139 | color: "#484848",
140 | lineHeight: 20,
141 | },
142 | priorityMarker: {
143 | position: "absolute",
144 | left: 65,
145 | top: 17,
146 | },
147 |
148 | rightChevronContainer: {
149 | position: "absolute",
150 | right: 25,
151 | top: 10,
152 | },
153 | rightChevron: {
154 | alignItems: "center",
155 | justifyContent: "center",
156 | borderRadius: 50,
157 | width: 60,
158 | height: 60,
159 | },
160 | icon: {
161 | marginRight: -2,
162 | marginTop: -2,
163 | },
164 | checkbox: {
165 | position: "absolute",
166 | left: 8,
167 | top: 14,
168 | },
169 |
170 | taskPriority: {
171 | marginTop: 5,
172 | width: 4,
173 | height: 40,
174 | borderRadius: 9999,
175 | borderWidth: 1,
176 | },
177 | checkBoxStyle: {
178 | borderRadius: 10,
179 | backgroundColor: "transparent",
180 | borderWidth: 0,
181 | },
182 | });
183 |
--------------------------------------------------------------------------------
/src/screens/TaskList/Components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { Appbar } from "react-native-paper";
3 |
4 | import { ThemeContext } from "../../../navigation/ThemeProvider";
5 |
6 | const Header = ({ navigation, handleSlider, handleSync }) => {
7 | const { theme } = useContext(ThemeContext);
8 |
9 | return (
10 |
11 |
12 | handleSync()} />
13 | handleSlider()}
16 | />
17 | navigation.navigate("More")}
20 | />
21 |
22 | );
23 | };
24 |
25 | export default Header;
26 |
--------------------------------------------------------------------------------
/src/screens/TaskList/Components/TaskCard.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import { StyleSheet, Text, View } from "react-native";
3 | import { CheckBox } from "react-native-elements";
4 |
5 | import { priorityColor } from "../../../utils/priority";
6 |
7 | import { TouchableRipple } from "react-native-paper";
8 | import moment from "moment";
9 | import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
10 | import Ripple from "react-native-material-ripple";
11 |
12 | import { ThemeContext } from "../../../navigation/ThemeProvider";
13 |
14 | export default function TaskCard({
15 | taskItem,
16 | navigation,
17 | updateCompleted,
18 | onToggleSnackBar,
19 | handleSetTaskId,
20 | onDismissSnackBar,
21 | }) {
22 | const { theme } = useContext(ThemeContext);
23 |
24 | const [checked, setChecked] = useState(taskItem.isCompleted);
25 |
26 | const { taskTime } = taskItem;
27 |
28 | const handleCompleted = () => {
29 | setChecked(!checked);
30 | updateCompleted(checked, taskItem.id);
31 | if (!checked) {
32 | onToggleSnackBar();
33 | handleSetTaskId(taskItem.id);
34 | } else {
35 | onDismissSnackBar();
36 | }
37 | };
38 |
39 | return (
40 |
41 | navigation.navigate("Task Item", taskItem)}
49 | >
50 |
51 |
52 | {
60 | handleCompleted();
61 | }}
62 | containerStyle={styles.checkBoxStyle}
63 | />
64 |
65 |
66 |
76 | {taskItem.taskTitle}
77 |
78 |
87 | {taskTime !== ""
88 | ? "Due " + moment(taskTime.toDate()).calendar()
89 | : moment(taskItem.createdAt.toDate()).calendar()}
90 |
91 |
92 |
93 |
99 |
100 |
101 |
106 | navigation.navigate("Task Item", taskItem)
107 | }
108 | >
109 |
115 |
116 |
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | const styles = StyleSheet.create({
124 | mainContainer: {
125 | marginVertical: 5,
126 | marginHorizontal: 7,
127 | },
128 | taskListContainer: {
129 | borderRadius: 15,
130 | elevation: 2,
131 | },
132 | taskListView: {
133 | flex: 1,
134 | paddingVertical: 9,
135 | },
136 | taskItemTitle: {
137 | paddingTop: 10,
138 | marginHorizontal: 80,
139 | fontSize: 18,
140 | fontWeight: "bold",
141 | },
142 | taskItemDate: {
143 | marginBottom: 10,
144 | marginHorizontal: 80,
145 | fontSize: 14,
146 | color: "#767676",
147 | },
148 | priorityMarker: {
149 | position: "absolute",
150 | left: 65,
151 | top: 17,
152 | },
153 |
154 | rightChevronContainer: {
155 | position: "absolute",
156 | right: 25,
157 | top: 10,
158 | },
159 | rightChevron: {
160 | alignItems: "center",
161 | justifyContent: "center",
162 | borderRadius: 50,
163 | width: 60,
164 | height: 60,
165 | },
166 | icon: {
167 | marginRight: -2,
168 | marginTop: -2,
169 | },
170 | checkbox: {
171 | position: "absolute",
172 | left: 8,
173 | top: 14,
174 | },
175 |
176 | taskPriority: {
177 | marginTop: 5,
178 | width: 4,
179 | height: 40,
180 | borderRadius: 9999,
181 | borderWidth: 1,
182 | },
183 | checkBoxStyle: {
184 | borderRadius: 10,
185 | backgroundColor: "transparent",
186 | borderWidth: 0,
187 | },
188 | });
189 |
--------------------------------------------------------------------------------
/src/screens/TaskList/Tasks.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, useContext } from "react";
2 | import {
3 | StyleSheet,
4 | View,
5 | ToastAndroid,
6 | FlatList,
7 | RefreshControl,
8 | } from "react-native";
9 | import { useFocusEffect } from "@react-navigation/native";
10 | import { Provider, FAB, Portal, Snackbar } from "react-native-paper";
11 | import AsyncStorage from "@react-native-async-storage/async-storage";
12 | import NetInfo from "@react-native-community/netinfo";
13 |
14 | import { deleteTask, getAllTasks, updateCompleted } from "../../utils/firebase";
15 |
16 | import { ThemeContext } from "../../navigation/ThemeProvider";
17 |
18 | import AppBar from "./Components/Header";
19 | import TaskCard from "./Components/TaskCard";
20 | import FullCard from "./Components/FullCard";
21 | import BottomSheet from "./Components/BottomSheet";
22 | import Colors from "../../theming/colors";
23 |
24 | const TasksList = ({ navigation }) => {
25 | const { theme } = useContext(ThemeContext);
26 |
27 | /**
28 | * Refresh Control
29 | */
30 | const [refreshing, setRefreshing] = useState(true);
31 |
32 | const onRefresh = async () => {
33 | setRefreshing(true);
34 | ToastAndroid.show("Updating your tasks", ToastAndroid.SHORT);
35 | getTasks(sortMode, sortOrder);
36 | };
37 |
38 | /**
39 | * Get tasks from cloud storage
40 | */
41 | const [tasksList, setTasksList] = useState([]);
42 |
43 | const getTasks = async () => {
44 | let list = await getAllTasks(sortMode, sortOrder);
45 | completedFilter &&
46 | (list = list.filter((item) => item.isCompleted === true));
47 | priorityFilter !== 0 &&
48 | (list = list.filter((item) => item.priorityIs === priorityFilter));
49 | setTasksList(list);
50 | setRefreshing(false);
51 |
52 | NetInfo.fetch().then((state) => {
53 | !state.isConnected &&
54 | ToastAndroid.show(
55 | "Cannot retrieve tasks at this moment. Please check your internet connection",
56 | ToastAndroid.SHORT
57 | );
58 | });
59 | };
60 |
61 | /**
62 | * Sorting and filters
63 | */
64 | const [sorting, setSorting] = useState({
65 | sortMode: "createdAt",
66 | sortOrder: "desc",
67 | });
68 | const { sortMode, sortOrder } = sorting;
69 | const [completedFilter, setCompletedFilter] = useState(false);
70 | const [priorityFilter, setPriorityFilter] = useState(0);
71 |
72 | const [displayMode, setDisplayMode] = useState("compact");
73 |
74 | AsyncStorage.getItem("@cardStyle").then(
75 | (value) => value && setDisplayMode(JSON.parse(value))
76 | );
77 |
78 | useFocusEffect(
79 | useCallback(() => {
80 | let isMounted = true;
81 |
82 | getTasks(sortMode, sortOrder);
83 |
84 | return () => (isMounted = false);
85 | }, [sorting, completedFilter, priorityFilter, displayMode])
86 | );
87 |
88 | /**
89 | * Sync with cloud storage
90 | */
91 | const handleSync = async () => {
92 | setRefreshing(true);
93 | await getTasks(sortMode, sortOrder);
94 | ToastAndroid.show("Synced with cloud storage", ToastAndroid.SHORT);
95 | };
96 |
97 | /**
98 | * Handle sort and filter settings
99 | */
100 | const handleSorting = (sortMode) => {
101 | setRefreshing(true);
102 | setSorting({
103 | sortMode: sortMode,
104 | sortOrder: sortOrder === "asc" ? "desc" : "asc",
105 | });
106 | };
107 |
108 | const handleCompletedFilter = () => {
109 | setRefreshing(true);
110 | setCompletedFilter(!completedFilter);
111 | };
112 |
113 | const handlePriorityFilter = (priority) => {
114 | setRefreshing(true);
115 | if (priority === priorityFilter) {
116 | setPriorityFilter(0);
117 | } else {
118 | setPriorityFilter(priority);
119 | }
120 | };
121 |
122 | /**
123 | * Toggle and dismiss snackbar
124 | */
125 | const [visible, setVisible] = useState(false);
126 | const onToggleSnackBar = () => setVisible(true);
127 | const onDismissSnackBar = () => {
128 | setVisible(false);
129 | setDeleteTaskId("");
130 | };
131 |
132 | /**
133 | * Delete task from snackbar when completed
134 | */
135 | const [deleteTaskId, setDeleteTaskId] = useState();
136 |
137 | const handleSetTaskId = (taskId) => setDeleteTaskId(taskId);
138 |
139 | const handleDeleteTask = async () => {
140 | if (deleteTaskId !== "") {
141 | setRefreshing(true);
142 | await deleteTask(navigation, deleteTaskId);
143 | setDeleteTaskId("");
144 | getTasks(sortMode, sortOrder);
145 | }
146 | };
147 |
148 | const handleDisplayMode = (value) => setDisplayMode(value);
149 |
150 | /**
151 | * Indivisual task item
152 | */
153 | const renderTaskCard = ({ item }) => (
154 |
162 | );
163 |
164 | const renderFullCard = ({ item }) => (
165 |
173 | );
174 |
175 | return (
176 |
177 | _panel.show({ velocity: -1.5 })}
180 | handleSync={handleSync}
181 | />
182 |
183 |
189 | item.id}
194 | renderItem={
195 | displayMode === "fullcard"
196 | ? renderFullCard
197 | : renderTaskCard
198 | }
199 | refreshControl={
200 |
206 | }
207 | />
208 |
209 | navigation.navigate("Add Task")}
217 | />
218 |
219 | (_panel = c)}
227 | handleDisplayMode={handleDisplayMode}
228 | displayMode={displayMode}
229 | />
230 |
241 | Task Completed
242 |
243 |
244 |
245 | );
246 | };
247 |
248 | export default TasksList;
249 |
250 | const styles = StyleSheet.create({
251 | flatListContainer: {
252 | flex: 1,
253 | paddingVertical: 5,
254 | },
255 | fab: {
256 | position: "absolute",
257 | margin: 16,
258 | right: 0,
259 | bottom: 0,
260 | },
261 | });
262 |
--------------------------------------------------------------------------------
/src/theming/colors.js:
--------------------------------------------------------------------------------
1 | const lightTheme = {
2 | textColor: "#484848",
3 | iconColor: "#FFFFFF",
4 | subTextColor: "#767676",
5 | accentColor: "#118086",
6 | priorityHigh: "#FF0000",
7 | priorityMid: "orange",
8 | priorityLow: "#1E90FF",
9 | background: "#FAFAFA",
10 | tabNavigator: "#FFFFFF",
11 | deleteColor: "#E53935",
12 | };
13 |
14 | // const darkTheme = {
15 | // textColor: "#A80000",
16 | // iconColor: "#ffffff",
17 | // subTextColor: "#767676",
18 | // accentColor: "black",
19 | // priorityHigh: "red",
20 | // priorityMid: "orange",
21 | // priorityLow: "dodgerblue",
22 | // background: "rgba(0,0,0,0.9)",
23 | // tabNavigator: "rgba(0,0,0,1)",
24 | // };
25 |
26 | export default lightTheme;
27 |
--------------------------------------------------------------------------------
/src/theming/themes.js:
--------------------------------------------------------------------------------
1 | // Application Colors
2 |
3 | export const applicationColors = {};
4 |
5 | // Light Theme
6 |
7 | export const lightTheme = {
8 | colorAccentPrimary: "#118086",
9 | colorAccentSecondary: "#118086",
10 | backgroundColor: "#FAFAFA",
11 | priority: { high: "#FF0000", mid: "orange", low: "#1E90FF" },
12 | textColor: "#484848",
13 | subTextColor: "#767676",
14 | chipColor: "#FFFFFF",
15 |
16 | iconColor: "#118086",
17 |
18 | delete: "#E53935",
19 | cardBackground: "#FFFFFF",
20 | bottomSheet: "#FFFFFF",
21 | cardIcon: "#118086",
22 | fabGroup: "#FFFFFF",
23 | };
24 |
25 | // Dark Theme
26 |
27 | export const darkTheme = {
28 | colorAccentPrimary: "#242529",
29 | colorAccentSecondary: "#3399FF",
30 | backgroundColor: "#202125",
31 | priority: { high: "#FF0000", mid: "orange", low: "#3399FF" },
32 | textColor: "#FFFFFF",
33 | subTextColor: "rgba(255,255,255,0.7)",
34 | chipColor: "#FFFFFF",
35 |
36 | iconColor: "#FFFFFF",
37 | cardBackground: "#242529",
38 | bottomSheet: "#242529",
39 | cardIcon: "rgba(255,255,255,0.9)",
40 | fabGroup: "#3399ff",
41 | };
42 |
43 | const theme = {
44 | colorAccentPrimary: "",
45 | colorAccentSecondary: "",
46 | backgroundColor: "",
47 | priority: { high: "", mid: "", low: "" },
48 | headerColor: "",
49 | headerTextColor: "",
50 | textColor: "",
51 | subTextColor: "",
52 | chipColor: "",
53 | };
54 |
--------------------------------------------------------------------------------
/src/utils/firebase.js:
--------------------------------------------------------------------------------
1 | import firebase from "../../firebaseConfig";
2 | import "firebase/firestore";
3 | import { Alert } from "react-native";
4 |
5 | export const auth = firebase.auth();
6 |
7 | export const loginUser = (email, password) => {
8 | auth.signInWithEmailAndPassword(email, password).catch((error) => {
9 | Alert.alert(error.message);
10 | });
11 | };
12 |
13 | export const signupUser = (email, password) => {
14 | auth.createUserWithEmailAndPassword(email, password)
15 | .then(() => {
16 | const userUid = firebase.auth().currentUser.uid;
17 |
18 | firebase.firestore().collection("users").doc(userUid).set({
19 | userUid: userUid,
20 | userEmail: email,
21 | });
22 | })
23 | .catch((error) => {
24 | Alert.alert(error.message);
25 | });
26 | };
27 |
28 | export const logout = () => auth.signOut();
29 |
30 | export const passwordReset = (email) => auth.sendPasswordResetEmail(email);
31 |
32 | export const addTask = async (
33 | navigation,
34 | taskTitle,
35 | taskTime,
36 | taskContent,
37 | priorityIs,
38 | collaborators,
39 | isCompleted = false,
40 | isUpdated = false
41 | ) => {
42 | const timeStamp = firebase.firestore.Timestamp.fromDate(new Date());
43 |
44 | await firebase
45 | .firestore()
46 | .collection("users")
47 | .doc(firebase.auth().currentUser.uid)
48 | .collection("tasks")
49 | .add({
50 | userId: firebase.auth().currentUser.uid,
51 | taskTitle: taskTitle,
52 | taskTime: taskTime,
53 | taskContent: taskContent,
54 | createdAt: timeStamp,
55 | priorityIs: priorityIs,
56 | collaborators: collaborators,
57 | isCompleted: isCompleted,
58 | isUpdated: isUpdated,
59 | })
60 | .catch((error) => console.log(error));
61 |
62 | navigation.navigate("Your Tasks");
63 | };
64 |
65 | export const addSampleData = async (navigation) => {
66 | const timeStamp = firebase.firestore.Timestamp.fromDate(new Date());
67 | const dummyData = {
68 | userId: firebase.auth().currentUser.uid,
69 | taskTitle: "Task Title",
70 | taskTime: timeStamp,
71 | taskContent: "Task Content",
72 | createdAt: timeStamp,
73 | priorityIs: 2,
74 | collaborators: ["abc@gmail.com"],
75 | isCompleted: false,
76 | isUpdated: false,
77 | };
78 |
79 | await firebase
80 | .firestore()
81 | .collection("users")
82 | .doc(firebase.auth().currentUser.uid)
83 | .collection("tasks")
84 | .add(dummyData)
85 | .catch((error) => console.log(error));
86 |
87 | navigation.navigate("Your Tasks");
88 | };
89 |
90 | export const updateTask = async (
91 | navigation,
92 | id,
93 | taskTitle,
94 | taskTime,
95 | taskContent,
96 | taskPriority,
97 | collaborators,
98 | isCompleted
99 | ) => {
100 | const timeStamp = firebase.firestore.Timestamp.fromDate(new Date());
101 |
102 | await firebase
103 | .firestore()
104 | .collection("users")
105 | .doc(firebase.auth().currentUser.uid)
106 | .collection("tasks")
107 | .doc(id)
108 | .update({
109 | userId: firebase.auth().currentUser.uid,
110 | taskTitle: taskTitle,
111 | taskTime: taskTime,
112 | taskContent: taskContent,
113 | createdAt: timeStamp,
114 | priorityIs: taskPriority,
115 | collaborators: collaborators,
116 | isCompleted: isCompleted,
117 | isUpdated: true,
118 | })
119 | .catch((error) => console.log(error));
120 |
121 | navigation.navigate("Your Tasks");
122 | };
123 |
124 | export const deleteTask = async (navigation, id) => {
125 | await firebase
126 | .firestore()
127 | .collection("users")
128 | .doc(firebase.auth().currentUser.uid)
129 | .collection("tasks")
130 | .doc(id)
131 | .delete();
132 |
133 | navigation.navigate("Your Tasks");
134 | };
135 |
136 | export const updateCompleted = async (isCompleted, taskId) => {
137 | const dbRef = firebase
138 | .firestore()
139 | .collection("users")
140 | .doc(firebase.auth().currentUser.uid)
141 | .collection("tasks");
142 |
143 | dbRef.doc(taskId).update({ isCompleted: !isCompleted });
144 | };
145 |
146 | export const deleteUser = async (navigation) => {
147 | const user = firebase.auth().currentUser;
148 |
149 | await firebase
150 | .firestore()
151 | .collection("users")
152 | .doc(firebase.auth().currentUser.uid)
153 | .delete();
154 |
155 | user.delete()
156 | .then(function () {
157 | logout(navigation);
158 | })
159 | .catch(function (error) {
160 | Alert.alert(error.message);
161 | });
162 | };
163 |
164 | export const currentUserEmail = () => {
165 | return auth.currentUser.email;
166 | };
167 |
168 | export const getAllTasks = async (sortBy, sortOrder) => {
169 | const dbRef = firebase
170 | .firestore()
171 | .collection("users")
172 | .doc(firebase.auth().currentUser.uid)
173 | .collection("tasks");
174 |
175 | let list = [];
176 |
177 | const snapshot = await dbRef.orderBy(sortBy, sortOrder).get();
178 | snapshot.forEach((doc) => {
179 | list.push({
180 | id: doc.id,
181 | ...doc.data(),
182 | });
183 | });
184 | return list;
185 | };
186 |
--------------------------------------------------------------------------------
/src/utils/priority.js:
--------------------------------------------------------------------------------
1 | import Colors from "../theming/colors";
2 |
3 | /**
4 | * Sets the priority marker's color
5 | * @param {Number} priority
6 | */
7 |
8 | export const priorityColor = (priority) => {
9 | if (priority === 1) {
10 | return {
11 | backgroundColor: Colors.priorityHigh,
12 | borderColor: Colors.priorityHigh,
13 | };
14 | } else if (priority === 2) {
15 | return {
16 | backgroundColor: Colors.priorityMid,
17 | borderColor: Colors.priorityMid,
18 | };
19 | } else if (priority === 3) {
20 | return {
21 | backgroundColor: Colors.priorityLow,
22 | borderColor: Colors.priorityLow,
23 | };
24 | } else {
25 | return {
26 | backgroundColor: "white",
27 | borderColor: "white",
28 | };
29 | }
30 | };
31 |
32 | export const priorityTextColor = (priority) => {
33 | if (priority === 1) {
34 | return {
35 | color: Colors.priorityHigh,
36 | };
37 | } else if (priority === 2) {
38 | return {
39 | color: Colors.priorityMid,
40 | };
41 | } else if (priority === 3) {
42 | return {
43 | color: Colors.priorityLow,
44 | };
45 | } else {
46 | return {
47 | color: Colors.textColor,
48 | };
49 | }
50 | };
51 |
--------------------------------------------------------------------------------