├── .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 | [![runs with expo](https://img.shields.io/badge/Runs%20with%20Expo-4630EB.svg?logo=EXPO&labelColor=f3f3f3&logoColor=000)](https://expo.io/) 4 | ![GitHub](https://img.shields.io/github/license/rajarsheechatterjee/task-manager) 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 | ![Home-screen](./assets/screenshots/SS2.png) 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 | 156 | 157 | Add Collaborators 158 | 159 | 160 | setEmail(text)} 166 | selectionColor={theme.colorAccentSecondary} 167 | theme={{ 168 | colors: { 169 | primary: theme.colorAccentSecondary, 170 | text: theme.textColor, 171 | underlineColor: "transparent", 172 | }, 173 | }} 174 | style={{ 175 | backgroundColor: theme.backgroundColor, 176 | }} 177 | /> 178 | {error !== "" && ( 179 | 185 | {error} 186 | 187 | )} 188 | 189 | 190 | 196 | 202 | 203 | 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 | 171 | 172 | Add Collaborators 173 | 174 | 175 | setEmail(text)} 181 | selectionColor={theme.colorAccentSecondary} 182 | theme={{ 183 | colors: { 184 | primary: theme.colorAccentSecondary, 185 | text: theme.textColor, 186 | underlineColor: "transparent", 187 | }, 188 | }} 189 | style={{ 190 | backgroundColor: theme.backgroundColor, 191 | }} 192 | /> 193 | {error !== "" && ( 194 | 200 | {error} 201 | 202 | )} 203 | 204 | 205 | 211 | 217 | 218 | 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 | 88 | 89 | 90 | The note will be deleted 91 | 92 | 93 | 94 | 100 | 106 | 107 | 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 | --------------------------------------------------------------------------------