├── .gitignore ├── Application ├── To-Do │ ├── View │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Cells │ │ │ ├── ImageAttachmentCell.swift │ │ │ └── TaskCell.swift │ │ ├── EmptyState.swift │ │ └── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ ├── Model │ │ ├── To_Do.xcdatamodeld │ │ │ ├── .xccurrentversion │ │ │ ├── To_Do.xcdatamodel │ │ │ │ └── contents │ │ │ └── To_Do2.xcdatamodel │ │ │ │ └── contents │ │ └── SortTypes.swift │ ├── Helpers │ │ ├── Extensions.swift │ │ ├── TextviewBorder.swift │ │ ├── Constants.swift │ │ ├── CameraHelper.swift │ │ ├── Alert.swift │ │ ├── Info.plist │ │ ├── SceneDelegate.swift │ │ └── AppDelegate.swift │ └── Controller │ │ ├── OnboardingViewController.swift │ │ ├── ResultsTableController.swift │ │ ├── TaskHistoryViewController.swift │ │ ├── TaskDetailsViewController.swift │ │ └── TodoViewController.swift ├── To-Do.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── To-Do.xcscheme │ └── project.pbxproj └── To-DoTests │ ├── Info.plist │ └── TaskTests.swift ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── workflows │ └── main.yml ├── PULL_REQUEST_TEMPLATE.md └── pull_request_template.md. ├── LICENSE ├── README.md └── contributing.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## User settings 2 | xcuserdata/ 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Application/To-Do/View/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Application/To-Do.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Application/To-Do/Model/To_Do.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | To_Do2.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Application/To-Do.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // To-Do 4 | // 5 | // Created by ELezov on 15.10.2020. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | static let empty = "" 14 | 15 | func trim() -> String { 16 | return self.trimmingCharacters(in: .whitespacesAndNewlines) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # These owners will be the default owners for everything in 5 | # the repo. Unless a later match takes precedence, 6 | # @global-owner1 and @global-owner2 will be requested for 7 | # review when someone opens a pull request. 8 | # Please add names of code owners here and tag the below 9 | # aaryankotharii 10 | * @aaryankotharii 11 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/TextviewBorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextviewBorder.swift 3 | // To-Do 4 | // 5 | // Created by Aaryan Kothari on 30/09/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITextView{ 12 | func addBorder(){ 13 | self.layer.cornerRadius = 6 14 | self.layer.borderWidth = 1 15 | self.layer.borderColor = UIColor.separator.cgColor 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Application/To-Do/View/Cells/ImageAttachmentCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageAttachmentCell.swift 3 | // To-Do 4 | // 5 | // Created by Lucas Araujo on 06/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageAttachmentCell: UICollectionViewCell { 12 | @IBOutlet private weak var imageView: UIImageView! 13 | 14 | func setImage(_ image: UIImage?) { 15 | imageView.image = image 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Application/To-Do/View/Cells/TaskCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskCell.swift 3 | // To-Do 4 | // 5 | // Created by Adriana González Martínez on 10/2/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TaskCell: UITableViewCell { 12 | 13 | @IBOutlet weak var title: UILabel! 14 | @IBOutlet weak var subtitle: UILabel! 15 | @IBOutlet weak var starImage: UIImageView! 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | // Initialization code 20 | self.starImage.image = UIImage(systemName: "star.fill") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: macos-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@master 16 | - name: Switch to Xcode 12 17 | run: sudo xcode-select -s /Applications/Xcode_12.app 18 | - name: Build 19 | env: 20 | PROJECT: To-Do.xcodeproj 21 | SCHEME: To-Do 22 | DESTINATION: platform=iOS Simulator,OS=14.0,name=iPhone 11 23 | run: | 24 | cd Application 25 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release clean test CODE_SIGNING_ALLOWED=NO | xcpretty 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Testing (please complete the following information):** 27 | - Device: [e.g. iPhone6] 28 | - iOS: [e.g. 14.0] 29 | - Xcode [e.g. 12.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /Application/To-DoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // To-Do 4 | // 5 | // Created by Aaryan Kothari on 11/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | class Constants { 13 | 14 | struct ViewController{ 15 | static let Onboarding = "OnboardingViewController" 16 | static let ResultsTable = "ResultsTableController" 17 | } 18 | 19 | struct Cell{ 20 | static let taskCell = "todocell" 21 | static let photoCell = "AttachmentCell" 22 | } 23 | 24 | struct Segue{ 25 | static let taskToTaskDetail = "gototask" 26 | } 27 | 28 | struct Key{ 29 | static let onboarding = "already_shown_onboarding" 30 | } 31 | 32 | struct Action{ 33 | static let star = "star" 34 | static let unstar = "unstar" 35 | static let add = "add" 36 | static let update = "update" 37 | static let delete = "delete" 38 | static let complete = "complete" 39 | static let cancel = "cancel" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 IEEE VIT Student Chapter 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 | -------------------------------------------------------------------------------- /Application/To-Do/Controller/OnboardingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingViewController.swift 3 | // To-Do 4 | // 5 | // Created by Abraao Levi on 03/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class OnboardingViewController: UIViewController { 12 | 13 | private enum LocalConstants { 14 | static let cornerRadius: CGFloat = 10 15 | } 16 | 17 | @IBOutlet weak var nextButton: UIButton! 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | setupViews() 22 | } 23 | 24 | func alreadyShown() -> Bool { 25 | return UserDefaults.standard.bool(forKey: Constants.Key.onboarding) 26 | } 27 | 28 | @IBAction func nextButtonTapped(_ sender: Any) { 29 | markAsSeen() 30 | dismiss(animated: true) 31 | } 32 | 33 | private func markAsSeen() { 34 | UserDefaults.standard.set(true, forKey: Constants.Key.onboarding) 35 | } 36 | 37 | fileprivate func setupViews() { 38 | nextButton.layer.cornerRadius = LocalConstants.cornerRadius 39 | nextButton.clipsToBounds = true 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | Include a summary of the change and relevant motivation/context. List any dependencies that are required for this change. 3 | 4 | Fixes # [ISSUE] 5 | 6 | ### Type of Change: 7 | **Delete irrelevant options.** 8 | 9 | - Code 10 | - Quality Assurance 11 | - User Interface 12 | - Documentation 13 | 14 | **Code/Quality Assurance Only** 15 | - Bug fix (non-breaking change which fixes an issue) 16 | - This change requires a documentation update (software upgrade on readme file) 17 | - New feature (non-breaking change which adds functionality pre-approved by mentors) 18 | 19 | 20 | 21 | ### How Has This Been Tested? 22 | Describe the tests you ran to verify your changes. Provide instructions or GIFs so we can reproduce. List any relevant details for your test. 23 | 24 | 25 | ### Checklist: 26 | **Delete irrelevant options.** 27 | 28 | - [ ] My PR follows the style guidelines of this project 29 | - [ ] I have performed a self-review of my own code or materials 30 | - [ ] I have commented my code or provided relevant documentation, particularly in hard-to-understand areas 31 | - [ ] I have made corresponding changes to the documentation 32 | - [ ] Any dependent changes have been merged 33 | 34 | **Code/Quality Assurance Only** 35 | - [ ] My changes generate no new warnings 36 | - [ ] My PR currently breaks something (fix or feature that would cause existing functionality to not work as expected) 37 | -------------------------------------------------------------------------------- /.github/pull_request_template.md.: -------------------------------------------------------------------------------- 1 | ### Description 2 | Include a summary of the change and relevant motivation/context. List any dependencies that are required for this change. 3 | 4 | Fixes # [ISSUE] 5 | 6 | ### Type of Change: 7 | **Delete irrelevant options.** 8 | 9 | - Code 10 | - Quality Assurance 11 | - User Interface 12 | - Documentation 13 | 14 | **Code/Quality Assurance Only** 15 | - Bug fix (non-breaking change which fixes an issue) 16 | - This change requires a documentation update (software upgrade on readme file) 17 | - New feature (non-breaking change which adds functionality pre-approved by mentors) 18 | 19 | 20 | 21 | ### How Has This Been Tested? 22 | Describe the tests you ran to verify your changes. Provide instructions or GIFs so we can reproduce. List any relevant details for your test. 23 | 24 | 25 | ### Checklist: 26 | **Delete irrelevant options.** 27 | 28 | - [ ] My PR follows the style guidelines of this project 29 | - [ ] I have performed a self-review of my own code or materials 30 | - [ ] I have commented my code or provided relevant documentation, particularly in hard-to-understand areas 31 | - [ ] I have made corresponding changes to the documentation 32 | - [ ] Any dependent changes have been merged 33 | 34 | **Code/Quality Assurance Only** 35 | - [ ] My changes generate no new warnings 36 | - [ ] My PR currently breaks something (fix or feature that would cause existing functionality to not work as expected) 37 | -------------------------------------------------------------------------------- /Application/To-Do/Model/SortTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SortTypes.swift 3 | // To-Do 4 | // 5 | // Created by Aman Personal on 02/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum SortTypesAvailable: CaseIterable { 12 | case sortByNameAsc 13 | case sortByNameDesc 14 | case sortByDateAsc 15 | case sortByDateDesc 16 | 17 | func getTitleForSortType() -> String { 18 | var titleString = "" 19 | switch self { 20 | case .sortByNameAsc: 21 | titleString = "Sort By Name (A-Z)" 22 | case .sortByNameDesc: 23 | titleString = "Sort By Name (Z-A)" 24 | case .sortByDateAsc: 25 | titleString = "Sort By Date (Earliest first)" 26 | case .sortByDateDesc: 27 | titleString = "Sort By Date (Latest first)" 28 | } 29 | return titleString 30 | } 31 | 32 | func getSortDescriptor() -> [NSSortDescriptor] { 33 | switch self { 34 | case .sortByNameAsc: 35 | return [NSSortDescriptor(key: "title", ascending: true)] 36 | case .sortByNameDesc: 37 | return [NSSortDescriptor(key: "title", ascending: false)] 38 | case .sortByDateAsc: 39 | return [NSSortDescriptor(key: "dueDateTimeStamp", ascending: true)] 40 | case .sortByDateDesc: 41 | return [NSSortDescriptor(key: "dueDateTimeStamp", ascending: false)] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/CameraHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraHelper.swift 3 | // To-Do 4 | // 5 | // Created by Lucas Araujo on 06/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import AVKit 12 | 13 | class CameraHelper: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 14 | 15 | private let cameraController = UIImagePickerController() 16 | private var didFinish: ((UIImage?) -> Void)? 17 | 18 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 19 | if let pickedImage = info[.editedImage] as? UIImage { 20 | didFinish?(pickedImage) 21 | } 22 | didFinish = nil 23 | picker.dismiss(animated: true, completion: nil) 24 | } 25 | 26 | func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { 27 | didFinish?(nil) 28 | didFinish = nil 29 | picker.dismiss(animated: true, completion: nil) 30 | } 31 | 32 | func openCamera(in controller: UIViewController, completion: @escaping (UIImage?) -> Void) { 33 | didFinish = completion 34 | 35 | if UIImagePickerController.isSourceTypeAvailable(.camera) { 36 | cameraController.sourceType = .camera 37 | } else { 38 | cameraController.sourceType = .photoLibrary 39 | } 40 | cameraController.delegate = self 41 | cameraController.allowsEditing = true 42 | controller.present(cameraController, animated: true, completion: nil) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Application/To-Do/Controller/ResultsTableController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultsTableController.swift 3 | // To-Do 4 | // 5 | // Created by Amirthy Tejeshwar on 01/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ResultsTableController: UITableViewController { 12 | var todoList = [Task]() 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | setupEmptyState() 18 | } 19 | 20 | fileprivate func setupEmptyState() { 21 | let emptyView = EmptyState(.emptySearch) 22 | self.tableView.backgroundView = emptyView 23 | self.tableView.setNeedsLayout() 24 | self.tableView.layoutIfNeeded() 25 | } 26 | 27 | 28 | //MARK: UITableView DataSource 29 | 30 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 31 | 32 | if todoList.isEmpty { 33 | self.tableView.backgroundView?.isHidden = false 34 | self.tableView.separatorStyle = .none 35 | } else { 36 | self.tableView.backgroundView?.isHidden = true 37 | self.tableView.separatorStyle = .singleLine 38 | } 39 | return todoList.count 40 | } 41 | 42 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 43 | let cell = UITableViewCell(style: .subtitle, reuseIdentifier: Constants.Cell.taskCell) 44 | let task = todoList[indexPath.row] 45 | cell.textLabel?.text = task.title 46 | cell.detailTextLabel?.text = task.dueDate 47 | return cell 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/Alert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alert.swift 3 | // To-Do 4 | // 5 | // Created by Mikaela Caron on 10/3/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Defines static functions for various alerts that can be used throughout the app 12 | struct Alert { 13 | 14 | /// Creates a UIAlertController and presents it on the specific view controller 15 | /// - Parameters: 16 | /// - vc: The UIViewController where the alert is presented 17 | /// - title: The title of the UIAlertController 18 | /// - message: The message of the UIAlertController 19 | private static func showBasicAlert(on vc: UIViewController, title: String, message: String) { 20 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) 21 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 22 | DispatchQueue.main.async { vc.present(alert, animated: true, completion: nil) } 23 | } 24 | 25 | /// Run `showBasicAlert` with a specified title and message related to not having a title for the task 26 | /// - Parameters: 27 | /// - vc: UIViewController to present this alert 28 | static func showNoTaskTitle(on vc: UIViewController) { 29 | showBasicAlert(on: vc, title: "😧 Uh Oh", message: "Give your task a name!") 30 | } 31 | 32 | /// Run `showBasicAlert` with a specified title and message related to not having a due date for the task 33 | /// - Parameters: 34 | /// - vc: UIViewController to present this alert 35 | static func showNoTaskDueDate(on vc: UIViewController) { 36 | showBasicAlert(on: vc, title: "🙁 Uh Oh", message: "Due date is empty") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Application/To-Do/Model/To_Do.xcdatamodeld/To_Do.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Application/To-Do/Model/To_Do.xcdatamodeld/To_Do2.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # Hacktoberfest 2020 with IEEE-VIT :heart: 4 | This is a simple ToDo iOS application built using swift. Users can also add subtasks and deadlines along with their tasks! 5 | 6 | Support open source software by participating in [Hacktoberfest](https://hacktoberfest.digitalocean.com) and get goodies and a free t-shirt! :yellow_heart: 7 | 8 | > Please check all issues labelled as `hacktoberfest` to start contributing! 9 | 10 | Kindly consider leaving a :star: if you like the repository and our organisation. 11 | 12 | ## Getting Started 13 | * Fork it. 14 | 15 | * Clone your forked repo and move inside it: 16 | 17 | `git clone https://github.com/IEEE-VIT/ToDo-iOS.git && cd ToDo-iOS` 18 | 19 | * Checkout to a new branch to work on an issue: 20 | 21 | `git checkout -b my-amazing-feature` 22 | 23 | * Running the project 24 | 25 | `open the project in Xcode and run!` 26 |
27 | `Shortcut: (⌘ + R)` 28 | 29 | * Once you're all done coding, it's time to open a PR :) 30 | Run the following commands from the root of the project directory: 31 | 32 | `git add .` 33 | 34 | `git commit -m "A short description about the feature."` 35 | 36 | `git push origin ` 37 | 38 | Open your forked repo in your browser and then raise a PR to the `master` branch of this repository! 39 | 40 | 41 | 42 | ## Contributing 43 | To start contributing, check out [CONTRIBUTING.md](https://github.com/IEEE-VIT/ToDo-iOS/blob/master/contributing.md). New contributors are always welcome to support this project. If you want something gentle to start with, check out issues labelled as `easy` or `good-first-issue`. Check out issues labelled as `hacktoberfest` if you are up for some grabs! :) 44 | 45 | ## License 46 | This project is licensed under [MIT](https://github.com/IEEE-VIT/ToDo-iOS/blob/master/LICENSE). 47 | -------------------------------------------------------------------------------- /Application/To-Do/View/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | NSCameraUsageDescription 64 | A picture is worth a thousand words 65 | NSPhotoLibraryUsageDescription 66 | A picture is worth a thousand words 67 | 68 | 69 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // To-Do 4 | // 5 | // Created by Aaryan Kothari on 29/09/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | 50 | // Save changes in the application's managed object context when the application transitions to the background. 51 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 52 | } 53 | 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Application/To-Do.xcodeproj/xcshareddata/xcschemes/To-Do.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /Application/To-DoTests/TaskTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // To_DoTests.swift 3 | // To-DoTests 4 | // 5 | // Created by Rizwan on 06/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import CoreData 11 | @testable import To_Do 12 | 13 | class TaskTests: XCTestCase { 14 | 15 | lazy var mockPersistantContainer: NSPersistentContainer = { 16 | let container = NSPersistentContainer(name: "To_Do") 17 | 18 | let description = NSPersistentStoreDescription() 19 | description.url = URL(fileURLWithPath: "/dev/null") 20 | container.persistentStoreDescriptions = [description] 21 | 22 | container.loadPersistentStores(completionHandler: { _, error in 23 | if let error = error as NSError? { 24 | fatalError("Failed to load stores: \(error), \(error.userInfo)") 25 | } 26 | }) 27 | 28 | return container 29 | }() 30 | 31 | // MARK:- Setup 32 | 33 | override func setUpWithError() throws { 34 | try super.setUpWithError() 35 | try initStubs() 36 | // Put setup code here. This method is called before the invocation of each test method in the class. 37 | } 38 | 39 | override func tearDownWithError() throws { 40 | flushData() 41 | try super.tearDownWithError() 42 | // Put teardown code here. This method is called after the invocation of each test method in the class. 43 | } 44 | 45 | // MARK:- Helpers 46 | 47 | @discardableResult func createTask( 48 | title: String? = nil, 49 | subtasks: String? = nil, 50 | dueDate: String? = nil, 51 | dueDateTimeStamp: Double, 52 | isFavourite: Bool = false, 53 | isComplete: Bool = false 54 | ) -> Task? { 55 | let task = Task(context: mockPersistantContainer.viewContext) 56 | task.title = title 57 | task.subTasks = subtasks 58 | task.dueDate = dueDate 59 | task.isFavourite = isFavourite 60 | task.dueDateTimeStamp = dueDateTimeStamp 61 | task.isComplete = isComplete 62 | return task 63 | } 64 | 65 | func initStubs() throws { 66 | _ = createTask(title: "Task Title", subtasks: "Sub Tasks", dueDateTimeStamp: Date().timeIntervalSince1970) 67 | try mockPersistantContainer.viewContext.save() 68 | } 69 | 70 | func flushData() { 71 | let objs = try! mockPersistantContainer.viewContext.fetch(Task.fetchRequest()) 72 | for case let obj as NSManagedObject in objs { 73 | mockPersistantContainer.viewContext.delete(obj) 74 | } 75 | try! mockPersistantContainer.viewContext.save() 76 | 77 | } 78 | 79 | // MARK:- Task Tests 80 | 81 | func testFetchAllTasks() { 82 | let results = try? mockPersistantContainer.viewContext.fetch(Task.fetchRequest()) 83 | XCTAssertEqual(results?.count, 1) 84 | } 85 | 86 | func testCreatTask() { 87 | let task = createTask(title: "Task Title", dueDateTimeStamp: Date().timeIntervalSince1970) 88 | XCTAssertNotNil(task) 89 | } 90 | 91 | func testMarkAsComplete() throws { 92 | let tasks = try mockPersistantContainer.viewContext.fetch(Task.fetchRequest()) as? [Task] 93 | let task = try XCTUnwrap(tasks?.first) 94 | 95 | let newIsCompleteValue = !task.isComplete 96 | 97 | task.isComplete = newIsCompleteValue 98 | try mockPersistantContainer.viewContext.save() 99 | 100 | XCTAssertEqual(task.isComplete, newIsCompleteValue) 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Application/To-Do/Helpers/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // To-Do 4 | // 5 | // Created by Aaryan Kothari on 29/09/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | // MARK: UISceneSession Lifecycle 23 | 24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 25 | // Called when a new scene session is being created. 26 | // Use this method to select a configuration to create the new scene with. 27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 28 | } 29 | 30 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 31 | // Called when the user discards a scene session. 32 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 33 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 34 | } 35 | 36 | // MARK: - Core Data stack 37 | 38 | lazy var persistentContainer: NSPersistentContainer = { 39 | /* 40 | The persistent container for the application. This implementation 41 | creates and returns a container, having loaded the store for the 42 | application to it. This property is optional since there are legitimate 43 | error conditions that could cause the creation of the store to fail. 44 | */ 45 | let container = NSPersistentContainer(name: "To_Do") 46 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 47 | if let error = error as NSError? { 48 | // Replace this implementation with code to handle the error appropriately. 49 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 50 | 51 | /* 52 | Typical reasons for an error here include: 53 | * The parent directory does not exist, cannot be created, or disallows writing. 54 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 55 | * The device is out of space. 56 | * The store could not be migrated to the current model version. 57 | Check the error message to determine what the actual problem was. 58 | */ 59 | fatalError("Unresolved error \(error), \(error.userInfo)") 60 | } 61 | }) 62 | return container 63 | }() 64 | 65 | // MARK: - Core Data Saving support 66 | 67 | func saveContext () { 68 | let context = persistentContainer.viewContext 69 | if context.hasChanges { 70 | do { 71 | try context.save() 72 | } catch { 73 | // Replace this implementation with code to handle the error appropriately. 74 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 75 | let nserror = error as NSError 76 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 77 | } 78 | } 79 | } 80 | 81 | } 82 | 83 | -------------------------------------------------------------------------------- /Application/To-Do/View/EmptyState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyState.swift 3 | // To-Do 4 | // 5 | // Created by Bayu Kurniawan on 10/10/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EmptyState: UIView { 12 | 13 | /// Properties 14 | 15 | private lazy var imageView: UIImageView = { 16 | let imageView = UIImageView() 17 | imageView.tintColor = .black 18 | imageView.contentMode = .scaleAspectFit 19 | 20 | return imageView 21 | }() 22 | 23 | private lazy var headingLabel: UILabel = { 24 | let label = UILabel() 25 | label.font = UIFont.boldSystemFont(ofSize: 18.0) 26 | label.textColor = .black 27 | label.numberOfLines = 0 28 | label.textAlignment = .center 29 | 30 | return label 31 | }() 32 | 33 | private lazy var subheadingLabel: UILabel = { 34 | let label = UILabel() 35 | label.font = UIFont.systemFont(ofSize: 15.0, weight: .medium) 36 | label.textColor = .darkGray 37 | label.textAlignment = .center 38 | 39 | label.numberOfLines = 2 40 | 41 | return label 42 | }() 43 | 44 | init(_ type : EmptyStateType) { 45 | super.init(frame: .zero) 46 | 47 | self.imageView.image = type.image 48 | self.headingLabel.text = type.heading 49 | self.subheadingLabel.text = type.subheading 50 | self.subheadingLabel.numberOfLines = 0 51 | 52 | let stackViews = UIStackView(arrangedSubviews: [imageView, headingLabel, subheadingLabel]) 53 | stackViews.axis = .vertical 54 | stackViews.spacing = 10.0 55 | 56 | addSubview(stackViews) 57 | stackViews.translatesAutoresizingMaskIntoConstraints = false 58 | stackViews.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.30).isActive = true 59 | stackViews.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 1.0).isActive = true 60 | stackViews.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true 61 | stackViews.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true 62 | } 63 | 64 | required init?(coder: NSCoder) { 65 | fatalError("init(coder:) has not been implemented") 66 | } 67 | 68 | public enum EmptyStateType{ 69 | case emptySearch 70 | case emptyList 71 | case emptyHistory 72 | 73 | var heading : String{ 74 | switch self { 75 | case .emptySearch: 76 | return "No tasks found :(" 77 | case .emptyList: 78 | return "No tasks added" 79 | case .emptyHistory: 80 | return "No task history found" 81 | } 82 | } 83 | 84 | 85 | var subheading : String{ 86 | switch self { 87 | case .emptySearch: 88 | return """ 89 | The task you search is not found. 90 | Create new one! 91 | """ 92 | case .emptyList: 93 | return """ 94 | You can create a new task with ease. 95 | Tap the '+' button on top! 96 | """ 97 | case .emptyHistory: 98 | return """ 99 | You can add tasks to task history by marking a task as complete. 100 | Swipe left on a task to mark it as complete! 101 | """ 102 | } 103 | } 104 | 105 | var image : UIImage?{ 106 | switch self { 107 | case .emptySearch: 108 | return UIImage(systemName: "magnifyingglass") 109 | case .emptyList: 110 | return UIImage(systemName: "note.text") 111 | case .emptyHistory: 112 | return UIImage(systemName: "minus.rectangle") 113 | } 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /Application/To-Do/View/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | This document contains certain rules and guidelines that developers are expected to follow, while contributing to any repository. 4 | 5 | --- 6 | ## 1. Commit Messages 7 | 8 | * Use the `-m` flag only for minor changes. The message following the `-m` flag must be of the below format : 9 | > ` ` 10 | 11 | :white_check_mark: __Examples of valid messages:__ 12 | * Added serialisers.py for users app 13 | * Updated utils/validator.js file 14 | * Changed functionality of authentication process 15 | 16 | :x: __Examples of invalid messages:__ 17 | * Idk why this is not working 18 | * Only ui bug fixes left 19 | * All changes done, ready for production :)) 20 | 21 | * Before opening a PR, make sure you squash all your commits into one single commit using `it rebase` (squash). Instead of having 50 commits that describe 1 feature implementation, there must be one commit that describes everything that has been done so far. You can read up about it [here](https://www.internalpointers.com/post/squash-commits-into-one-git). 22 | > NOTE: While squashing your commits to write a new one, do not make use of `-m` flag. In this case, a vim editor window shall open. Write a title for the commit within 50-70 characters, leave a line and add an understandable description. 23 | 24 | ## 2. Issues 25 | * Issues __MUST__ be opened any time any of the following events occur : 26 | 1. You encounter an issue such that a major (50 lines of code or above) portion of the code needs to be changed/added. 27 | 2. You want feature enhancements 28 | 3. You encounter bugs 29 | 4. Code refactoring is required 30 | 5. Test coverage should be increased 31 | * __Open issues with the given template only.__ 32 | * Feel free to label the issues appropriately. 33 | * Do not remove the headings (questions in bold) while opening an issue with the given template. Simply append to it. 34 | 35 | 36 | ## 3. Branches and PRs 37 | 38 | * No commits must be made to the `master` branch directly. The `master` branch shall only consist of the working code. 39 | * Developers are expected to work on feature branches, and upon successful development and testing, a PR (pull request) must be opened to merge with master. 40 | * A branch must be named as either as the feature being implemented, or the issue being fixed. 41 | 42 | :white_check_mark: __Examples of valid brach names:__ 43 | * #8123 (issue number) 44 | * OAuth (feature) 45 | * questionsUtils (functionality of the questions) 46 | 47 | :x: __Examples of invalid branch names__: 48 | * ziyan-testing 49 | * attemptToFixAuth 50 | * SomethingRandom 51 | 52 | 53 | ## 4. Discussion Ethics 54 | 55 | * Developers should be clear and concise while commenting on issues or PR reviews. If needed, one should provide visual reference or a code snippet for everyone involved to properly grasp the issue. 56 | * Everyone should be respectful of everyone's opinion. Any harsh/disrespectful language is __STRICTLY__ prohibited and will not be tolerated under any circumstances. 57 | 58 | ## 5. Coding Ethics 59 | 60 | * Developers are highly encouraged to use comments wherever necessary and make the code self documented. 61 | * The project structure should be neat and organised. All folders and files should be organised semantically according to their functionality. 62 | * The name of the folders and files should not be too long but should be as self explanatory as possible. 63 | * Documentation shall __STRICTLY__ have gender neutral terms. Instead of using "he/him" or "she/her", one should use "they/them" or "the user". 64 | 65 | ## 6. Coding Style Guidelines 66 | 67 | Developers should aim to write clean, maintainable, scalable and testable code. If your code is not testable, that means, it's time to refactor it. The following guidelines might come in handy for this: 68 | 69 | * Python: [Hitchiker's Guide to Python](https://docs.python-guide.org/writing/style/), [Google](https://github.com/google/styleguide/blob/gh-pages/pyguide.md) 70 | * GoLang: [Effective-Go](https://golang.org/doc/effective_go.html) 71 | * Django: [Django-Styleguide](https://github.com/HackSoftware/Django-Styleguide) 72 | * JavaScript: [Airbnb](https://github.com/airbnb/javascript) 73 | * React.JS: [Airbnb](https://github.com/airbnb/javascript/tree/master/react) 74 | * Flutter/Dart: [Effective-Dart](https://dart.dev/guides/language/effective-dart) 75 | * Kotlin: [Kotlin Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html) 76 | * Swift: [Swift Style Guide](https://github.com/github/swift-style-guide), [Google](https://google.github.io/swift/) 77 | * Docker: [Dev Best Practices](https://docs.docker.com/develop/), [Dockerfile Best Practices](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/) 78 | -------------------------------------------------------------------------------- /Application/To-Do/Controller/TaskHistoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskHistoryViewController.swift 3 | // To-Do 4 | // 5 | // Created by Mikaela Caron on 10/9/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class TaskHistoryViewController: UIViewController { 13 | 14 | // MARK: - IBOutlets 15 | @IBOutlet var historyTableView: UITableView! 16 | 17 | /// `DataSource` for historyTableView 18 | var completedList : [Task] = [] 19 | 20 | /// CoreData managed object 21 | var moc: NSManagedObjectContext! 22 | 23 | /// Default fetch request for tasks 24 | lazy var defaultFetchRequest: NSFetchRequest = { 25 | let fetchRequest : NSFetchRequest = Task.fetchRequest() 26 | return fetchRequest 27 | }() 28 | 29 | // MARK: - View Lifecycle 30 | 31 | override func viewDidLoad() { 32 | super.viewDidLoad() 33 | } 34 | 35 | override func viewWillAppear(_ animated: Bool) { 36 | loadData() 37 | if completedList.isEmpty { 38 | setupEmptyState() 39 | } 40 | } 41 | 42 | // MARK: - Logic 43 | 44 | fileprivate func setupEmptyState() { 45 | DispatchQueue.main.async { 46 | let emptyBackgroundView = EmptyState(.emptyHistory) 47 | self.historyTableView.backgroundView = emptyBackgroundView 48 | self.historyTableView.setNeedsLayout() 49 | self.historyTableView.layoutIfNeeded() 50 | } 51 | 52 | } 53 | 54 | /// Initialize ManagedObjectContext 55 | func loadData() { 56 | guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { 57 | return 58 | } 59 | let persistenceContainer = appDelegate.persistentContainer 60 | moc = persistenceContainer.viewContext 61 | do { 62 | let request = Task.fetchRequest() as NSFetchRequest 63 | let filter = NSPredicate(format: "isComplete = %d", true) 64 | request.predicate = filter 65 | completedList = try moc.fetch(request) 66 | DispatchQueue.main.async { 67 | self.historyTableView.reloadData() 68 | } 69 | } catch { 70 | print("can't fetch data") 71 | } 72 | } 73 | /// Deletes task from CoreData and removes from `completedList` and `historyTableView` 74 | /// - Parameter indexPath: Which task to delete 75 | func deleteTask(indexPath: IndexPath){ 76 | 77 | let confirmation = UIAlertController(title: nil, message: "Delete this task?", preferredStyle: .actionSheet) 78 | let deleteAction = UIAlertAction(title: "Yes", style: .destructive) { [weak self] (_) in 79 | guard let self = self else { return } 80 | self.historyTableView.beginUpdates() 81 | 82 | let task = self.completedList.remove(at: indexPath.row) 83 | self.historyTableView.deleteRows(at: [indexPath], with: .automatic) 84 | self.moc.delete(task) 85 | do { 86 | try self.moc.save() 87 | } catch { 88 | self.completedList.insert(task, at: indexPath.row) 89 | print(error.localizedDescription) 90 | } 91 | self.historyTableView.endUpdates() 92 | } 93 | let noAction = UIAlertAction(title: "No", style: .cancel) { (_) in 94 | print("not going to delete") 95 | return 96 | } 97 | confirmation.addAction(deleteAction) 98 | confirmation.addAction(noAction) 99 | present(confirmation, animated: true, completion: nil) 100 | } 101 | 102 | // I wasn't sure if you want the user to be able to update completed tasks so I set isUserInteractionEnabled to false for all subviews of taskDetailVC 103 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 104 | if let taskDetailVC = segue.destination as? TaskDetailsViewController { 105 | // Hide the tab bar when new controller is pushed onto the screen 106 | taskDetailVC.hidesBottomBarWhenPushed = true 107 | // taskDetailVC.delegate = self 108 | taskDetailVC.task = sender as? Task 109 | taskDetailVC.view.subviews.forEach { 110 | $0.isUserInteractionEnabled = false 111 | } 112 | if let button = taskDetailVC.navigationItem.rightBarButtonItem { 113 | button.isEnabled = false 114 | button.tintColor = .clear 115 | } 116 | } 117 | } 118 | } 119 | 120 | // MARK: - TableView DataSource and Delegate Methods 121 | extension TaskHistoryViewController: UITableViewDelegate, UITableViewDataSource { 122 | 123 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 124 | if completedList.isEmpty { 125 | self.historyTableView.backgroundView?.isHidden = false 126 | self.historyTableView.separatorStyle = .none 127 | } else { 128 | self.historyTableView.backgroundView?.isHidden = true 129 | self.historyTableView.separatorStyle = .singleLine 130 | } 131 | 132 | return completedList.count 133 | } 134 | 135 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 136 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cell.taskCell, for: indexPath) as! TaskCell 137 | let task = completedList[indexPath.row] 138 | cell.title.text = task.title 139 | cell.subtitle.text = task.dueDate 140 | cell.starImage.isHidden = true 141 | return cell 142 | } 143 | 144 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 145 | if editingStyle == .delete { 146 | deleteTask(indexPath: indexPath) 147 | } 148 | } 149 | 150 | // pushes taskDetailsVC with completed task details 151 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 152 | let task = completedList[indexPath.row] 153 | performSegue(withIdentifier: Constants.Segue.taskToTaskDetail, sender: task) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Application/To-Do/Controller/TaskDetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskDetailsViewController.swift 3 | // To-Do 4 | // 5 | // Created by Aaryan Kothari on 30/09/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol TaskDelegate: class { 12 | func didTapSave(task : Task) 13 | func didTapUpdate(task : Task) 14 | } 15 | 16 | class TaskDetailsViewController: UIViewController{ 17 | 18 | // OUTLETS 19 | @IBOutlet private weak var taskTitleTextField: UITextField! 20 | @IBOutlet private weak var subTasksTextView: UITextView! 21 | @IBOutlet private weak var endDateTextField: UITextField! 22 | @IBOutlet private weak var saveButton: UIBarButtonItem! 23 | @IBOutlet private weak var attachmentCollection: UICollectionView! 24 | 25 | 26 | // VARIABLES 27 | var task : Task? = nil 28 | var endDate : String = .empty 29 | var endDatePicker: UIDatePicker! 30 | var dateFormatter: DateFormatter = DateFormatter() 31 | weak var delegate : TaskDelegate? 32 | var isUpdate: Bool = false 33 | var selectedDateTimeStamp: Double? 34 | var imagesAttached = [UIImage]() 35 | 36 | var cameraHelper = CameraHelper() 37 | 38 | var hapticGenerator: UINotificationFeedbackGenerator? = nil 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | isUpdate = (task != nil) 43 | endDatePicker = UIDatePicker() 44 | endDatePicker.addTarget(self, action: #selector(didPickDate(_:)), for: .valueChanged) 45 | endDatePicker.minimumDate = Date() 46 | endDateTextField.inputView = endDatePicker 47 | dateFormatter.dateStyle = .medium 48 | subTasksTextView.addBorder() 49 | loadTaskForUpdate() 50 | taskTitleTextField.delegate = self 51 | // Tap outside to close the keybord 52 | let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing)) 53 | view.addGestureRecognizer(tap) 54 | saveButton.title = isUpdate ? Constants.Action.update : Constants.Action.add 55 | } 56 | 57 | @IBAction func addImageAttachment(_ sender: Any) { 58 | // opening camera 59 | cameraHelper.openCamera(in: self) { [weak self] image in 60 | guard let image = image else { return } 61 | 62 | // Adding attachment 63 | self?.imagesAttached.append(image) 64 | self?.attachmentCollection.reloadData() 65 | } 66 | } 67 | 68 | @IBAction func saveTapped(_ sender: UIBarButtonItem) { 69 | hapticGenerator = UINotificationFeedbackGenerator() 70 | hapticGenerator?.prepare() 71 | 72 | guard isValidTask() else { 73 | hapticGenerator?.notificationOccurred(.warning) 74 | return 75 | } 76 | guard let task = createTaskBody() else { 77 | self.navigationController?.popViewController(animated: true) 78 | return 79 | } 80 | 81 | hapticGenerator?.notificationOccurred(.success) 82 | 83 | if isUpdate { 84 | self.delegate?.didTapUpdate(task: task) 85 | } else { 86 | self.delegate?.didTapSave(task: task) 87 | } 88 | self.navigationController?.popViewController(animated: true) 89 | 90 | hapticGenerator = nil 91 | } 92 | 93 | /// Function that determines if a task is valid or not. A valid task has both a title and due date. 94 | /// Title: String taken from `taskTitleTextField` 95 | /// endDate : String taken from `endDueDateTextField` 96 | /// - Returns: A bool whether or not the task is valid. 97 | func isValidTask() -> Bool { 98 | if taskTitleTextField.text?.trim().isEmpty ?? true { 99 | Alert.showNoTaskTitle(on: self) 100 | return false 101 | } else if endDateTextField.text?.trim().isEmpty ?? true { 102 | Alert.showNoTaskDueDate(on: self) 103 | return false 104 | } else { 105 | return true 106 | } 107 | } 108 | 109 | // function that `Creates Task body` 110 | /// Title: String taken from `taskTitleTextField` 111 | /// Subtask: String taken from `subTasksTextView` 112 | /// endDate : String taken from `didPickDate method` 113 | func createTaskBody()->Task? { 114 | let title = taskTitleTextField.text?.trim() ?? .empty 115 | let subtask = subTasksTextView.text?.trim() ?? .empty 116 | /// check if we are updating the task or creatiing the task 117 | if self.task == nil { 118 | let mainController = self.delegate as! TodoViewController 119 | self.task = Task(context: mainController.moc) 120 | } 121 | task?.title = title 122 | task?.subTasks = subtask 123 | task?.dueDate = endDate 124 | task?.dueDateTimeStamp = selectedDateTimeStamp ?? 0 125 | task?.attachments = try? NSKeyedArchiver.archivedData(withRootObject: imagesAttached, requiringSecureCoding: false) 126 | task?.isComplete = false 127 | 128 | return task 129 | } 130 | 131 | func loadTaskForUpdate() { 132 | guard let task = self.task else { 133 | subTasksTextView.textColor = .placeholderText 134 | return 135 | } 136 | taskTitleTextField.text = task.title 137 | subTasksTextView.text = task.subTasks 138 | endDateTextField.text = task.dueDate 139 | 140 | // Recover attachments 141 | if let attachments = task.attachments { 142 | imagesAttached = NSKeyedUnarchiver.unarchiveObject(with: attachments) as? [UIImage] ?? [] 143 | } 144 | } 145 | 146 | // IBOUTLET for datepicker 147 | /// function is called when `Date is changed` 148 | /// `Dateformatter` is used to convert `Date` to `String` 149 | @objc func didPickDate(_ sender: UIDatePicker) { 150 | dateFormatter.dateFormat = "MM/dd/yyyy hh:mm a" 151 | let selectedDate = sender.date 152 | self.selectedDateTimeStamp = sender.date.timeIntervalSince1970 153 | endDate = dateFormatter.string(from: selectedDate) 154 | endDateTextField.text = endDate 155 | } 156 | 157 | } 158 | extension TaskDetailsViewController: UITextFieldDelegate, UITextViewDelegate { 159 | func textFieldShouldReturn(_ textField: UITextField) -> Bool { 160 | if textField == taskTitleTextField { 161 | textField.resignFirstResponder() 162 | return true 163 | } 164 | return false 165 | } 166 | 167 | func textViewDidBeginEditing(_ textView: UITextView) { 168 | if textView.textColor == .placeholderText { 169 | textView.text = nil 170 | textView.textColor = .black 171 | } 172 | } 173 | 174 | func textViewDidEndEditing(_ textView: UITextView) { 175 | if textView.text.isEmpty { 176 | textView.text = "Enter your subtasks here" 177 | textView.textColor = .placeholderText 178 | } 179 | } 180 | } 181 | 182 | extension TaskDetailsViewController: UICollectionViewDelegate, UICollectionViewDataSource { 183 | 184 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 185 | return imagesAttached.count 186 | } 187 | 188 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 189 | 190 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.Cell.photoCell, for: indexPath) as! ImageAttachmentCell 191 | let image = imagesAttached[indexPath.row] 192 | 193 | cell.setImage(image) 194 | 195 | return cell 196 | } 197 | 198 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 199 | debugPrint("Click: \(indexPath.row) \(imagesAttached[indexPath.row])") 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /Application/To-Do/Controller/TodoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoViewController.swift 3 | // To-Do 4 | // 5 | // Created by Aaryan Kothari on 29/09/20. 6 | // Copyright © 2020 Aaryan Kothari. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class TodoViewController: UITableViewController { 13 | 14 | //MARK: -------- OUTLETS & VARIABLES -------- 15 | 16 | /// `Tableview` to display list of tasks 17 | @IBOutlet weak var todoTableView: UITableView! 18 | 19 | /// `Sort button` to sort tasks 20 | @IBOutlet weak var sortButton: UIBarButtonItem! 21 | 22 | /// `SearchController` to include search bar 23 | var searchController: UISearchController! 24 | 25 | /// `ResultsController` to display results of specific search 26 | var resultsTableController: ResultsTableController! 27 | 28 | /// `DataSource` for todoTableview 29 | var todoList : [Task] = [] 30 | 31 | /// last task tapped! 32 | var lastIndexTapped : Int = 0 33 | 34 | /// Coredata managed object 35 | var moc: NSManagedObjectContext! 36 | 37 | /// Controller to fetch tasks from core-data 38 | var fetchedResultsController: NSFetchedResultsController! 39 | 40 | /// default fetch request for tasks 41 | lazy var defaultFetchRequest: NSFetchRequest = { 42 | let fetchRequest : NSFetchRequest = Task.fetchRequest() 43 | return fetchRequest 44 | }() 45 | 46 | /// default sort is `Alphabetical ascending` 47 | var currentSelectedSortType: SortTypesAvailable = .sortByNameAsc 48 | 49 | var hapticNotificationGenerator: UINotificationFeedbackGenerator? = nil 50 | 51 | //MARK: -------- View lifecycle methods -------- 52 | 53 | override func viewDidLoad() { 54 | super.viewDidLoad() 55 | showOnboardingIfNeeded() /// present onboarding screen for first time 56 | setupEmptyState() /// show emppty view if no tasks present 57 | loadData() /// Core data setup and population 58 | setupSearchController() /// setup search view controller for searching 59 | } 60 | 61 | override func viewDidAppear(_ animated: Bool) { 62 | super.viewDidAppear(animated) 63 | self.navigationItem.searchController = searchController 64 | } 65 | 66 | /// initialize ManagedObjectContext 67 | func loadData() { 68 | guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } 69 | let persistenceContainer = appDelegate.persistentContainer 70 | moc = persistenceContainer.viewContext 71 | defaultFetchRequest.sortDescriptors = currentSelectedSortType.getSortDescriptor() 72 | defaultFetchRequest.predicate = NSPredicate(format: "isComplete = %d", false) 73 | setupFetchedResultsController(fetchRequest: defaultFetchRequest) 74 | /// reloading the table view with the fetched objects 75 | if let objects = fetchedResultsController.fetchedObjects { 76 | self.todoList = objects 77 | DispatchQueue.main.async { 78 | self.tableView.reloadData() 79 | } 80 | } 81 | } 82 | 83 | /// initialize FetchedResultsController 84 | func setupFetchedResultsController(fetchRequest: NSFetchRequest) { 85 | fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil) 86 | fetchedResultsController.delegate = self 87 | do { 88 | try fetchedResultsController.performFetch() 89 | } catch { 90 | fatalError(error.localizedDescription) 91 | } 92 | } 93 | 94 | //MARK: -------- IBActions -------- 95 | /// perform segue to `TaskDetailsViewController` if `+` tapped 96 | @IBAction func addTasksTapped(_ sender: UIBarButtonItem) { 97 | performSegue(withIdentifier: Constants.Segue.taskToTaskDetail, sender: false) 98 | } 99 | 100 | @IBAction func sortButtonTapped(_ sender: UIBarButtonItem) { 101 | showSortAlertController() 102 | } 103 | 104 | //MARK: -------- STAR - DELETE - COMPLETE - UPDATE -------- 105 | 106 | /// function called when `Star Task` tapped 107 | /// - Parameter index: Which task to star 108 | func starTask(at index : Int){ 109 | todoList[index].isFavourite = todoList[index].isFavourite ? false : true 110 | updateTask() 111 | } 112 | 113 | /// function called when `Delete Task` tapped 114 | /// - Parameter index: Which task to delete 115 | func deleteTask(at index : Int){ 116 | hapticNotificationGenerator = UINotificationFeedbackGenerator() 117 | hapticNotificationGenerator?.prepare() 118 | 119 | let element = todoList.remove(at: index) /// removes task at index 120 | moc.delete(element) /// deleting the object from core data 121 | do { 122 | try moc.save() 123 | hapticNotificationGenerator?.notificationOccurred(.success) 124 | } catch { 125 | todoList.insert(element, at: index) 126 | print(error.localizedDescription) 127 | hapticNotificationGenerator?.notificationOccurred(.error) 128 | } 129 | tableView.reloadData() /// Reload tableview with remaining data 130 | hapticNotificationGenerator = nil 131 | } 132 | 133 | /// Mark a task as complete and remove from the `tableView` 134 | /// - Parameter index: Which task to mark as complete 135 | func completeTask(at index : Int){ 136 | todoList[index].isComplete = true 137 | todoList.remove(at: index) /// removes task at index 138 | updateTask() 139 | tableView.reloadData() 140 | } 141 | 142 | /// Update task 143 | /// function called whenever updating a task is required 144 | func updateTask(){ 145 | hapticNotificationGenerator = UINotificationFeedbackGenerator() 146 | hapticNotificationGenerator?.prepare() 147 | 148 | do { 149 | try moc.save() 150 | hapticNotificationGenerator?.notificationOccurred(.success) 151 | } catch { 152 | print(error.localizedDescription) 153 | hapticNotificationGenerator?.notificationOccurred(.error) 154 | } 155 | loadData() 156 | hapticNotificationGenerator = nil 157 | } 158 | 159 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 160 | if let taskDetailVC = segue.destination as? TaskDetailsViewController { 161 | // Hide the tab bar when new controller is pushed onto the screen 162 | taskDetailVC.hidesBottomBarWhenPushed = true 163 | taskDetailVC.delegate = self 164 | taskDetailVC.task = sender as? Task 165 | } 166 | } 167 | 168 | /// onboarding setup 169 | fileprivate func showOnboardingIfNeeded() { 170 | guard let onboardingController = self.storyboard?.instantiateViewController(identifier: Constants.ViewController.Onboarding) as? OnboardingViewController else { return } 171 | 172 | if !onboardingController.alreadyShown() { 173 | DispatchQueue.main.async { 174 | self.present(onboardingController, animated: true) 175 | } 176 | } 177 | } 178 | 179 | /// search controller setup 180 | fileprivate func setupSearchController() { 181 | resultsTableController = 182 | self.storyboard?.instantiateViewController(withIdentifier: Constants.ViewController.ResultsTable) as? ResultsTableController 183 | resultsTableController.tableView.delegate = self 184 | searchController = UISearchController(searchResultsController: resultsTableController) 185 | searchController.delegate = self 186 | searchController.searchResultsUpdater = self 187 | searchController.searchBar.autocapitalizationType = .none 188 | searchController.searchBar.delegate = self 189 | searchController.view.backgroundColor = .white 190 | } 191 | 192 | fileprivate func setupEmptyState() { 193 | let emptyBackgroundView = EmptyState(.emptyList) 194 | tableView.backgroundView = emptyBackgroundView 195 | tableView.setNeedsLayout() 196 | tableView.layoutIfNeeded() 197 | } 198 | 199 | //MARK: ------ Tableview Datasource methods ------ 200 | 201 | /// function to determine `Number of rows` in tableview 202 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 203 | self.sortButton.isEnabled = self.todoList.count > 0 204 | 205 | if todoList.isEmpty { 206 | tableView.separatorStyle = .none 207 | tableView.backgroundView?.isHidden = false 208 | } else { 209 | tableView.separatorStyle = .singleLine 210 | tableView.backgroundView?.isHidden = true 211 | 212 | } 213 | 214 | return todoList.count 215 | } 216 | 217 | /// function to determine `tableview cell` at a given row 218 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 219 | let cell = tableView.dequeueReusableCell(withIdentifier: Constants.Cell.taskCell, for: indexPath) as! TaskCell 220 | let task = todoList[indexPath.row] 221 | cell.title.text = task.title 222 | cell.subtitle.text = task.dueDate 223 | cell.starImage.isHidden = todoList[indexPath.row].isFavourite ? false : true 224 | return cell 225 | } 226 | 227 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 228 | lastIndexTapped = indexPath.row 229 | let task = todoList[indexPath.row] 230 | performSegue(withIdentifier: Constants.Segue.taskToTaskDetail, sender: task) 231 | } 232 | 233 | 234 | //MARK: ----- Tableview Delagate methods ------ 235 | 236 | /// `UISwipeActionsConfiguration` for delete and star buttons 237 | override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { 238 | 239 | let actions = Constants.Action.self 240 | 241 | // DELETE ACTION 242 | let delete = UIContextualAction(style: .destructive, title: actions.delete) { _,_,_ in 243 | self.deleteTask(at: indexPath.row) 244 | } 245 | 246 | // STAR ACTION 247 | let star = UIContextualAction(style: .normal, title: .empty) { _,_,_ in 248 | self.starTask(at: indexPath.row) 249 | } 250 | star.backgroundColor = .orange 251 | star.title = todoList[indexPath.row].isFavourite ? actions.unstar : actions.star 252 | 253 | let swipeActions = UISwipeActionsConfiguration(actions: [delete,star]) 254 | return swipeActions 255 | } 256 | 257 | /// `UISwipeActionsConfiguration` for completing a task 258 | override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { 259 | let completeTask = UIContextualAction(style: .normal, title: .empty) { (_, _, _) in 260 | self.completeTask(at: indexPath.row) 261 | } 262 | completeTask.backgroundColor = .systemGreen 263 | completeTask.title = Constants.Action.complete 264 | let swipeActions = UISwipeActionsConfiguration(actions: [completeTask]) 265 | 266 | return swipeActions 267 | } 268 | 269 | /// function to determine `View for Footer` in tableview. 270 | override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { 271 | return UIView() /// Returns an empty view. 272 | } 273 | } 274 | 275 | extension TodoViewController: NSFetchedResultsControllerDelegate { 276 | func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 277 | tableView.beginUpdates() 278 | } 279 | 280 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 281 | tableView.endUpdates() 282 | } 283 | 284 | func controller(_ controller: NSFetchedResultsController, 285 | didChange anObject: Any, 286 | at indexPath: IndexPath?, 287 | for type: NSFetchedResultsChangeType, 288 | newIndexPath: IndexPath?) { 289 | switch type { 290 | case .insert: 291 | tableView.insertRows(at: [newIndexPath!], with: .fade) 292 | case .delete: 293 | tableView.deleteRows(at: [indexPath!], with: .fade) 294 | case .update: 295 | tableView.reloadRows(at: [indexPath!], with: .fade) 296 | case .move: 297 | break 298 | @unknown default: 299 | break 300 | } 301 | } 302 | } 303 | 304 | //MARK: - TaskDelegate 305 | /// protocol for `saving` or `updating` `Tasks` 306 | extension TodoViewController : TaskDelegate{ 307 | func didTapSave(task: Task) { 308 | todoList.append(task) 309 | do { 310 | try moc.save() 311 | } catch { 312 | todoList.removeLast() 313 | print(error.localizedDescription) 314 | } 315 | loadData() 316 | } 317 | 318 | func didTapUpdate(task: Task) { 319 | /// Reload tableview with new data 320 | updateTask() 321 | } 322 | 323 | 324 | } 325 | 326 | // MARK: - Search Bar Delegate 327 | 328 | extension TodoViewController: UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate { 329 | func updateSearchResults(for searchController: UISearchController) { 330 | /// perform search only when there is some text 331 | if let text: String = searchController.searchBar.text?.lowercased(), text.count > 0, let resultsController = searchController.searchResultsController as? ResultsTableController { 332 | resultsController.todoList = todoList.filter({ (task) -> Bool in 333 | if task.title?.lowercased().contains(text) == true || task.subTasks?.lowercased().contains(text) == true { 334 | return true 335 | } 336 | return false 337 | }) 338 | let fetchRequest : NSFetchRequest = Task.fetchRequest() 339 | fetchRequest.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)] 340 | fetchRequest.predicate = NSPredicate(format: "title contains[c] %@", text) 341 | setupFetchedResultsController(fetchRequest: fetchRequest) 342 | resultsController.tableView.reloadData() 343 | } else { 344 | /// default case when text not available or text length is zero 345 | tableView.reloadData() 346 | } 347 | } 348 | 349 | func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { 350 | tableView.reloadData() 351 | } 352 | } 353 | 354 | // MARK:- Sort functionality 355 | extension TodoViewController { 356 | 357 | func showSortAlertController() { 358 | let alertController = UIAlertController(title: nil, message: "Choose sort type", preferredStyle: .actionSheet) 359 | 360 | SortTypesAvailable.allCases.forEach { (sortType) in 361 | let action = UIAlertAction(title: sortType.getTitleForSortType(), style: .default) { (_) in 362 | self.currentSelectedSortType = sortType 363 | self.loadData() 364 | } 365 | alertController.addAction(action) 366 | } 367 | 368 | let cancelAction = UIAlertAction(title: Constants.Action.cancel, style: .cancel, handler: nil) 369 | alertController.addAction(cancelAction) 370 | self.present(alertController, animated: true) 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /Application/To-Do.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 07F46C8525277C7E007DC6AC /* SortTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07F46C8425277C7E007DC6AC /* SortTypes.swift */; }; 11 | 3307EC692537AFB100963CC4 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3307EC682537AFB100963CC4 /* Extensions.swift */; }; 12 | 4F953E502531C25E0027144C /* EmptyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F953E4F2531C25E0027144C /* EmptyState.swift */; }; 13 | 5BBE559E2523A42700090F87 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE559D2523A42700090F87 /* AppDelegate.swift */; }; 14 | 5BBE55A02523A42700090F87 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE559F2523A42700090F87 /* SceneDelegate.swift */; }; 15 | 5BBE55A52523A42700090F87 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBE55A32523A42700090F87 /* Main.storyboard */; }; 16 | 5BBE55A82523A42700090F87 /* To_Do.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE55A62523A42700090F87 /* To_Do.xcdatamodeld */; }; 17 | 5BBE55AA2523A42C00090F87 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BBE55A92523A42C00090F87 /* Assets.xcassets */; }; 18 | 5BBE55AD2523A42C00090F87 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BBE55AB2523A42C00090F87 /* LaunchScreen.storyboard */; }; 19 | 5BBE55B72523A90A00090F87 /* TodoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE55B62523A90A00090F87 /* TodoViewController.swift */; }; 20 | 5BBE55C925245DBF00090F87 /* TaskDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE55C825245DBF00090F87 /* TaskDetailsViewController.swift */; }; 21 | 5BBE55CB2524623900090F87 /* TextviewBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BBE55CA2524623900090F87 /* TextviewBorder.swift */; }; 22 | 5BC2F07B2532D10F00B648C2 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC2F07A2532D10F00B648C2 /* Constants.swift */; }; 23 | 603B4C66252647BD00579EC6 /* ResultsTableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 603B4C65252647BD00579EC6 /* ResultsTableController.swift */; }; 24 | 6555BCAC252C3FAC00E7DF3D /* TaskTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6555BCAB252C3FAC00E7DF3D /* TaskTests.swift */; }; 25 | 8E6209BD25291AF0004CE4B9 /* OnboardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E6209BC25291AF0004CE4B9 /* OnboardingViewController.swift */; }; 26 | AB95E033252712C4008C4E48 /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB95E032252712C4008C4E48 /* TaskCell.swift */; }; 27 | E924B484252D53F3004FD116 /* CameraHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E924B483252D53F3004FD116 /* CameraHelper.swift */; }; 28 | E924B487252D63FE004FD116 /* ImageAttachmentCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E924B486252D63FD004FD116 /* ImageAttachmentCell.swift */; }; 29 | FF2303B42529183700B88831 /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF2303B32529183700B88831 /* Alert.swift */; }; 30 | FFA6683A2530E07F004E1DE3 /* TaskHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA668392530E07F004E1DE3 /* TaskHistoryViewController.swift */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXContainerItemProxy section */ 34 | 6555BCAE252C3FAC00E7DF3D /* PBXContainerItemProxy */ = { 35 | isa = PBXContainerItemProxy; 36 | containerPortal = 5BBE55922523A42700090F87 /* Project object */; 37 | proxyType = 1; 38 | remoteGlobalIDString = 5BBE55992523A42700090F87; 39 | remoteInfo = "To-Do"; 40 | }; 41 | /* End PBXContainerItemProxy section */ 42 | 43 | /* Begin PBXFileReference section */ 44 | 07F46C8425277C7E007DC6AC /* SortTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortTypes.swift; sourceTree = ""; }; 45 | 3307EC682537AFB100963CC4 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 46 | 4F953E4F2531C25E0027144C /* EmptyState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyState.swift; sourceTree = ""; }; 47 | 5BBE559A2523A42700090F87 /* To-Do.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "To-Do.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 5BBE559D2523A42700090F87 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | 5BBE559F2523A42700090F87 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 50 | 5BBE55A42523A42700090F87 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 51 | 5BBE55A72523A42700090F87 /* To_Do.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = To_Do.xcdatamodel; sourceTree = ""; }; 52 | 5BBE55A92523A42C00090F87 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | 5BBE55AC2523A42C00090F87 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 54 | 5BBE55AE2523A42C00090F87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = "To-Do/Helpers/Info.plist"; sourceTree = SOURCE_ROOT; }; 55 | 5BBE55B62523A90A00090F87 /* TodoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoViewController.swift; sourceTree = ""; }; 56 | 5BBE55C825245DBF00090F87 /* TaskDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskDetailsViewController.swift; sourceTree = ""; }; 57 | 5BBE55CA2524623900090F87 /* TextviewBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextviewBorder.swift; sourceTree = ""; }; 58 | 5BC2F07A2532D10F00B648C2 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 59 | 603B4C65252647BD00579EC6 /* ResultsTableController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsTableController.swift; sourceTree = ""; }; 60 | 6555BCA9252C3FAC00E7DF3D /* To-DoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "To-DoTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | 6555BCAB252C3FAC00E7DF3D /* TaskTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskTests.swift; sourceTree = ""; }; 62 | 6555BCAD252C3FAC00E7DF3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 8E6209BC25291AF0004CE4B9 /* OnboardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewController.swift; sourceTree = ""; }; 64 | AB95E032252712C4008C4E48 /* TaskCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCell.swift; sourceTree = ""; }; 65 | E924B483252D53F3004FD116 /* CameraHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraHelper.swift; sourceTree = ""; }; 66 | E924B486252D63FD004FD116 /* ImageAttachmentCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAttachmentCell.swift; sourceTree = ""; }; 67 | E924B48A252D686A004FD116 /* To_Do2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = To_Do2.xcdatamodel; sourceTree = ""; }; 68 | FF2303B32529183700B88831 /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; 69 | FFA668392530E07F004E1DE3 /* TaskHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskHistoryViewController.swift; sourceTree = ""; }; 70 | /* End PBXFileReference section */ 71 | 72 | /* Begin PBXFrameworksBuildPhase section */ 73 | 5BBE55972523A42700090F87 /* Frameworks */ = { 74 | isa = PBXFrameworksBuildPhase; 75 | buildActionMask = 2147483647; 76 | files = ( 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | 6555BCA6252C3FAC00E7DF3D /* Frameworks */ = { 81 | isa = PBXFrameworksBuildPhase; 82 | buildActionMask = 2147483647; 83 | files = ( 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 5BBE55912523A42700090F87 = { 91 | isa = PBXGroup; 92 | children = ( 93 | 5BBE559C2523A42700090F87 /* To-Do */, 94 | 6555BCAA252C3FAC00E7DF3D /* To-DoTests */, 95 | 5BBE559B2523A42700090F87 /* Products */, 96 | ); 97 | sourceTree = ""; 98 | }; 99 | 5BBE559B2523A42700090F87 /* Products */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 5BBE559A2523A42700090F87 /* To-Do.app */, 103 | 6555BCA9252C3FAC00E7DF3D /* To-DoTests.xctest */, 104 | ); 105 | name = Products; 106 | sourceTree = ""; 107 | }; 108 | 5BBE559C2523A42700090F87 /* To-Do */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 5BBE55C1252453E500090F87 /* Helpers */, 112 | 5BBE55C0252453D700090F87 /* Controller */, 113 | 5BBE55BF252453CB00090F87 /* View */, 114 | 5BBE55BE252453C400090F87 /* Model */, 115 | ); 116 | path = "To-Do"; 117 | sourceTree = ""; 118 | }; 119 | 5BBE55BE252453C400090F87 /* Model */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 5BBE55A62523A42700090F87 /* To_Do.xcdatamodeld */, 123 | 07F46C8425277C7E007DC6AC /* SortTypes.swift */, 124 | ); 125 | path = Model; 126 | sourceTree = ""; 127 | }; 128 | 5BBE55BF252453CB00090F87 /* View */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 5BC2F0822532D86900B648C2 /* Cells */, 132 | 4F953E4F2531C25E0027144C /* EmptyState.swift */, 133 | 5BBE55A32523A42700090F87 /* Main.storyboard */, 134 | 5BBE55A92523A42C00090F87 /* Assets.xcassets */, 135 | 5BBE55AB2523A42C00090F87 /* LaunchScreen.storyboard */, 136 | ); 137 | path = View; 138 | sourceTree = ""; 139 | }; 140 | 5BBE55C0252453D700090F87 /* Controller */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | 5BBE55B62523A90A00090F87 /* TodoViewController.swift */, 144 | 5BBE55C825245DBF00090F87 /* TaskDetailsViewController.swift */, 145 | 603B4C65252647BD00579EC6 /* ResultsTableController.swift */, 146 | 8E6209BC25291AF0004CE4B9 /* OnboardingViewController.swift */, 147 | FFA668392530E07F004E1DE3 /* TaskHistoryViewController.swift */, 148 | ); 149 | path = Controller; 150 | sourceTree = ""; 151 | }; 152 | 5BBE55C1252453E500090F87 /* Helpers */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 5BBE55AE2523A42C00090F87 /* Info.plist */, 156 | 5BBE559D2523A42700090F87 /* AppDelegate.swift */, 157 | 5BBE559F2523A42700090F87 /* SceneDelegate.swift */, 158 | 5BBE55CA2524623900090F87 /* TextviewBorder.swift */, 159 | FF2303B32529183700B88831 /* Alert.swift */, 160 | E924B483252D53F3004FD116 /* CameraHelper.swift */, 161 | 5BC2F07A2532D10F00B648C2 /* Constants.swift */, 162 | 3307EC682537AFB100963CC4 /* Extensions.swift */, 163 | ); 164 | path = Helpers; 165 | sourceTree = ""; 166 | }; 167 | 5BC2F0822532D86900B648C2 /* Cells */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | E924B486252D63FD004FD116 /* ImageAttachmentCell.swift */, 171 | AB95E032252712C4008C4E48 /* TaskCell.swift */, 172 | ); 173 | path = Cells; 174 | sourceTree = ""; 175 | }; 176 | 6555BCAA252C3FAC00E7DF3D /* To-DoTests */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 6555BCAB252C3FAC00E7DF3D /* TaskTests.swift */, 180 | 6555BCAD252C3FAC00E7DF3D /* Info.plist */, 181 | ); 182 | path = "To-DoTests"; 183 | sourceTree = ""; 184 | }; 185 | /* End PBXGroup section */ 186 | 187 | /* Begin PBXNativeTarget section */ 188 | 5BBE55992523A42700090F87 /* To-Do */ = { 189 | isa = PBXNativeTarget; 190 | buildConfigurationList = 5BBE55B12523A42C00090F87 /* Build configuration list for PBXNativeTarget "To-Do" */; 191 | buildPhases = ( 192 | 5BBE55962523A42700090F87 /* Sources */, 193 | 5BBE55972523A42700090F87 /* Frameworks */, 194 | 5BBE55982523A42700090F87 /* Resources */, 195 | ); 196 | buildRules = ( 197 | ); 198 | dependencies = ( 199 | ); 200 | name = "To-Do"; 201 | productName = "To-Do"; 202 | productReference = 5BBE559A2523A42700090F87 /* To-Do.app */; 203 | productType = "com.apple.product-type.application"; 204 | }; 205 | 6555BCA8252C3FAC00E7DF3D /* To-DoTests */ = { 206 | isa = PBXNativeTarget; 207 | buildConfigurationList = 6555BCB2252C3FAC00E7DF3D /* Build configuration list for PBXNativeTarget "To-DoTests" */; 208 | buildPhases = ( 209 | 6555BCA5252C3FAC00E7DF3D /* Sources */, 210 | 6555BCA6252C3FAC00E7DF3D /* Frameworks */, 211 | 6555BCA7252C3FAC00E7DF3D /* Resources */, 212 | ); 213 | buildRules = ( 214 | ); 215 | dependencies = ( 216 | 6555BCAF252C3FAC00E7DF3D /* PBXTargetDependency */, 217 | ); 218 | name = "To-DoTests"; 219 | productName = "To-DoTests"; 220 | productReference = 6555BCA9252C3FAC00E7DF3D /* To-DoTests.xctest */; 221 | productType = "com.apple.product-type.bundle.unit-test"; 222 | }; 223 | /* End PBXNativeTarget section */ 224 | 225 | /* Begin PBXProject section */ 226 | 5BBE55922523A42700090F87 /* Project object */ = { 227 | isa = PBXProject; 228 | attributes = { 229 | LastSwiftUpdateCheck = 1200; 230 | LastUpgradeCheck = 1170; 231 | ORGANIZATIONNAME = "Aaryan Kothari"; 232 | TargetAttributes = { 233 | 5BBE55992523A42700090F87 = { 234 | CreatedOnToolsVersion = 11.7; 235 | }; 236 | 6555BCA8252C3FAC00E7DF3D = { 237 | CreatedOnToolsVersion = 12.0; 238 | TestTargetID = 5BBE55992523A42700090F87; 239 | }; 240 | }; 241 | }; 242 | buildConfigurationList = 5BBE55952523A42700090F87 /* Build configuration list for PBXProject "To-Do" */; 243 | compatibilityVersion = "Xcode 9.3"; 244 | developmentRegion = en; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | en, 248 | Base, 249 | ); 250 | mainGroup = 5BBE55912523A42700090F87; 251 | productRefGroup = 5BBE559B2523A42700090F87 /* Products */; 252 | projectDirPath = ""; 253 | projectRoot = ""; 254 | targets = ( 255 | 5BBE55992523A42700090F87 /* To-Do */, 256 | 6555BCA8252C3FAC00E7DF3D /* To-DoTests */, 257 | ); 258 | }; 259 | /* End PBXProject section */ 260 | 261 | /* Begin PBXResourcesBuildPhase section */ 262 | 5BBE55982523A42700090F87 /* Resources */ = { 263 | isa = PBXResourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | 5BBE55AD2523A42C00090F87 /* LaunchScreen.storyboard in Resources */, 267 | 5BBE55AA2523A42C00090F87 /* Assets.xcassets in Resources */, 268 | 5BBE55A52523A42700090F87 /* Main.storyboard in Resources */, 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | 6555BCA7252C3FAC00E7DF3D /* Resources */ = { 273 | isa = PBXResourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXResourcesBuildPhase section */ 280 | 281 | /* Begin PBXSourcesBuildPhase section */ 282 | 5BBE55962523A42700090F87 /* Sources */ = { 283 | isa = PBXSourcesBuildPhase; 284 | buildActionMask = 2147483647; 285 | files = ( 286 | 07F46C8525277C7E007DC6AC /* SortTypes.swift in Sources */, 287 | 4F953E502531C25E0027144C /* EmptyState.swift in Sources */, 288 | 5BBE55B72523A90A00090F87 /* TodoViewController.swift in Sources */, 289 | E924B484252D53F3004FD116 /* CameraHelper.swift in Sources */, 290 | 5BBE559E2523A42700090F87 /* AppDelegate.swift in Sources */, 291 | 5BBE55A02523A42700090F87 /* SceneDelegate.swift in Sources */, 292 | 5BBE55A82523A42700090F87 /* To_Do.xcdatamodeld in Sources */, 293 | 5BC2F07B2532D10F00B648C2 /* Constants.swift in Sources */, 294 | 5BBE55CB2524623900090F87 /* TextviewBorder.swift in Sources */, 295 | E924B487252D63FE004FD116 /* ImageAttachmentCell.swift in Sources */, 296 | 603B4C66252647BD00579EC6 /* ResultsTableController.swift in Sources */, 297 | 8E6209BD25291AF0004CE4B9 /* OnboardingViewController.swift in Sources */, 298 | 5BBE55C925245DBF00090F87 /* TaskDetailsViewController.swift in Sources */, 299 | FF2303B42529183700B88831 /* Alert.swift in Sources */, 300 | 3307EC692537AFB100963CC4 /* Extensions.swift in Sources */, 301 | FFA6683A2530E07F004E1DE3 /* TaskHistoryViewController.swift in Sources */, 302 | AB95E033252712C4008C4E48 /* TaskCell.swift in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | 6555BCA5252C3FAC00E7DF3D /* Sources */ = { 307 | isa = PBXSourcesBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | 6555BCAC252C3FAC00E7DF3D /* TaskTests.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | /* End PBXSourcesBuildPhase section */ 315 | 316 | /* Begin PBXTargetDependency section */ 317 | 6555BCAF252C3FAC00E7DF3D /* PBXTargetDependency */ = { 318 | isa = PBXTargetDependency; 319 | target = 5BBE55992523A42700090F87 /* To-Do */; 320 | targetProxy = 6555BCAE252C3FAC00E7DF3D /* PBXContainerItemProxy */; 321 | }; 322 | /* End PBXTargetDependency section */ 323 | 324 | /* Begin PBXVariantGroup section */ 325 | 5BBE55A32523A42700090F87 /* Main.storyboard */ = { 326 | isa = PBXVariantGroup; 327 | children = ( 328 | 5BBE55A42523A42700090F87 /* Base */, 329 | ); 330 | name = Main.storyboard; 331 | sourceTree = ""; 332 | }; 333 | 5BBE55AB2523A42C00090F87 /* LaunchScreen.storyboard */ = { 334 | isa = PBXVariantGroup; 335 | children = ( 336 | 5BBE55AC2523A42C00090F87 /* Base */, 337 | ); 338 | name = LaunchScreen.storyboard; 339 | sourceTree = ""; 340 | }; 341 | /* End PBXVariantGroup section */ 342 | 343 | /* Begin XCBuildConfiguration section */ 344 | 5BBE55AF2523A42C00090F87 /* Debug */ = { 345 | isa = XCBuildConfiguration; 346 | buildSettings = { 347 | ALWAYS_SEARCH_USER_PATHS = NO; 348 | CLANG_ANALYZER_NONNULL = YES; 349 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 350 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 351 | CLANG_CXX_LIBRARY = "libc++"; 352 | CLANG_ENABLE_MODULES = YES; 353 | CLANG_ENABLE_OBJC_ARC = YES; 354 | CLANG_ENABLE_OBJC_WEAK = YES; 355 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 356 | CLANG_WARN_BOOL_CONVERSION = YES; 357 | CLANG_WARN_COMMA = YES; 358 | CLANG_WARN_CONSTANT_CONVERSION = YES; 359 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 360 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 361 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 362 | CLANG_WARN_EMPTY_BODY = YES; 363 | CLANG_WARN_ENUM_CONVERSION = YES; 364 | CLANG_WARN_INFINITE_RECURSION = YES; 365 | CLANG_WARN_INT_CONVERSION = YES; 366 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 367 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 368 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 369 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 370 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 371 | CLANG_WARN_STRICT_PROTOTYPES = YES; 372 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 373 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 374 | CLANG_WARN_UNREACHABLE_CODE = YES; 375 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 376 | COPY_PHASE_STRIP = NO; 377 | DEBUG_INFORMATION_FORMAT = dwarf; 378 | ENABLE_STRICT_OBJC_MSGSEND = YES; 379 | ENABLE_TESTABILITY = YES; 380 | GCC_C_LANGUAGE_STANDARD = gnu11; 381 | GCC_DYNAMIC_NO_PIC = NO; 382 | GCC_NO_COMMON_BLOCKS = YES; 383 | GCC_OPTIMIZATION_LEVEL = 0; 384 | GCC_PREPROCESSOR_DEFINITIONS = ( 385 | "DEBUG=1", 386 | "$(inherited)", 387 | ); 388 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 389 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 390 | GCC_WARN_UNDECLARED_SELECTOR = YES; 391 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 392 | GCC_WARN_UNUSED_FUNCTION = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 13.7; 395 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 396 | MTL_FAST_MATH = YES; 397 | ONLY_ACTIVE_ARCH = YES; 398 | SDKROOT = iphoneos; 399 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 400 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 401 | }; 402 | name = Debug; 403 | }; 404 | 5BBE55B02523A42C00090F87 /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ALWAYS_SEARCH_USER_PATHS = NO; 408 | CLANG_ANALYZER_NONNULL = YES; 409 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 410 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 411 | CLANG_CXX_LIBRARY = "libc++"; 412 | CLANG_ENABLE_MODULES = YES; 413 | CLANG_ENABLE_OBJC_ARC = YES; 414 | CLANG_ENABLE_OBJC_WEAK = YES; 415 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 416 | CLANG_WARN_BOOL_CONVERSION = YES; 417 | CLANG_WARN_COMMA = YES; 418 | CLANG_WARN_CONSTANT_CONVERSION = YES; 419 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 420 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 421 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 422 | CLANG_WARN_EMPTY_BODY = YES; 423 | CLANG_WARN_ENUM_CONVERSION = YES; 424 | CLANG_WARN_INFINITE_RECURSION = YES; 425 | CLANG_WARN_INT_CONVERSION = YES; 426 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 427 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 428 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 429 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 430 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 431 | CLANG_WARN_STRICT_PROTOTYPES = YES; 432 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 433 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 434 | CLANG_WARN_UNREACHABLE_CODE = YES; 435 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 436 | COPY_PHASE_STRIP = NO; 437 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 438 | ENABLE_NS_ASSERTIONS = NO; 439 | ENABLE_STRICT_OBJC_MSGSEND = YES; 440 | GCC_C_LANGUAGE_STANDARD = gnu11; 441 | GCC_NO_COMMON_BLOCKS = YES; 442 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 443 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 444 | GCC_WARN_UNDECLARED_SELECTOR = YES; 445 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 446 | GCC_WARN_UNUSED_FUNCTION = YES; 447 | GCC_WARN_UNUSED_VARIABLE = YES; 448 | IPHONEOS_DEPLOYMENT_TARGET = 13.7; 449 | MTL_ENABLE_DEBUG_INFO = NO; 450 | MTL_FAST_MATH = YES; 451 | SDKROOT = iphoneos; 452 | SWIFT_COMPILATION_MODE = wholemodule; 453 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 454 | VALIDATE_PRODUCT = YES; 455 | }; 456 | name = Release; 457 | }; 458 | 5BBE55B22523A42C00090F87 /* Debug */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 462 | CODE_SIGN_STYLE = Automatic; 463 | DEVELOPMENT_TEAM = F8CHS6PHQS; 464 | ENABLE_TESTABILITY = YES; 465 | INFOPLIST_FILE = "To-Do/Helpers/Info.plist"; 466 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 467 | LD_RUNPATH_SEARCH_PATHS = ( 468 | "$(inherited)", 469 | "@executable_path/Frameworks", 470 | ); 471 | PRODUCT_BUNDLE_IDENTIFIER = "ak.To-Do"; 472 | PRODUCT_NAME = "$(TARGET_NAME)"; 473 | SWIFT_VERSION = 5.0; 474 | TARGETED_DEVICE_FAMILY = "1,2"; 475 | }; 476 | name = Debug; 477 | }; 478 | 5BBE55B32523A42C00090F87 /* Release */ = { 479 | isa = XCBuildConfiguration; 480 | buildSettings = { 481 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 482 | CODE_SIGN_STYLE = Automatic; 483 | DEVELOPMENT_TEAM = F8CHS6PHQS; 484 | ENABLE_TESTABILITY = YES; 485 | INFOPLIST_FILE = "To-Do/Helpers/Info.plist"; 486 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 487 | LD_RUNPATH_SEARCH_PATHS = ( 488 | "$(inherited)", 489 | "@executable_path/Frameworks", 490 | ); 491 | PRODUCT_BUNDLE_IDENTIFIER = "ak.To-Do"; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | SWIFT_VERSION = 5.0; 494 | TARGETED_DEVICE_FAMILY = "1,2"; 495 | }; 496 | name = Release; 497 | }; 498 | 6555BCB0252C3FAC00E7DF3D /* Debug */ = { 499 | isa = XCBuildConfiguration; 500 | buildSettings = { 501 | BUNDLE_LOADER = "$(TEST_HOST)"; 502 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 503 | CODE_SIGN_STYLE = Automatic; 504 | INFOPLIST_FILE = "To-DoTests/Info.plist"; 505 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 506 | LD_RUNPATH_SEARCH_PATHS = ( 507 | "$(inherited)", 508 | "@executable_path/Frameworks", 509 | "@loader_path/Frameworks", 510 | ); 511 | PRODUCT_BUNDLE_IDENTIFIER = "ak.To-Do.To-DoTests"; 512 | PRODUCT_NAME = "$(TARGET_NAME)"; 513 | SWIFT_VERSION = 5.0; 514 | TARGETED_DEVICE_FAMILY = "1,2"; 515 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/To-Do.app/To-Do"; 516 | }; 517 | name = Debug; 518 | }; 519 | 6555BCB1252C3FAC00E7DF3D /* Release */ = { 520 | isa = XCBuildConfiguration; 521 | buildSettings = { 522 | BUNDLE_LOADER = "$(TEST_HOST)"; 523 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 524 | CODE_SIGN_STYLE = Automatic; 525 | INFOPLIST_FILE = "To-DoTests/Info.plist"; 526 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 527 | LD_RUNPATH_SEARCH_PATHS = ( 528 | "$(inherited)", 529 | "@executable_path/Frameworks", 530 | "@loader_path/Frameworks", 531 | ); 532 | PRODUCT_BUNDLE_IDENTIFIER = "ak.To-Do.To-DoTests"; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SWIFT_VERSION = 5.0; 535 | TARGETED_DEVICE_FAMILY = "1,2"; 536 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/To-Do.app/To-Do"; 537 | }; 538 | name = Release; 539 | }; 540 | /* End XCBuildConfiguration section */ 541 | 542 | /* Begin XCConfigurationList section */ 543 | 5BBE55952523A42700090F87 /* Build configuration list for PBXProject "To-Do" */ = { 544 | isa = XCConfigurationList; 545 | buildConfigurations = ( 546 | 5BBE55AF2523A42C00090F87 /* Debug */, 547 | 5BBE55B02523A42C00090F87 /* Release */, 548 | ); 549 | defaultConfigurationIsVisible = 0; 550 | defaultConfigurationName = Release; 551 | }; 552 | 5BBE55B12523A42C00090F87 /* Build configuration list for PBXNativeTarget "To-Do" */ = { 553 | isa = XCConfigurationList; 554 | buildConfigurations = ( 555 | 5BBE55B22523A42C00090F87 /* Debug */, 556 | 5BBE55B32523A42C00090F87 /* Release */, 557 | ); 558 | defaultConfigurationIsVisible = 0; 559 | defaultConfigurationName = Release; 560 | }; 561 | 6555BCB2252C3FAC00E7DF3D /* Build configuration list for PBXNativeTarget "To-DoTests" */ = { 562 | isa = XCConfigurationList; 563 | buildConfigurations = ( 564 | 6555BCB0252C3FAC00E7DF3D /* Debug */, 565 | 6555BCB1252C3FAC00E7DF3D /* Release */, 566 | ); 567 | defaultConfigurationIsVisible = 0; 568 | defaultConfigurationName = Release; 569 | }; 570 | /* End XCConfigurationList section */ 571 | 572 | /* Begin XCVersionGroup section */ 573 | 5BBE55A62523A42700090F87 /* To_Do.xcdatamodeld */ = { 574 | isa = XCVersionGroup; 575 | children = ( 576 | E924B48A252D686A004FD116 /* To_Do2.xcdatamodel */, 577 | 5BBE55A72523A42700090F87 /* To_Do.xcdatamodel */, 578 | ); 579 | currentVersion = E924B48A252D686A004FD116 /* To_Do2.xcdatamodel */; 580 | path = To_Do.xcdatamodeld; 581 | sourceTree = ""; 582 | versionGroupType = wrapper.xcdatamodel; 583 | }; 584 | /* End XCVersionGroup section */ 585 | }; 586 | rootObject = 5BBE55922523A42700090F87 /* Project object */; 587 | } 588 | -------------------------------------------------------------------------------- /Application/To-Do/View/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 150 | 156 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 334 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 370 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 406 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 444 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 559 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 642 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | --------------------------------------------------------------------------------