├── .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 |
--------------------------------------------------------------------------------