├── PersonalLogs
├── Assets.xcassets
│ ├── Contents.json
│ ├── logo.imageset
│ │ ├── logo.png
│ │ ├── logoDarkMode.png
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── icon-20.png
│ │ ├── icon-29.png
│ │ ├── icon-40.png
│ │ ├── icon-76.png
│ │ ├── icon-1024.png
│ │ ├── icon-20@2x.png
│ │ ├── icon-20@3x.png
│ │ ├── icon-29@2x.png
│ │ ├── icon-29@3x.png
│ │ ├── icon-40@2x.png
│ │ ├── icon-40@3x.png
│ │ ├── icon-60@2x.png
│ │ ├── icon-60@3x.png
│ │ ├── icon-76@2x.png
│ │ ├── icon-83.5@2x.png
│ │ └── Contents.json
│ ├── emptyState.imageset
│ │ ├── emptyState.png
│ │ ├── emptyStateDarkMode.png
│ │ └── Contents.json
│ ├── textColor.colorset
│ │ └── Contents.json
│ ├── primaryActionColor.colorset
│ │ └── Contents.json
│ ├── secundaryActionColor.colorset
│ │ └── Contents.json
│ ├── backgroundColor.colorset
│ │ └── Contents.json
│ └── backgroundCellColor.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Protocols
│ ├── NoteDelegate.swift
│ ├── NoteListDelegate.swift
│ ├── RepositoryItem.swift
│ └── Repository.swift
├── Model
│ ├── NoteRepository.swift
│ ├── OnBoardingStatus.swift
│ └── Note.swift
├── Extensions
│ ├── ColorExtension.swift
│ └── FileManagerExtension.swift
├── ViewControllers
│ ├── OnBoardingViewController.swift
│ ├── NoteViewController.swift
│ └── ListViewController.swift
├── Helpers
│ ├── Keychain
│ │ ├── KeychainHelper.swift
│ │ └── KeychainPasswordItem.swift
│ └── FileHelper.swift
├── AppDelegate.swift
├── Info.plist
├── SceneDelegate.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
└── View
│ ├── OnBoardingView.swift
│ ├── NoteView.swift
│ └── ListView.swift
├── PersonalLogsTests
├── Info.plist
└── PersonalLogsTests.swift
├── LICENSE
├── .gitignore
├── README.md
└── PersonalLogs.xcodeproj
└── project.pbxproj
/PersonalLogs/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/PersonalLogs/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/logo.imageset/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/logo.imageset/logo.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-20.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-29.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-40.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-76.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/logo.imageset/logoDarkMode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/logo.imageset/logoDarkMode.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/emptyState.imageset/emptyState.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/emptyState.imageset/emptyState.png
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/emptyState.imageset/emptyStateDarkMode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GabrielaBezerra/PersonalLogs/HEAD/PersonalLogs/Assets.xcassets/emptyState.imageset/emptyStateDarkMode.png
--------------------------------------------------------------------------------
/PersonalLogs/Protocols/NoteDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteDelegate.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 26/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol NoteDelegate: AnyObject {
12 | func didChange(title: String, body: String)
13 | }
14 |
--------------------------------------------------------------------------------
/PersonalLogs/Model/NoteRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotesRepository.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class NoteRepository: Repository {
12 | typealias Item = Note
13 | var items: [Note] = []
14 | }
15 |
--------------------------------------------------------------------------------
/PersonalLogs/Protocols/NoteListDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteListDelegate.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 26/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol NoteListDelegate: AnyObject {
12 | func didSelectNote(with id: UUID)
13 | func deleteNote(for id: UUID)
14 | func askForPasswordToLock(for id: UUID)
15 | func askForPasswordToUnlock(for id: UUID)
16 | }
17 |
--------------------------------------------------------------------------------
/PersonalLogs/Model/OnBoardingStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnBoardingStatus.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 01/06/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //MARK: - Codable
12 | struct OnBoardingStatus: Codable {
13 | static let plistName: String = "OnBoardingStatus"
14 |
15 | let firstLaunch: Bool = true
16 | let firstLaunchTimestamp: TimeInterval = Date().timeIntervalSince1970
17 |
18 | private enum CodingKeys: String, CodingKey {
19 | case firstLaunch, firstLaunchTimestamp
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/PersonalLogs/Extensions/ColorExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorExtensions.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 | static let primaryAction: UIColor = UIColor(named: "primaryActionColor")!
13 | static let secundaryAction: UIColor = UIColor(named: "secundaryActionColor")!
14 | static let text: UIColor = UIColor(named: "textColor")!
15 | static let background: UIColor = UIColor(named: "backgroundColor")!
16 | static let backgroundCell: UIColor = UIColor(named: "backgroundCellColor")!
17 | }
18 |
--------------------------------------------------------------------------------
/PersonalLogs/ViewControllers/OnBoardingViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnBoardingViewController.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 01/06/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class OnBoardingViewController: UIViewController {
13 |
14 | lazy var onBoardingView: OnBoardingView = {
15 | let view = OnBoardingView()
16 | view.dismissAction = {
17 | self.dismiss(animated: true, completion: nil)
18 | }
19 | return view
20 | }()
21 |
22 | override func loadView() {
23 | self.view = onBoardingView
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/PersonalLogsTests/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 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/textColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.090",
9 | "green" : "0.169",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.874",
27 | "green" : "1.000",
28 | "red" : "0.916"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/primaryActionColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.349",
9 | "green" : "0.627",
10 | "red" : "0.435"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.391",
27 | "green" : "0.742",
28 | "red" : "0.503"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/secundaryActionColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.231",
9 | "green" : "0.141",
10 | "red" : "0.729"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.252",
27 | "green" : "0.147",
28 | "red" : "0.849"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/backgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.800",
9 | "green" : "1.000",
10 | "red" : "0.880"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.082",
27 | "green" : "0.138",
28 | "red" : "0.104"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/backgroundCellColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "display-p3",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.581",
9 | "green" : "0.900",
10 | "red" : "0.707"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "display-p3",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0.148",
27 | "green" : "0.253",
28 | "red" : "0.189"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Gabriela Bezerra
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 |
--------------------------------------------------------------------------------
/PersonalLogsTests/PersonalLogsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PersonalLogsTests.swift
3 | // PersonalLogsTests
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import PersonalLogs
11 |
12 | class PersonalLogsTests: XCTestCase {
13 |
14 | override func setUpWithError() throws {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDownWithError() throws {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() throws {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() throws {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "logoDarkMode.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "universal",
21 | "scale" : "2x"
22 | },
23 | {
24 | "appearances" : [
25 | {
26 | "appearance" : "luminosity",
27 | "value" : "dark"
28 | }
29 | ],
30 | "idiom" : "universal",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "universal",
35 | "scale" : "3x"
36 | },
37 | {
38 | "appearances" : [
39 | {
40 | "appearance" : "luminosity",
41 | "value" : "dark"
42 | }
43 | ],
44 | "idiom" : "universal",
45 | "scale" : "3x"
46 | }
47 | ],
48 | "info" : {
49 | "author" : "xcode",
50 | "version" : 1
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/PersonalLogs/Protocols/RepositoryItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RepositoryItem.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 01/06/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //MARK: - RepositoryItem
12 | protocol RepositoryItem: AnyObject, Codable {
13 | var id: UUID { get }
14 | var locked: Bool { get set }
15 | func lock(password: String) -> Bool
16 | func unlock(password: String) -> Bool
17 | init()
18 | }
19 |
20 | //MARK: - RepositoryItem Default Implementations
21 | extension RepositoryItem {
22 |
23 | func lock(password: String) -> Bool {
24 | //save password on keychain
25 | let success = KeychainHelper().save(password: password, id: self.id)
26 |
27 | self.locked = success
28 | return success
29 | }
30 |
31 | func unlock(password: String) -> Bool {
32 | //check if password matches with the one on keychain
33 | let success = KeychainHelper().check(password: password, for: self.id)
34 |
35 | self.locked = success ? false : true
36 | return success
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/emptyState.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "emptyState.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "appearances" : [
10 | {
11 | "appearance" : "luminosity",
12 | "value" : "dark"
13 | }
14 | ],
15 | "filename" : "emptyStateDarkMode.png",
16 | "idiom" : "universal",
17 | "scale" : "1x"
18 | },
19 | {
20 | "idiom" : "universal",
21 | "scale" : "2x"
22 | },
23 | {
24 | "appearances" : [
25 | {
26 | "appearance" : "luminosity",
27 | "value" : "dark"
28 | }
29 | ],
30 | "idiom" : "universal",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "universal",
35 | "scale" : "3x"
36 | },
37 | {
38 | "appearances" : [
39 | {
40 | "appearance" : "luminosity",
41 | "value" : "dark"
42 | }
43 | ],
44 | "idiom" : "universal",
45 | "scale" : "3x"
46 | }
47 | ],
48 | "info" : {
49 | "author" : "xcode",
50 | "version" : 1
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/PersonalLogs/Model/Note.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Note.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //MARK: - Codable
12 | class Note: RepositoryItem {
13 |
14 | static let placeholder: (title: String, body: String) = (title: "Untitled Note",
15 | body: "It's your story to tell.")
16 |
17 | required init() {
18 | self.id = UUID()
19 | self.title = Self.placeholder.title
20 | self._body = Self.placeholder.body
21 | self.locked = false
22 | }
23 |
24 | let id: UUID
25 | var title: String
26 | var locked: Bool
27 | private var _body: String
28 | var body: String {
29 | get { locked ? "Classified. A secret is a secret, you know." : _body }
30 | set { _body = locked ? _body : newValue }
31 | }
32 |
33 | //Enum defining which properties are going to be saved. The "_body" property is going to be saved as "body"
34 | private enum CodingKeys: String, CodingKey {
35 | case id, title, _body = "body", locked
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/PersonalLogs/Helpers/Keychain/KeychainHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeychainHelper.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 01/06/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct KeychainHelper {
12 |
13 | private let service = "PersonalLogsService"
14 |
15 | func check(password: String, for id: UUID) -> Bool {
16 | do {
17 | let item = KeychainPasswordItem(service: service, account: id.uuidString)
18 | let keychainPassword = try item.readPassword()
19 | if keychainPassword == password { try item.deleteItem() }
20 | return keychainPassword == password
21 | } catch {
22 | print(error)
23 | return false
24 | }
25 | }
26 |
27 | func save(password: String, id: UUID) -> Bool {
28 | do {
29 | try KeychainPasswordItem(service: service, account: id.uuidString).savePassword(password)
30 | return true
31 | } catch {
32 | print(error)
33 | return false
34 | }
35 | }
36 |
37 | @discardableResult
38 | func removePassword(from id: UUID) -> Bool {
39 | do {
40 | try KeychainPasswordItem(service: service, account: id.uuidString).deleteItem()
41 | return true
42 | } catch {
43 | print(error)
44 | return false
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/PersonalLogs/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/PersonalLogs/Extensions/FileManagerExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileManagerExtension.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 5/31/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | extension FileManager {
13 |
14 | func printContent(from path: String, recursivelly: Bool = false) {
15 | let lastComponent = path.split(separator: "/").last!
16 | var isDirectory: ObjCBool = false
17 | fileExists(atPath: path, isDirectory: &isDirectory)
18 | if isDirectory.boolValue {
19 | print("\n📁 Directory: \(lastComponent)")
20 | if let contents = try? contentsOfDirectory(atPath: path) {
21 | print("Contents: \(contents)")
22 | if recursivelly {
23 | for item in contents {
24 | let itemPath = path+"/"+(item.replacingOccurrences(of: " ", with: "\\ "))
25 | printContent(from: itemPath, recursivelly: true)
26 | }
27 | }
28 | }
29 | } else {
30 | print("\n📊 File: \(lastComponent)")
31 | let fileExtension = lastComponent.split(separator: ".").last
32 | switch fileExtension {
33 | case "plist":
34 | let url = URL(fileURLWithPath: path)
35 | if let data = try? Data(contentsOf: url),
36 | let plist = try? PropertyListSerialization.propertyList(from: data,options: .mutableContainers, format: nil) as? [String : Any] {
37 | print("Content: \(plist.description)")
38 | }
39 | default:
40 | if let content = try? String(contentsOfFile: path, encoding: .utf8) {
41 | print("Content: \(content)")
42 | }
43 | }
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/PersonalLogs/ViewControllers/NoteViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteViewController.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class NoteViewController: UIViewController {
12 |
13 | private let noteRepository: NoteRepository
14 | private let noteID: UUID
15 | private var note: Note? {
16 | noteRepository.readItem(id: noteID)
17 | }
18 |
19 | lazy var noteView: NoteView = {
20 | let view = NoteView()
21 | view.delegate = self
22 | return view
23 | }()
24 |
25 | init(noteRepository: NoteRepository, id: UUID) {
26 | self.noteRepository = noteRepository
27 | self.noteID = id
28 | super.init(nibName: nil, bundle: nil)
29 | }
30 |
31 | override func loadView() {
32 | self.view = noteView
33 | }
34 |
35 | override func viewDidLoad() {
36 | super.viewDidLoad()
37 | setupNavigationItem()
38 |
39 | noteView.titleField.text = note?.title
40 | noteView.bodyTextView.text = note?.body
41 | }
42 |
43 | func setupNavigationItem() {
44 | navigationItem.largeTitleDisplayMode = .never
45 | navigationController?.navigationBar.standardAppearance.configureWithTransparentBackground()
46 | navigationController?.navigationBar.topItem?.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
47 | }
48 |
49 | required init?(coder: NSCoder) {
50 | fatalError("init(coder:) has not been implemented")
51 | }
52 |
53 | }
54 |
55 |
56 | extension NoteViewController: NoteDelegate {
57 |
58 | func didChange(title: String, body: String) {
59 | guard let note = note else { return }
60 | note.title = title
61 | note.body = body
62 | noteRepository.update(item: note)
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/PersonalLogs/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 |
37 |
38 |
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UIStatusBarTintParameters
47 |
48 | UINavigationBar
49 |
50 | Style
51 | UIBarStyleDefault
52 | Translucent
53 |
54 |
55 |
56 | UISupportedInterfaceOrientations
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationLandscapeLeft
60 | UIInterfaceOrientationLandscapeRight
61 |
62 | UISupportedInterfaceOrientations~ipad
63 |
64 | UIInterfaceOrientationPortrait
65 | UIInterfaceOrientationPortraitUpsideDown
66 | UIInterfaceOrientationLandscapeLeft
67 | UIInterfaceOrientationLandscapeRight
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/PersonalLogs/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | lazy var navigationController: UINavigationController = {
17 | let navigationController = UINavigationController(rootViewController: ListViewController())
18 | navigationController.navigationBar.prefersLargeTitles = true
19 | navigationController.navigationBar.tintColor = .primaryAction
20 | return navigationController
21 | }()
22 |
23 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
24 |
25 | if let windowScene = scene as? UIWindowScene {
26 | let window = UIWindow(windowScene: windowScene)
27 | window.rootViewController = navigationController
28 | self.window = window
29 | window.makeKeyAndVisible()
30 |
31 | //MARK: - App Sandbox
32 | // FileManager.default.printContent(from: NSHomeDirectory(), recursivelly: true)
33 |
34 | //MARK: - PropertyLists
35 | // let onBoardingStatusURL = URL(fileURLWithPath: NSHomeDirectory()+"/Library/Preferences/"+OnBoardingStatus.plistName)
36 | //
37 | // if let data = try? Data(contentsOf: onBoardingStatusURL),
38 | // let onBoardingPropertyList = try? PropertyListDecoder().decode(OnBoardingStatus.self, from: data) {
39 | //
40 | // print("FirstLaunch:",onBoardingPropertyList.firstLaunch)
41 | // print("FirstLaunchTimestamp:",onBoardingPropertyList.firstLaunchTimestamp)
42 | // print("OnBoarding has already been seen by the user.")
43 | //
44 | // } else {
45 | //
46 | // let status = OnBoardingStatus()
47 | // if let data = try? PropertyListEncoder().encode(status) {
48 | // try? data.write(to: onBoardingStatusURL)
49 | // }
50 | //
51 | // navigationController.present(OnBoardingViewController(), animated: true)
52 | // }
53 |
54 | //MARK: - UserDefaults
55 | let isFirstLaunch = (UserDefaults.standard.value(forKey: "FirstLaunch") as? Bool) ?? false
56 | if !isFirstLaunch {
57 | UserDefaults.standard.set(true, forKey: "FirstLaunch")
58 | UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "FirstLaunchTimestamp")
59 | navigationController.present(OnBoardingViewController(), animated: true)
60 | }
61 | }
62 |
63 | }
64 |
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/PersonalLogs/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon-20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "icon-20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "icon-29.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "icon-29@2x.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "icon-29@3x.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "icon-40@2x.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "icon-40@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "icon-60@2x.png",
47 | "idiom" : "iphone",
48 | "scale" : "2x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "icon-60@3x.png",
53 | "idiom" : "iphone",
54 | "scale" : "3x",
55 | "size" : "60x60"
56 | },
57 | {
58 | "filename" : "icon-20.png",
59 | "idiom" : "ipad",
60 | "scale" : "1x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "2x",
66 | "size" : "20x20"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "1x",
71 | "size" : "29x29"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "2x",
76 | "size" : "29x29"
77 | },
78 | {
79 | "filename" : "icon-40.png",
80 | "idiom" : "ipad",
81 | "scale" : "1x",
82 | "size" : "40x40"
83 | },
84 | {
85 | "idiom" : "ipad",
86 | "scale" : "2x",
87 | "size" : "40x40"
88 | },
89 | {
90 | "filename" : "icon-76.png",
91 | "idiom" : "ipad",
92 | "scale" : "1x",
93 | "size" : "76x76"
94 | },
95 | {
96 | "filename" : "icon-76@2x.png",
97 | "idiom" : "ipad",
98 | "scale" : "2x",
99 | "size" : "76x76"
100 | },
101 | {
102 | "filename" : "icon-83.5@2x.png",
103 | "idiom" : "ipad",
104 | "scale" : "2x",
105 | "size" : "83.5x83.5"
106 | },
107 | {
108 | "filename" : "icon-1024.png",
109 | "idiom" : "ios-marketing",
110 | "scale" : "1x",
111 | "size" : "1024x1024"
112 | }
113 | ],
114 | "info" : {
115 | "author" : "xcode",
116 | "version" : 1
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/PersonalLogs/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 |
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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/xcode,macos,swift
3 | # Edit at https://www.gitignore.io/?templates=xcode,macos,swift
4 |
5 | ### macOS ###
6 | # General
7 | .DS_Store
8 | .AppleDouble
9 | .LSOverride
10 |
11 | # Icon must end with two \r
12 | Icon
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### Swift ###
34 | # Xcode
35 | #
36 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
37 |
38 | ## Build generated
39 | build/
40 | DerivedData/
41 |
42 | ## Various settings
43 | *.pbxuser
44 | !default.pbxuser
45 | *.mode1v3
46 | !default.mode1v3
47 | *.mode2v3
48 | !default.mode2v3
49 | *.perspectivev3
50 | !default.perspectivev3
51 | xcuserdata/
52 |
53 | ## Other
54 | *.moved-aside
55 | *.xccheckout
56 | *.xcscmblueprint
57 |
58 | ## Obj-C/Swift specific
59 | *.hmap
60 | *.ipa
61 | *.dSYM.zip
62 | *.dSYM
63 |
64 | ## Playgrounds
65 | timeline.xctimeline
66 | playground.xcworkspace
67 |
68 | # Swift Package Manager
69 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
70 | # Packages/
71 | # Package.pins
72 | # Package.resolved
73 | .build/
74 | # Add this line if you want to avoid checking in Xcode SPM integration.
75 | # .swiftpm/xcode
76 |
77 | # CocoaPods
78 | # We recommend against adding the Pods directory to your .gitignore. However
79 | # you should judge for yourself, the pros and cons are mentioned at:
80 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
81 | # Pods/
82 | # Add this line if you want to avoid checking in source code from the Xcode workspace
83 | # *.xcworkspace
84 |
85 | # Carthage
86 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
87 | # Carthage/Checkouts
88 |
89 | Carthage/Build
90 |
91 | # Accio dependency management
92 | Dependencies/
93 | .accio/
94 |
95 | # fastlane
96 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
97 | # screenshots whenever they are needed.
98 | # For more information about the recommended setup visit:
99 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
100 |
101 | fastlane/report.xml
102 | fastlane/Preview.html
103 | fastlane/screenshots/**/*.png
104 | fastlane/test_output
105 |
106 | # Code Injection
107 | # After new code Injection tools there's a generated folder /iOSInjectionProject
108 | # https://github.com/johnno1962/injectionforxcode
109 |
110 | iOSInjectionProject/
111 |
112 | ### Xcode ###
113 | # Xcode
114 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
115 |
116 | ## User settings
117 |
118 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
119 |
120 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
121 |
122 | ## Xcode Patch
123 | *.xcodeproj/*
124 | !*.xcodeproj/project.pbxproj
125 | !*.xcodeproj/xcshareddata/
126 | !*.xcworkspace/contents.xcworkspacedata
127 | /*.gcno
128 |
129 | ### Xcode Patch ###
130 | **/xcshareddata/WorkspaceSettings.xcsettings
131 |
132 | # End of https://www.gitignore.io/api/xcode,macos,swift
133 |
--------------------------------------------------------------------------------
/PersonalLogs/Protocols/Repository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Repository.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 26/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //MARK: - Repository
12 | protocol Repository: AnyObject {
13 |
14 | associatedtype Item: RepositoryItem
15 |
16 | var items: [Item] { get set }
17 |
18 | func createNewItem() -> Item?
19 | func readAllItems() -> [Item]
20 | func readItem(id: UUID) -> Item?
21 | func update(item: Item)
22 | func delete(id: UUID)
23 | func lock(id: UUID, password: String) -> Bool
24 | func unlock(id: UUID, password: String) -> Bool
25 |
26 | }
27 |
28 | //MARK: - Repository Default Implementations
29 | extension Repository {
30 |
31 | func createNewItem() -> Item? {
32 |
33 | //creating new item
34 | let newItem = Item()
35 |
36 | //persist file
37 | if let data = try? JSONEncoder().encode(newItem) {
38 | FileHelper().createFile(with: data, name: newItem.id.uuidString)
39 | return newItem
40 | }
41 |
42 | return nil
43 | }
44 |
45 | func readAllItems() -> [Item] {
46 |
47 | //read the content of the documents path
48 | let fileNames: [String] = FileHelper().contentsForDirectory(atPath: "")
49 |
50 | //retrieve items from fileNames and updating items array
51 | self.items = fileNames.compactMap { fileName in
52 | if let data = FileHelper().retrieveFile(at: fileName) {
53 | //decode from Data type to Item type
54 | let item = try? JSONDecoder().decode(Item.self, from: data)
55 | // TODO: remove body content from here in case its locked
56 | return item
57 | }
58 | return nil
59 | }
60 |
61 | return items
62 | }
63 |
64 | func readItem(id: UUID) -> Item? {
65 | //read one file by name. In our case, note files are named with their id.uuidString.
66 | if let data = FileHelper().retrieveFile(at: id.uuidString) {
67 | //decode from Data type to Item type
68 | let item = try? JSONDecoder().decode(Item.self, from: data)
69 | return item
70 | }
71 | return nil
72 | }
73 |
74 | func update(item: Item) {
75 | //encode to Data format
76 | if let data = try? JSONEncoder().encode(item) {
77 | //overrite persisted file
78 | FileHelper().updateFile(at: item.id.uuidString, data: data)
79 | }
80 | }
81 |
82 | func delete(id: UUID) {
83 | //remove file
84 | FileHelper().removeFile(at: id.uuidString)
85 | //remove password from keychain
86 | KeychainHelper().removePassword(from: id)
87 | }
88 |
89 | func lock(id: UUID, password: String) -> Bool {
90 | //get item by id
91 | guard let item = readItem(id: id) else { return false }
92 |
93 | //lock item with password
94 | if item.lock(password: password) {
95 | //if it was successfully locked, persist its new locked state.
96 | update(item: item)
97 | return true
98 | }
99 | return false
100 | }
101 |
102 | func unlock(id: UUID, password: String) -> Bool {
103 | //get item by id
104 | guard let item = readItem(id: id) else { return false }
105 |
106 | //unlock item with password
107 | if item.unlock(password: password) {
108 | //if it was successfully unlocked, persist its new unlocked state.
109 | update(item: item)
110 | return true
111 | }
112 | return false
113 | }
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/PersonalLogs/Helpers/FileHelper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileHelper.swift
3 | // FoundPersBas
4 | //
5 | // Created by Yuri on 04/04/19.
6 | // Copyright © 2019 academy.IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //MARK: - FileManager
12 | struct FileHelper {
13 |
14 | let manager = FileManager.default
15 | let mainPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
16 |
17 | //Create a new directory on the documents by default, or on the choosen path
18 | func createDirectory(with name: String, at path: String? = nil) {
19 | let contentPath = constructPath(named: name, from: path)
20 | if !directoryExists(with: name, at: path) {
21 | do {
22 | try manager.createDirectory(at: contentPath, withIntermediateDirectories: true, attributes: nil)
23 | } catch (let error) { print(error.localizedDescription) }
24 | }
25 | }
26 |
27 | //Remove directory and all of it's contents
28 | func removeDirectory(named: String, at path: String? = nil) -> Bool {
29 | let dirPath = constructPath(named: named, from: path)
30 | do {
31 | try manager.removeItem(at: dirPath)
32 | return !manager.fileExists(atPath: dirPath.path)
33 | } catch (let error) {
34 | print(error.localizedDescription)
35 | return false
36 | }
37 | }
38 |
39 | //Check if the directory exists
40 | func directoryExists(with name: String, at path: String? = nil) -> Bool {
41 | let dirPath = constructPath(named: name, from: path)
42 | var isDirectory = ObjCBool(true)
43 | return manager.fileExists(atPath: dirPath.path, isDirectory: &isDirectory) && isDirectory.boolValue
44 |
45 | }
46 |
47 | func contentsForDirectory(atPath path: String) -> [String] {
48 | let contentPath = constructPath(named: path)
49 | let itens = try? manager.contentsOfDirectory(atPath: contentPath.path)
50 | return itens ?? []
51 | }
52 |
53 | func fullPathForContents(at directory: String) -> [String] {
54 | let contentPath = constructPath(named: directory)
55 | guard let itens = try? manager.contentsOfDirectory(atPath: contentPath.path) else {
56 | return []
57 | }
58 |
59 | return itens.map { "\(contentPath.path)/\($0)"}
60 | }
61 |
62 | @discardableResult
63 | func createFile(with data: Data, name: String) -> Bool {
64 | let contentPath = constructPath(named: name)
65 | manager.createFile(atPath: contentPath.path, contents: data, attributes: nil)
66 | return manager.fileExists(atPath: contentPath.path)
67 | }
68 |
69 | @discardableResult
70 | func removeFile(at path: String) -> Bool {
71 | let contentPath = constructPath(named: path)
72 | do {
73 | try manager.removeItem(at: contentPath)
74 | return !manager.fileExists(atPath: contentPath.path)
75 | } catch (let error) {
76 | print(error.localizedDescription)
77 | return false
78 | }
79 | }
80 |
81 | @discardableResult
82 | func updateFile(at path: String, data: Data) -> Bool {
83 | let contentPath = constructPath(named: path)
84 | do {
85 | try data.write(to: contentPath)
86 | return true
87 | } catch (let error) {
88 | print(error.localizedDescription)
89 | return false
90 | }
91 | }
92 |
93 |
94 | func retrieveFile(at path: String) -> Data? {
95 | let contentPath = constructPath(named: path)
96 | let data = try? Data(contentsOf: contentPath)
97 | return data
98 | }
99 |
100 | func constructPath(named: String, from path: String? = nil) -> URL {
101 | let contentPath = mainPath
102 | if let path = path {
103 | return contentPath.appendingPathComponent(path).appendingPathComponent(named)
104 | } else {
105 | return contentPath.appendingPathComponent(named)
106 | }
107 | }
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/PersonalLogs/View/OnBoardingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnBoardingView.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 01/06/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class OnBoardingView: UIView {
13 |
14 | lazy var titleLabel: UILabel = {
15 | let label = UILabel()
16 | label.font = UIFont.systemFont(ofSize: 32, weight: .medium)
17 | label.textColor = .text
18 | label.numberOfLines = 0
19 | label.text = "Simple & Safe"
20 | return label
21 | }()
22 |
23 | lazy var bodyLabel: UILabel = {
24 | let label = UILabel()
25 | label.font = UIFont.systemFont(ofSize: 18, weight: .regular)
26 | label.textColor = .text
27 | label.numberOfLines = 0
28 | label.text = """
29 |
30 | 📗 Personal Logs is the best way to save your personal notes and reflections.
31 |
32 | 1. Create a new note taping the + button on the right corner and write your note, quite simple.
33 |
34 | 2. Don't worry about saving the changes you make on your notes, every letter you type is automatically saved by default.
35 |
36 | 3. You can add a password for each individual note, if you'd like. Just find the note you want to lock on the Personal Logs list, and swipe right to see the option.
37 |
38 | ✨ Enjoy the app, and keep writing!
39 |
40 | 🖋
41 | """
42 | return label
43 | }()
44 |
45 | lazy var startButton: UIButton = {
46 | let button = UIButton()
47 | button.setTitle("START WRITING", for: .normal)
48 | button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 20)
49 | button.setTitleColor(.white, for: .normal)
50 | button.backgroundColor = .primaryAction
51 | button.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
52 | return button
53 | }()
54 |
55 | var dismissAction: (() -> Void)!
56 | @objc func dismiss() {
57 | dismissAction()
58 | }
59 |
60 | override init(frame: CGRect) {
61 | super.init(frame: frame)
62 | self.backgroundColor = .background
63 | setupConstraints()
64 | }
65 |
66 | required init?(coder: NSCoder) {
67 | fatalError("init(coder:) has not been implemented")
68 | }
69 |
70 | override func layoutSubviews() {
71 | super.layoutSubviews()
72 | startButton.layer.cornerCurve = .continuous
73 | startButton.layer.cornerRadius = 11
74 | }
75 |
76 | func setupConstraints() {
77 |
78 | self.addSubview(titleLabel)
79 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
80 | NSLayoutConstraint.activate([
81 | titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
82 | titleLabel.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 40),
83 | titleLabel.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.75)
84 | ])
85 |
86 | self.addSubview(bodyLabel)
87 | bodyLabel.translatesAutoresizingMaskIntoConstraints = false
88 | NSLayoutConstraint.activate([
89 | bodyLabel.centerXAnchor.constraint(equalTo: titleLabel.centerXAnchor),
90 | bodyLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 20),
91 | bodyLabel.widthAnchor.constraint(equalTo: titleLabel.widthAnchor)
92 | ])
93 |
94 | self.addSubview(startButton)
95 | startButton.translatesAutoresizingMaskIntoConstraints = false
96 | NSLayoutConstraint.activate([
97 | startButton.centerXAnchor.constraint(equalTo: titleLabel.centerXAnchor),
98 | startButton.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: -90),
99 | startButton.widthAnchor.constraint(equalTo: bodyLabel.widthAnchor, multiplier: 1),
100 | startButton.heightAnchor.constraint(equalToConstant: 50)
101 | ])
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/PersonalLogs/View/NoteView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteView.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class NoteView: UIView {
12 |
13 | weak var delegate: NoteDelegate? = nil
14 |
15 | lazy var titleField: UITextField = {
16 | let textField = UITextField(frame: .zero)
17 | textField.backgroundColor = .clear
18 | textField.borderStyle = .none
19 | textField.adjustsFontSizeToFitWidth = true
20 | textField.font = UIFont.systemFont(ofSize: 16, weight: .medium)
21 | textField.tintColor = .primaryAction
22 | textField.minimumFontSize = 18
23 | textField.delegate = self
24 | return textField
25 | }()
26 |
27 | lazy var bodyTextView: UITextView = {
28 | let textView = UITextView()
29 | textView.font = UIFont.systemFont(ofSize: 12, weight: .regular)
30 | textView.backgroundColor = .clear
31 | textView.textColor = .lightGray
32 | textView.tintColor = .primaryAction
33 | textView.delegate = self
34 | return textView
35 | }()
36 |
37 | override init(frame: CGRect) {
38 | super.init(frame: frame)
39 | self.backgroundColor = .backgroundCell
40 | setupTitleField()
41 | setupBodyTextView()
42 | }
43 |
44 | required init?(coder: NSCoder) {
45 | fatalError("init(coder:) has not been implemented")
46 | }
47 |
48 | func setupTitleField() {
49 | self.addSubview(titleField)
50 | titleField.translatesAutoresizingMaskIntoConstraints = false
51 | NSLayoutConstraint.activate([
52 | titleField.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor),
53 | titleField.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 25),
54 | titleField.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -25),
55 | titleField.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height * 0.05)
56 | ])
57 | }
58 |
59 | func setupBodyTextView() {
60 | self.addSubview(bodyTextView)
61 | bodyTextView.translatesAutoresizingMaskIntoConstraints = false
62 | NSLayoutConstraint.activate([
63 | bodyTextView.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: 5),
64 | bodyTextView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 20),
65 | bodyTextView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -20),
66 | bodyTextView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
67 | ])
68 | }
69 |
70 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
71 | super.touchesBegan(touches, with: event)
72 | self.endEditing(true)
73 | }
74 |
75 | }
76 |
77 |
78 | extension NoteView: UITextViewDelegate {
79 |
80 | func textViewDidBeginEditing(_ textView: UITextView) {
81 | textView.textColor = .text
82 | if textView.text == Note.placeholder.body {
83 | textView.text.removeAll()
84 | }
85 | }
86 |
87 | func textViewDidEndEditing(_ textView: UITextView) {
88 | textView.textColor = .lightGray
89 | if textView.text.isEmpty {
90 | textView.text = Note.placeholder.body
91 | }
92 | delegate?.didChange(title: titleField.text!, body: bodyTextView.text!)
93 | }
94 |
95 | func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
96 | delegate?.didChange(title: titleField.text!, body: bodyTextView.text!+text)
97 | return true
98 | }
99 |
100 | }
101 |
102 |
103 | extension NoteView: UITextFieldDelegate {
104 |
105 | func textFieldDidBeginEditing(_ textField: UITextField) {
106 | if textField.text == Note.placeholder.title {
107 | textField.text!.removeAll()
108 | }
109 | }
110 |
111 | func textFieldDidEndEditing(_ textField: UITextField) {
112 | if textField.text!.isEmpty {
113 | textField.text = Note.placeholder.title
114 | }
115 | delegate?.didChange(title: titleField.text!, body: bodyTextView.text!)
116 | }
117 |
118 | func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
119 | delegate?.didChange(title: titleField.text!+string, body: bodyTextView.text!)
120 | return true
121 | }
122 |
123 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
124 | self.endEditing(true)
125 | return true
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/PersonalLogs/ViewControllers/ListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListViewController.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ListViewController: UIViewController {
12 |
13 | //MARK: - Model
14 | private var noteRepository: NoteRepository {
15 | NoteRepository()
16 | }
17 |
18 | //MARK: - Views
19 | lazy var listView: ListView = {
20 | let view = ListView()
21 | view.delegate = self
22 | return view
23 | }()
24 |
25 | lazy var newNoteBarButtonItem: UIBarButtonItem = UIBarButtonItem(
26 | image: UIImage(systemName: "plus"),
27 | style: .done,
28 | target: self,
29 | action: #selector(newNote)
30 | )
31 |
32 | lazy var onboardingBarButtonItem: UIBarButtonItem = UIBarButtonItem(
33 | image: UIImage(systemName: "info"),
34 | style: .plain,
35 | target: self,
36 | action: #selector(showOnboarding)
37 | )
38 |
39 | //MARK: - Alerts
40 | func showPasswordAlert(okAction: @escaping (String) -> Void) {
41 | let alert = UIAlertController(title: nil, message: "Insert the Secret Password", preferredStyle: .alert)
42 |
43 | alert.addTextField { (textField) in
44 | textField.tintColor = UIColor(named: "primaryActionColor")
45 | textField.textContentType = .password
46 | textField.isSecureTextEntry = true
47 | }
48 |
49 | let cancelAction = UIAlertAction(title: "Cancel", style: .destructive)
50 | alert.addAction(cancelAction)
51 |
52 | let okAction = UIAlertAction(title: "OK", style: .default) { action in
53 | guard let pass = alert.textFields?.first?.text, !pass.isEmpty else { return }
54 | okAction(pass)
55 | }
56 | okAction.setValue(UIColor.primaryAction, forKey: "titleTextColor")
57 | alert.addAction(okAction)
58 |
59 | self.present(alert, animated: true, completion: nil)
60 | }
61 |
62 | func showConfirmationAlert(okAction: @escaping () -> Void) {
63 | let alert = UIAlertController(title: "This operation cannot be undone", message: "Are you sure you want to continue?", preferredStyle: .alert)
64 |
65 | let cancelAction = UIAlertAction(title: "Cancel", style: .destructive)
66 | alert.addAction(cancelAction)
67 |
68 | let okAction = UIAlertAction(title: "OK", style: .default) { action in okAction() }
69 | okAction.setValue(UIColor.primaryAction, forKey: "titleTextColor")
70 | alert.addAction(okAction)
71 |
72 | self.present(alert, animated: true, completion: nil)
73 | }
74 |
75 | func showFadingAlert(message: String) {
76 | let lockedAlert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
77 | self.present(lockedAlert, animated: true, completion: nil)
78 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
79 | lockedAlert.dismiss(animated: true, completion: nil)
80 | }
81 | }
82 |
83 | //MARK: - ViewController Cycle
84 | override func loadView() {
85 | self.view = listView
86 | }
87 |
88 | override func viewDidLoad() {
89 | super.viewDidLoad()
90 |
91 | // Setting the title that shows on NavigationBar
92 | self.title = "Personal Logs"
93 |
94 | //Adding the newNoteBarButtonItem
95 | navigationItem.rightBarButtonItem = newNoteBarButtonItem
96 | navigationItem.leftBarButtonItem = onboardingBarButtonItem
97 | }
98 |
99 | override func viewWillAppear(_ animated: Bool) {
100 | super.viewWillAppear(animated)
101 |
102 | // Calling the repository to read all notes
103 | listView.notes = noteRepository.readAllItems()
104 | }
105 |
106 | //MARK: - Actions
107 | @objc func showOnboarding() {
108 | navigationController?.present(OnBoardingViewController(), animated: true)
109 | }
110 |
111 | @objc func newNote() {
112 | if let newNote = noteRepository.createNewItem() {
113 | showNoteDetail(id: newNote.id)
114 | }
115 | }
116 |
117 | func showNoteDetail(id: UUID) {
118 | let noteViewController = NoteViewController(noteRepository: noteRepository, id: id)
119 | navigationController?.pushViewController(noteViewController, animated: true)
120 | }
121 |
122 | }
123 |
124 | //MARK: - NoteListDelegate
125 | extension ListViewController: NoteListDelegate {
126 |
127 | func didSelectNote(with id: UUID) {
128 | showNoteDetail(id: id)
129 | }
130 |
131 | func deleteNote(for id: UUID) {
132 | showConfirmationAlert { [weak self] in
133 | self?.noteRepository.delete(id: id)
134 | self?.listView.notes = self?.noteRepository.readAllItems() ?? []
135 | }
136 | }
137 |
138 | func askForPasswordToLock(for id: UUID) {
139 | showPasswordAlert { [weak self] (password) in
140 | guard let self = self else { return }
141 |
142 | if self.noteRepository.lock(id: id, password: password) {
143 | self.showFadingAlert(message: "Locked 🔒")
144 | }
145 |
146 | self.listView.notes = self.noteRepository.readAllItems()
147 | }
148 | }
149 |
150 | func askForPasswordToUnlock(for id: UUID) {
151 | showPasswordAlert { [weak self] (password) in
152 | guard let self = self else { return }
153 |
154 | if self.noteRepository.unlock(id: id, password: password) {
155 | self.showFadingAlert(message: "Unlocked 🔓")
156 | } else {
157 | self.showFadingAlert(message: "Wrong Password ☠️")
158 | }
159 |
160 | self.listView.notes = self.noteRepository.readAllItems()
161 | }
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/PersonalLogs/View/ListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListView.swift
3 | // PersonalLogs
4 | //
5 | // Created by Gabriela Bezerra on 25/05/20.
6 | // Copyright © 2020 Academy IFCE. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ListView: UIView {
12 |
13 | weak var delegate: NoteListDelegate? = nil
14 |
15 | var notes: [Note] = [] {
16 | didSet {
17 | if notes.isEmpty {
18 | setupEmptyState()
19 | } else if tableView.superview == self {
20 | self.tableView.reloadData()
21 | } else {
22 | setupTableView()
23 | }
24 | }
25 | }
26 |
27 | override init(frame: CGRect) {
28 | super.init(frame: frame)
29 | self.backgroundColor = .background
30 | }
31 |
32 | required init?(coder: NSCoder) {
33 | fatalError("init(coder:) has not been implemented")
34 | }
35 |
36 | //MARK: - Views
37 | lazy var tableView: UITableView = {
38 | let tableView = UITableView(frame: .zero, style: .insetGrouped)
39 | tableView.backgroundColor = .background
40 | tableView.separatorColor = .text.withAlphaComponent(0.25)
41 | tableView.alwaysBounceVertical = false
42 | tableView.alwaysBounceHorizontal = false
43 | tableView.delegate = self
44 | tableView.dataSource = self
45 | return tableView
46 | }()
47 |
48 | lazy var emptyStateImageView: UIImageView = {
49 | let emptyStateImageView = UIImageView(image: UIImage(named: "emptyState"))
50 | emptyStateImageView.clipsToBounds = false
51 | return emptyStateImageView
52 | }()
53 |
54 | func showDetailOfNote(with id: UUID) {
55 | delegate?.didSelectNote(with: id)
56 | }
57 |
58 | //MARK: Setup Views
59 | func setupTableView() {
60 | emptyStateImageView.removeFromSuperview()
61 | self.addSubview(tableView)
62 | tableView.translatesAutoresizingMaskIntoConstraints = false
63 | NSLayoutConstraint.activate([
64 | tableView.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor),
65 | tableView.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 100),
66 | tableView.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor),
67 | tableView.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor)
68 | ])
69 | tableView.reloadData()
70 | }
71 |
72 | func setupEmptyState() {
73 | tableView.removeFromSuperview()
74 | self.addSubview(emptyStateImageView)
75 | emptyStateImageView.translatesAutoresizingMaskIntoConstraints = false
76 | NSLayoutConstraint.activate([
77 | emptyStateImageView.centerXAnchor.constraint(equalTo: self.centerXAnchor),
78 | emptyStateImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor)
79 | ])
80 | }
81 | }
82 |
83 |
84 | //MARK: - UITableView Delegate and DataSource
85 | extension ListView: UITableViewDataSource, UITableViewDelegate {
86 |
87 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
88 | notes.count
89 | }
90 |
91 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
92 |
93 | let currentNote = notes[indexPath.row]
94 |
95 | let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
96 | cell.textLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium)
97 | cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 12, weight: .regular)
98 | cell.detailTextLabel?.numberOfLines = 3
99 | cell.selectionStyle = .none
100 | cell.backgroundColor = .backgroundCell
101 |
102 | cell.textLabel?.text = currentNote.title
103 | cell.detailTextLabel?.text = currentNote.body
104 |
105 | return cell
106 | }
107 |
108 | func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
109 | let currentNote = notes[indexPath.row]
110 |
111 | let deleteAction = UIContextualAction(style: .destructive, title: "") {
112 | [weak self] (_, _, success) in
113 | self?.delegate?.deleteNote(for: currentNote.id)
114 | success(true)
115 | }
116 | deleteAction.image = UIImage(systemName: "trash.fill")
117 |
118 | return UISwipeActionsConfiguration(actions: [deleteAction])
119 | }
120 |
121 | func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
122 | let currentNote = notes[indexPath.row]
123 |
124 | let lockAction = UIContextualAction(style: .normal, title: "") {
125 | [weak self] (_, _, success) in
126 | currentNote.locked ? self?.delegate?.askForPasswordToUnlock(for: currentNote.id) : self?.delegate?.askForPasswordToLock(for: currentNote.id)
127 | success(true)
128 | }
129 |
130 | lockAction.backgroundColor = UIColor(named: "primaryActionColor")
131 | lockAction.image = UIImage(systemName: "lock.fill")
132 |
133 | return UISwipeActionsConfiguration(actions: [lockAction])
134 | }
135 |
136 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
137 | let currentNote = notes[indexPath.row]
138 |
139 | if !currentNote.locked {
140 | showDetailOfNote(with: currentNote.id)
141 | } else {
142 | delegate?.askForPasswordToUnlock(for: currentNote.id)
143 | }
144 | }
145 |
146 | func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
147 | UIView()
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PersonalLogs
2 |
3 | Persistência Básica com Repository em Swift.
4 |
5 | Alguns links para estudo mais prolongado sobre o assunto:
6 |
7 | ### Persistência em geral
8 |
9 | [103 iPS Persistence - DEV](https://dev.to/iphreaks/103-ips-persistence)
10 |
11 | [Apple Getting Started with Persistence](https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/PersistData.html)
12 |
13 | [iOS Data Storage Guidelines - Apple Developer](https://developer.apple.com/icloud/documentation/data-storage/index.html)
14 |
15 | [iOS storage best practices - Vikash Anand - Medium](https://medium.com/@anandin02/ios-storage-best-practices-294fca83ad9)
16 |
17 |
18 | ### App Sandbox
19 |
20 | [App Sandboxing - Apple Developer](https://developer.apple.com/app-sandboxing/)
21 |
22 | [File System Basics](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html)
23 |
24 | [About App Sandbox](https://developer.apple.com/library/archive/documentation/Security/Conceptual/AppSandboxDesignGuide/AboutAppSandbox/AboutAppSandbox.html)
25 |
26 |
27 | ### Archive and Serialization
28 |
29 | [Archives and Serialization | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/archives_and_serialization)
30 |
31 | [Encoding and Decoding Custom Types | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types)
32 |
33 | [Codable | Swift by Sundell](https://www.swiftbysundell.com/basics/codable/)
34 |
35 |
36 | ### FileManager
37 |
38 | [FileManager - NSHipster](https://nshipster.com/filemanager/)
39 |
40 | [FileManager - Foundation | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/filemanager)
41 |
42 | [Accessing Files and Directories](https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/AccessingFilesandDirectories/AccessingFilesandDirectories.html)
43 |
44 | ### Repository
45 |
46 | [Repository Design Pattern in Swift - Frederik Jacques - Medium](https://medium.com/@frederikjacques/repository-design-pattern-in-swift-952061485aa)
47 |
48 | [Introducing repository pattern with swift. · GitHub](https://gist.github.com/omayib/9f515b6d5a72802bc2e07673788a308d)
49 |
50 |
51 | ### Unit Test on Storage
52 |
53 | [Mocking the File System to Improve Testability (with JustMoc](https://www.telerik.com/blogs/mocking-the-file-system-to-improve-testability-with-justmock)
54 |
55 | [Faster & more robust tests with Xcode 10 | Swift by Sundell](https://www.swiftbysundell.com/wwdc2018/faster-and-more-robust-tests-with-xcode-10/)
56 |
57 | [Mock-free unit tests in Swift | Swift by Sundell](https://www.swiftbysundell.com/articles/mock-free-unit-tests-in-swift/)
58 |
59 | [Mocking for tests in iOS development](https://geek-is-stupid.github.io/2019-04-03-mocking-for-test-in-ios-development/)
60 |
61 | [Avoiding mocking UserDefaults | Swift by Sundell](https://www.swiftbysundell.com/tips/avoiding-mocking-userdefaults/)
62 |
63 | [How to Test Using Fake Data on iOS - Swift2Go - Medium](https://medium.com/swift2go/how-to-test-with-fake-data-on-ios-66dc87bf093e)
64 |
65 |
66 | ### Keychain
67 |
68 | [Keychain Services | Apple Developer Documentation](https://developer.apple.com/documentation/security/keychain_services)
69 |
70 | [Keychains | Apple Developer Documentation](https://developer.apple.com/documentation/security/keychain_services/keychains)
71 |
72 | [Basic iOS Security: Keychain and Hashing | raywenderlich.com](https://www.raywenderlich.com/129-basic-ios-security-keychain-and-hashing)
73 |
74 | [How To Secure iOS User Data: The Keychain and Biometrics – Face ID or Touch ID | raywenderlich.com](https://www.raywenderlich.com/236-how-to-secure-ios-user-data-the-keychain-and-biometrics-face-id-or-touch-id)
75 |
76 | [Keychain Services API Tutorial for Passwords in Swift | raywenderlich.com](https://www.raywenderlich.com/9240-keychain-services-api-tutorial-for-passwords-in-swift)
77 |
78 | [GenericKeychain](https://developer.apple.com/library/archive/samplecode/GenericKeychain/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007797-Intro-DontLinkElementID_2)
79 |
80 | [Storing CryptoKit Keys in the Keychain | Apple Developer Documentation](https://developer.apple.com/documentation/cryptokit/storing_cryptokit_keys_in_the_keychain)
81 |
82 | [Storing Keys in the Keychain | Apple Developer Documentation](https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_keychain)
83 |
84 | [Adding a Password to the Keychain | Apple Developer Documentation](https://developer.apple.com/documentation/security/keychain_services/keychain_items/adding_a_password_to_the_keychain)
85 |
86 | [Storing Keys in the Secure Enclave | Apple Developer Documentation](https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_secure_enclave)
87 |
88 | [Restricting Keychain Item Accessibility | Apple Developer Documentation](https://developer.apple.com/documentation/security/keychain_services/keychain_items/restricting_keychain_item_accessibility)
89 |
90 |
91 | ### Security / Privacy
92 |
93 | [Security | Apple Developer Documentation](https://developer.apple.com/documentation/security?language=swift)
94 |
95 | [About Software Security](https://developer.apple.com/library/archive/documentation/Security/Conceptual/Security_Overview/Introduction/Introduction.html#//apple_ref/doc/uid/TP30000976)
96 |
97 | [Other Security Resources](https://developer.apple.com/library/archive/documentation/Security/Conceptual/Security_Overview/SeeAlso/SeeAlso.html#//apple_ref/doc/uid/TP30000976-CH7-SW1)
98 |
99 | [App security overview](https://support.apple.com/pt-br/guide/security/sec35dd877d0/1/web/1)
100 |
101 | [Encryption and Data Protection overview - Suporte da Apple](https://support.apple.com/pt-br/guide/security/sece3bee0835/1/web/1)
102 |
103 | [Net Guru](https://www.netguru.com/blog/ios-app-data-security)
104 |
105 | [iOS App Templates](https://www.iosapptemplates.com/blog/ios-development/data-persistence-ios-swift)
106 |
107 | [MOBILE API SECURITY TECHNIQUES PART 1](https://blog.approov.io/mobile-api-security-techniques-part-1)
108 |
109 | [MOBILE API SECURITY TECHNIQUES PART 2](https://blog.approov.io/mobile-api-security-techniques-part-2)
110 |
111 | [MOBILE API SECURITY TECHNIQUES PART 3](https://blog.approov.io/mobile-api-security-techniques-part-3)
112 |
113 |
--------------------------------------------------------------------------------
/PersonalLogs/Helpers/Keychain/KeychainPasswordItem.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2016 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | A struct for accessing generic password keychain items.
7 | */
8 |
9 | import Foundation
10 |
11 | struct KeychainPasswordItem {
12 | // MARK: Types
13 |
14 | enum KeychainError: Error {
15 | case noPassword
16 | case unexpectedPasswordData
17 | case unexpectedItemData
18 | case unhandledError(status: OSStatus)
19 | }
20 |
21 | // MARK: Properties
22 |
23 | let service: String
24 |
25 | private(set) var account: String
26 |
27 | let accessGroup: String?
28 |
29 | // MARK: Intialization
30 |
31 | init(service: String, account: String, accessGroup: String? = nil) {
32 | self.service = service
33 | self.account = account
34 | self.accessGroup = accessGroup
35 | }
36 |
37 | // MARK: Keychain access
38 |
39 | func readPassword() throws -> String {
40 | /*
41 | Build a query to find the item that matches the service, account and
42 | access group.
43 | */
44 | var query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
45 | query[kSecMatchLimit as String] = kSecMatchLimitOne
46 | query[kSecReturnAttributes as String] = kCFBooleanTrue
47 | query[kSecReturnData as String] = kCFBooleanTrue
48 |
49 | // Try to fetch the existing keychain item that matches the query.
50 | var queryResult: AnyObject?
51 | let status = withUnsafeMutablePointer(to: &queryResult) {
52 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
53 | }
54 |
55 | // Check the return status and throw an error if appropriate.
56 | guard status != errSecItemNotFound else { throw KeychainError.noPassword }
57 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
58 |
59 | // Parse the password string from the query result.
60 | guard let existingItem = queryResult as? [String : AnyObject],
61 | let passwordData = existingItem[kSecValueData as String] as? Data,
62 | let password = String(data: passwordData, encoding: String.Encoding.utf8)
63 | else {
64 | throw KeychainError.unexpectedPasswordData
65 | }
66 |
67 | return password
68 | }
69 |
70 | func savePassword(_ password: String) throws {
71 | // Encode the password into an Data object.
72 | let encodedPassword = password.data(using: String.Encoding.utf8)!
73 |
74 | do {
75 | // Check for an existing item in the keychain.
76 | try _ = readPassword()
77 |
78 | // Update the existing item with the new password.
79 | var attributesToUpdate = [String : AnyObject]()
80 | attributesToUpdate[kSecValueData as String] = encodedPassword as AnyObject?
81 |
82 | let query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
83 | let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
84 |
85 | // Throw an error if an unexpected status was returned.
86 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
87 | }
88 | catch KeychainError.noPassword {
89 | /*
90 | No password was found in the keychain. Create a dictionary to save
91 | as a new keychain item.
92 | */
93 | var newItem = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
94 | newItem[kSecValueData as String] = encodedPassword as AnyObject?
95 |
96 | // Add a the new item to the keychain.
97 | let status = SecItemAdd(newItem as CFDictionary, nil)
98 |
99 | // Throw an error if an unexpected status was returned.
100 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
101 | }
102 | }
103 |
104 | mutating func renameAccount(_ newAccountName: String) throws {
105 | // Try to update an existing item with the new account name.
106 | var attributesToUpdate = [String : AnyObject]()
107 | attributesToUpdate[kSecAttrAccount as String] = newAccountName as AnyObject?
108 |
109 | let query = KeychainPasswordItem.keychainQuery(withService: service, account: self.account, accessGroup: accessGroup)
110 | let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
111 |
112 | // Throw an error if an unexpected status was returned.
113 | guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
114 |
115 | self.account = newAccountName
116 | }
117 |
118 | func deleteItem() throws {
119 | // Delete the existing item from the keychain.
120 | let query = KeychainPasswordItem.keychainQuery(withService: service, account: account, accessGroup: accessGroup)
121 | let status = SecItemDelete(query as CFDictionary)
122 |
123 | // Throw an error if an unexpected status was returned.
124 | guard status == noErr || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
125 | }
126 |
127 | static func passwordItems(forService service: String, accessGroup: String? = nil) throws -> [KeychainPasswordItem] {
128 | // Build a query for all items that match the service and access group.
129 | var query = KeychainPasswordItem.keychainQuery(withService: service, accessGroup: accessGroup)
130 | query[kSecMatchLimit as String] = kSecMatchLimitAll
131 | query[kSecReturnAttributes as String] = kCFBooleanTrue
132 | query[kSecReturnData as String] = kCFBooleanFalse
133 |
134 | // Fetch matching items from the keychain.
135 | var queryResult: AnyObject?
136 | let status = withUnsafeMutablePointer(to: &queryResult) {
137 | SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
138 | }
139 |
140 | // If no items were found, return an empty array.
141 | guard status != errSecItemNotFound else { return [] }
142 |
143 | // Throw an error if an unexpected status was returned.
144 | guard status == noErr else { throw KeychainError.unhandledError(status: status) }
145 |
146 | // Cast the query result to an array of dictionaries.
147 | guard let resultData = queryResult as? [[String : AnyObject]] else { throw KeychainError.unexpectedItemData }
148 |
149 | // Create a `KeychainPasswordItem` for each dictionary in the query result.
150 | var passwordItems = [KeychainPasswordItem]()
151 | for result in resultData {
152 | guard let account = result[kSecAttrAccount as String] as? String else { throw KeychainError.unexpectedItemData }
153 |
154 | let passwordItem = KeychainPasswordItem(service: service, account: account, accessGroup: accessGroup)
155 | passwordItems.append(passwordItem)
156 | }
157 |
158 | return passwordItems
159 | }
160 |
161 | // MARK: Convenience
162 |
163 | private static func keychainQuery(withService service: String, account: String? = nil, accessGroup: String? = nil) -> [String : AnyObject] {
164 | var query = [String : AnyObject]()
165 | query[kSecClass as String] = kSecClassGenericPassword
166 | query[kSecAttrService as String] = service as AnyObject?
167 |
168 | if let account = account {
169 | query[kSecAttrAccount as String] = account as AnyObject?
170 | }
171 |
172 | if let accessGroup = accessGroup {
173 | query[kSecAttrAccessGroup as String] = accessGroup as AnyObject?
174 | }
175 |
176 | return query
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/PersonalLogs.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 064502D6247BD46800AA475E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064502D5247BD46800AA475E /* AppDelegate.swift */; };
11 | 064502D8247BD46800AA475E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064502D7247BD46800AA475E /* SceneDelegate.swift */; };
12 | 064502DA247BD46800AA475E /* NoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064502D9247BD46800AA475E /* NoteView.swift */; };
13 | 064502DC247BD46F00AA475E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 064502DB247BD46F00AA475E /* Assets.xcassets */; };
14 | 064502DF247BD46F00AA475E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 064502DE247BD46F00AA475E /* Preview Assets.xcassets */; };
15 | 064502E2247BD46F00AA475E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 064502E0247BD46F00AA475E /* LaunchScreen.storyboard */; };
16 | 064502ED247BD47000AA475E /* PersonalLogsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064502EC247BD47000AA475E /* PersonalLogsTests.swift */; };
17 | 064502F8247BD4BD00AA475E /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064502F7247BD4BD00AA475E /* ListViewController.swift */; };
18 | 064502FA247BD4CC00AA475E /* NoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064502F9247BD4CC00AA475E /* NoteViewController.swift */; };
19 | 064502FE247BD50000AA475E /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = 064502FD247BD50000AA475E /* Note.swift */; };
20 | 06450302247BD5D200AA475E /* FileHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06450301247BD5D200AA475E /* FileHelper.swift */; };
21 | 06450304247BD5E500AA475E /* NoteRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06450303247BD5E500AA475E /* NoteRepository.swift */; };
22 | 06450308247C2E6C00AA475E /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06450307247C2E6C00AA475E /* ListView.swift */; };
23 | 0645030A247C657000AA475E /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06450309247C657000AA475E /* ColorExtension.swift */; };
24 | 0676FB2F247D270C008CB582 /* NoteListDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB2E247D270C008CB582 /* NoteListDelegate.swift */; };
25 | 0676FB31247D2734008CB582 /* NoteDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB30247D2734008CB582 /* NoteDelegate.swift */; };
26 | 0676FB33247D9401008CB582 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB32247D9400008CB582 /* Repository.swift */; };
27 | 0676FB752484B5B9008CB582 /* FileManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB742484B5B9008CB582 /* FileManagerExtension.swift */; };
28 | 0676FB7A2484B980008CB582 /* OnBoardingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB792484B980008CB582 /* OnBoardingViewController.swift */; };
29 | 0676FB7C2484B994008CB582 /* OnBoardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB7B2484B994008CB582 /* OnBoardingView.swift */; };
30 | 0676FB7E2484C523008CB582 /* OnBoardingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB7D2484C523008CB582 /* OnBoardingStatus.swift */; };
31 | 0676FB802484C95B008CB582 /* RepositoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB7F2484C95B008CB582 /* RepositoryItem.swift */; };
32 | 0676FB822484CD76008CB582 /* KeychainPasswordItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB812484CD76008CB582 /* KeychainPasswordItem.swift */; };
33 | 0676FB842484CE60008CB582 /* KeychainHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0676FB832484CE60008CB582 /* KeychainHelper.swift */; };
34 | /* End PBXBuildFile section */
35 |
36 | /* Begin PBXContainerItemProxy section */
37 | 064502E9247BD47000AA475E /* PBXContainerItemProxy */ = {
38 | isa = PBXContainerItemProxy;
39 | containerPortal = 064502CA247BD46800AA475E /* Project object */;
40 | proxyType = 1;
41 | remoteGlobalIDString = 064502D1247BD46800AA475E;
42 | remoteInfo = PersonalLogs;
43 | };
44 | /* End PBXContainerItemProxy section */
45 |
46 | /* Begin PBXFileReference section */
47 | 064502D2247BD46800AA475E /* Personal Logs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Personal Logs.app"; sourceTree = BUILT_PRODUCTS_DIR; };
48 | 064502D5247BD46800AA475E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
49 | 064502D7247BD46800AA475E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
50 | 064502D9247BD46800AA475E /* NoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteView.swift; sourceTree = ""; };
51 | 064502DB247BD46F00AA475E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
52 | 064502DE247BD46F00AA475E /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
53 | 064502E1247BD46F00AA475E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
54 | 064502E3247BD46F00AA475E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
55 | 064502E8247BD47000AA475E /* PersonalLogsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PersonalLogsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
56 | 064502EC247BD47000AA475E /* PersonalLogsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalLogsTests.swift; sourceTree = ""; };
57 | 064502EE247BD47000AA475E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
58 | 064502F7247BD4BD00AA475E /* ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewController.swift; sourceTree = ""; };
59 | 064502F9247BD4CC00AA475E /* NoteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteViewController.swift; sourceTree = ""; };
60 | 064502FD247BD50000AA475E /* Note.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Note.swift; sourceTree = ""; };
61 | 06450301247BD5D200AA475E /* FileHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileHelper.swift; sourceTree = ""; };
62 | 06450303247BD5E500AA475E /* NoteRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteRepository.swift; sourceTree = ""; };
63 | 06450307247C2E6C00AA475E /* ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = ""; };
64 | 06450309247C657000AA475E /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; };
65 | 0676FB2E247D270C008CB582 /* NoteListDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteListDelegate.swift; sourceTree = ""; };
66 | 0676FB30247D2734008CB582 /* NoteDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteDelegate.swift; sourceTree = ""; };
67 | 0676FB32247D9400008CB582 /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; };
68 | 0676FB742484B5B9008CB582 /* FileManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExtension.swift; sourceTree = ""; };
69 | 0676FB792484B980008CB582 /* OnBoardingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingViewController.swift; sourceTree = ""; };
70 | 0676FB7B2484B994008CB582 /* OnBoardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingView.swift; sourceTree = ""; };
71 | 0676FB7D2484C523008CB582 /* OnBoardingStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnBoardingStatus.swift; sourceTree = ""; };
72 | 0676FB7F2484C95B008CB582 /* RepositoryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryItem.swift; sourceTree = ""; };
73 | 0676FB812484CD76008CB582 /* KeychainPasswordItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainPasswordItem.swift; sourceTree = ""; };
74 | 0676FB832484CE60008CB582 /* KeychainHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainHelper.swift; sourceTree = ""; };
75 | /* End PBXFileReference section */
76 |
77 | /* Begin PBXFrameworksBuildPhase section */
78 | 064502CF247BD46800AA475E /* Frameworks */ = {
79 | isa = PBXFrameworksBuildPhase;
80 | buildActionMask = 2147483647;
81 | files = (
82 | );
83 | runOnlyForDeploymentPostprocessing = 0;
84 | };
85 | 064502E5247BD47000AA475E /* Frameworks */ = {
86 | isa = PBXFrameworksBuildPhase;
87 | buildActionMask = 2147483647;
88 | files = (
89 | );
90 | runOnlyForDeploymentPostprocessing = 0;
91 | };
92 | /* End PBXFrameworksBuildPhase section */
93 |
94 | /* Begin PBXGroup section */
95 | 064502C9247BD46800AA475E = {
96 | isa = PBXGroup;
97 | children = (
98 | 064502D4247BD46800AA475E /* PersonalLogs */,
99 | 064502EB247BD47000AA475E /* PersonalLogsTests */,
100 | 064502D3247BD46800AA475E /* Products */,
101 | );
102 | sourceTree = "";
103 | };
104 | 064502D3247BD46800AA475E /* Products */ = {
105 | isa = PBXGroup;
106 | children = (
107 | 064502D2247BD46800AA475E /* Personal Logs.app */,
108 | 064502E8247BD47000AA475E /* PersonalLogsTests.xctest */,
109 | );
110 | name = Products;
111 | sourceTree = "";
112 | };
113 | 064502D4247BD46800AA475E /* PersonalLogs */ = {
114 | isa = PBXGroup;
115 | children = (
116 | 064502D5247BD46800AA475E /* AppDelegate.swift */,
117 | 064502D7247BD46800AA475E /* SceneDelegate.swift */,
118 | 0676FB2D247D26FB008CB582 /* Protocols */,
119 | 0676FB762484B659008CB582 /* Extensions */,
120 | 06450300247BD5AE00AA475E /* Helpers */,
121 | 064502FF247BD50300AA475E /* Model */,
122 | 064502FB247BD4D100AA475E /* ViewControllers */,
123 | 064502FC247BD4E900AA475E /* View */,
124 | 064502DB247BD46F00AA475E /* Assets.xcassets */,
125 | 064502E0247BD46F00AA475E /* LaunchScreen.storyboard */,
126 | 064502E3247BD46F00AA475E /* Info.plist */,
127 | 064502DD247BD46F00AA475E /* Preview Content */,
128 | );
129 | path = PersonalLogs;
130 | sourceTree = "";
131 | };
132 | 064502DD247BD46F00AA475E /* Preview Content */ = {
133 | isa = PBXGroup;
134 | children = (
135 | 064502DE247BD46F00AA475E /* Preview Assets.xcassets */,
136 | );
137 | path = "Preview Content";
138 | sourceTree = "";
139 | };
140 | 064502EB247BD47000AA475E /* PersonalLogsTests */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 064502EC247BD47000AA475E /* PersonalLogsTests.swift */,
144 | 064502EE247BD47000AA475E /* Info.plist */,
145 | );
146 | path = PersonalLogsTests;
147 | sourceTree = "";
148 | };
149 | 064502FB247BD4D100AA475E /* ViewControllers */ = {
150 | isa = PBXGroup;
151 | children = (
152 | 064502F7247BD4BD00AA475E /* ListViewController.swift */,
153 | 064502F9247BD4CC00AA475E /* NoteViewController.swift */,
154 | 0676FB792484B980008CB582 /* OnBoardingViewController.swift */,
155 | );
156 | path = ViewControllers;
157 | sourceTree = "";
158 | };
159 | 064502FC247BD4E900AA475E /* View */ = {
160 | isa = PBXGroup;
161 | children = (
162 | 064502D9247BD46800AA475E /* NoteView.swift */,
163 | 06450307247C2E6C00AA475E /* ListView.swift */,
164 | 0676FB7B2484B994008CB582 /* OnBoardingView.swift */,
165 | );
166 | path = View;
167 | sourceTree = "";
168 | };
169 | 064502FF247BD50300AA475E /* Model */ = {
170 | isa = PBXGroup;
171 | children = (
172 | 064502FD247BD50000AA475E /* Note.swift */,
173 | 06450303247BD5E500AA475E /* NoteRepository.swift */,
174 | 0676FB7D2484C523008CB582 /* OnBoardingStatus.swift */,
175 | );
176 | path = Model;
177 | sourceTree = "";
178 | };
179 | 06450300247BD5AE00AA475E /* Helpers */ = {
180 | isa = PBXGroup;
181 | children = (
182 | 06450301247BD5D200AA475E /* FileHelper.swift */,
183 | 0676FB852484DFFB008CB582 /* Keychain */,
184 | );
185 | path = Helpers;
186 | sourceTree = "";
187 | };
188 | 0676FB2D247D26FB008CB582 /* Protocols */ = {
189 | isa = PBXGroup;
190 | children = (
191 | 0676FB7F2484C95B008CB582 /* RepositoryItem.swift */,
192 | 0676FB32247D9400008CB582 /* Repository.swift */,
193 | 0676FB2E247D270C008CB582 /* NoteListDelegate.swift */,
194 | 0676FB30247D2734008CB582 /* NoteDelegate.swift */,
195 | );
196 | path = Protocols;
197 | sourceTree = "";
198 | };
199 | 0676FB762484B659008CB582 /* Extensions */ = {
200 | isa = PBXGroup;
201 | children = (
202 | 06450309247C657000AA475E /* ColorExtension.swift */,
203 | 0676FB742484B5B9008CB582 /* FileManagerExtension.swift */,
204 | );
205 | path = Extensions;
206 | sourceTree = "";
207 | };
208 | 0676FB852484DFFB008CB582 /* Keychain */ = {
209 | isa = PBXGroup;
210 | children = (
211 | 0676FB832484CE60008CB582 /* KeychainHelper.swift */,
212 | 0676FB812484CD76008CB582 /* KeychainPasswordItem.swift */,
213 | );
214 | path = Keychain;
215 | sourceTree = "";
216 | };
217 | /* End PBXGroup section */
218 |
219 | /* Begin PBXNativeTarget section */
220 | 064502D1247BD46800AA475E /* PersonalLogs */ = {
221 | isa = PBXNativeTarget;
222 | buildConfigurationList = 064502F1247BD47000AA475E /* Build configuration list for PBXNativeTarget "PersonalLogs" */;
223 | buildPhases = (
224 | 064502CE247BD46800AA475E /* Sources */,
225 | 064502CF247BD46800AA475E /* Frameworks */,
226 | 064502D0247BD46800AA475E /* Resources */,
227 | );
228 | buildRules = (
229 | );
230 | dependencies = (
231 | );
232 | name = PersonalLogs;
233 | productName = PersonalLogs;
234 | productReference = 064502D2247BD46800AA475E /* Personal Logs.app */;
235 | productType = "com.apple.product-type.application";
236 | };
237 | 064502E7247BD47000AA475E /* PersonalLogsTests */ = {
238 | isa = PBXNativeTarget;
239 | buildConfigurationList = 064502F4247BD47000AA475E /* Build configuration list for PBXNativeTarget "PersonalLogsTests" */;
240 | buildPhases = (
241 | 064502E4247BD47000AA475E /* Sources */,
242 | 064502E5247BD47000AA475E /* Frameworks */,
243 | 064502E6247BD47000AA475E /* Resources */,
244 | );
245 | buildRules = (
246 | );
247 | dependencies = (
248 | 064502EA247BD47000AA475E /* PBXTargetDependency */,
249 | );
250 | name = PersonalLogsTests;
251 | productName = PersonalLogsTests;
252 | productReference = 064502E8247BD47000AA475E /* PersonalLogsTests.xctest */;
253 | productType = "com.apple.product-type.bundle.unit-test";
254 | };
255 | /* End PBXNativeTarget section */
256 |
257 | /* Begin PBXProject section */
258 | 064502CA247BD46800AA475E /* Project object */ = {
259 | isa = PBXProject;
260 | attributes = {
261 | LastSwiftUpdateCheck = 1150;
262 | LastUpgradeCheck = 1150;
263 | ORGANIZATIONNAME = "Academy IFCE";
264 | TargetAttributes = {
265 | 064502D1247BD46800AA475E = {
266 | CreatedOnToolsVersion = 11.5;
267 | };
268 | 064502E7247BD47000AA475E = {
269 | CreatedOnToolsVersion = 11.5;
270 | TestTargetID = 064502D1247BD46800AA475E;
271 | };
272 | };
273 | };
274 | buildConfigurationList = 064502CD247BD46800AA475E /* Build configuration list for PBXProject "PersonalLogs" */;
275 | compatibilityVersion = "Xcode 9.3";
276 | developmentRegion = en;
277 | hasScannedForEncodings = 0;
278 | knownRegions = (
279 | en,
280 | Base,
281 | );
282 | mainGroup = 064502C9247BD46800AA475E;
283 | productRefGroup = 064502D3247BD46800AA475E /* Products */;
284 | projectDirPath = "";
285 | projectRoot = "";
286 | targets = (
287 | 064502D1247BD46800AA475E /* PersonalLogs */,
288 | 064502E7247BD47000AA475E /* PersonalLogsTests */,
289 | );
290 | };
291 | /* End PBXProject section */
292 |
293 | /* Begin PBXResourcesBuildPhase section */
294 | 064502D0247BD46800AA475E /* Resources */ = {
295 | isa = PBXResourcesBuildPhase;
296 | buildActionMask = 2147483647;
297 | files = (
298 | 064502E2247BD46F00AA475E /* LaunchScreen.storyboard in Resources */,
299 | 064502DF247BD46F00AA475E /* Preview Assets.xcassets in Resources */,
300 | 064502DC247BD46F00AA475E /* Assets.xcassets in Resources */,
301 | );
302 | runOnlyForDeploymentPostprocessing = 0;
303 | };
304 | 064502E6247BD47000AA475E /* Resources */ = {
305 | isa = PBXResourcesBuildPhase;
306 | buildActionMask = 2147483647;
307 | files = (
308 | );
309 | runOnlyForDeploymentPostprocessing = 0;
310 | };
311 | /* End PBXResourcesBuildPhase section */
312 |
313 | /* Begin PBXSourcesBuildPhase section */
314 | 064502CE247BD46800AA475E /* Sources */ = {
315 | isa = PBXSourcesBuildPhase;
316 | buildActionMask = 2147483647;
317 | files = (
318 | 064502D6247BD46800AA475E /* AppDelegate.swift in Sources */,
319 | 0645030A247C657000AA475E /* ColorExtension.swift in Sources */,
320 | 06450304247BD5E500AA475E /* NoteRepository.swift in Sources */,
321 | 0676FB33247D9401008CB582 /* Repository.swift in Sources */,
322 | 0676FB802484C95B008CB582 /* RepositoryItem.swift in Sources */,
323 | 0676FB7E2484C523008CB582 /* OnBoardingStatus.swift in Sources */,
324 | 064502D8247BD46800AA475E /* SceneDelegate.swift in Sources */,
325 | 0676FB2F247D270C008CB582 /* NoteListDelegate.swift in Sources */,
326 | 064502F8247BD4BD00AA475E /* ListViewController.swift in Sources */,
327 | 0676FB31247D2734008CB582 /* NoteDelegate.swift in Sources */,
328 | 064502FA247BD4CC00AA475E /* NoteViewController.swift in Sources */,
329 | 0676FB7A2484B980008CB582 /* OnBoardingViewController.swift in Sources */,
330 | 0676FB752484B5B9008CB582 /* FileManagerExtension.swift in Sources */,
331 | 064502DA247BD46800AA475E /* NoteView.swift in Sources */,
332 | 06450302247BD5D200AA475E /* FileHelper.swift in Sources */,
333 | 0676FB822484CD76008CB582 /* KeychainPasswordItem.swift in Sources */,
334 | 0676FB7C2484B994008CB582 /* OnBoardingView.swift in Sources */,
335 | 064502FE247BD50000AA475E /* Note.swift in Sources */,
336 | 06450308247C2E6C00AA475E /* ListView.swift in Sources */,
337 | 0676FB842484CE60008CB582 /* KeychainHelper.swift in Sources */,
338 | );
339 | runOnlyForDeploymentPostprocessing = 0;
340 | };
341 | 064502E4247BD47000AA475E /* Sources */ = {
342 | isa = PBXSourcesBuildPhase;
343 | buildActionMask = 2147483647;
344 | files = (
345 | 064502ED247BD47000AA475E /* PersonalLogsTests.swift in Sources */,
346 | );
347 | runOnlyForDeploymentPostprocessing = 0;
348 | };
349 | /* End PBXSourcesBuildPhase section */
350 |
351 | /* Begin PBXTargetDependency section */
352 | 064502EA247BD47000AA475E /* PBXTargetDependency */ = {
353 | isa = PBXTargetDependency;
354 | target = 064502D1247BD46800AA475E /* PersonalLogs */;
355 | targetProxy = 064502E9247BD47000AA475E /* PBXContainerItemProxy */;
356 | };
357 | /* End PBXTargetDependency section */
358 |
359 | /* Begin PBXVariantGroup section */
360 | 064502E0247BD46F00AA475E /* LaunchScreen.storyboard */ = {
361 | isa = PBXVariantGroup;
362 | children = (
363 | 064502E1247BD46F00AA475E /* Base */,
364 | );
365 | name = LaunchScreen.storyboard;
366 | sourceTree = "";
367 | };
368 | /* End PBXVariantGroup section */
369 |
370 | /* Begin XCBuildConfiguration section */
371 | 064502EF247BD47000AA475E /* Debug */ = {
372 | isa = XCBuildConfiguration;
373 | buildSettings = {
374 | ALWAYS_SEARCH_USER_PATHS = NO;
375 | CLANG_ANALYZER_NONNULL = YES;
376 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
377 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
378 | CLANG_CXX_LIBRARY = "libc++";
379 | CLANG_ENABLE_MODULES = YES;
380 | CLANG_ENABLE_OBJC_ARC = YES;
381 | CLANG_ENABLE_OBJC_WEAK = YES;
382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
383 | CLANG_WARN_BOOL_CONVERSION = YES;
384 | CLANG_WARN_COMMA = YES;
385 | CLANG_WARN_CONSTANT_CONVERSION = YES;
386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
388 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
389 | CLANG_WARN_EMPTY_BODY = YES;
390 | CLANG_WARN_ENUM_CONVERSION = YES;
391 | CLANG_WARN_INFINITE_RECURSION = YES;
392 | CLANG_WARN_INT_CONVERSION = YES;
393 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
394 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
395 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
396 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
397 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
398 | CLANG_WARN_STRICT_PROTOTYPES = YES;
399 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
400 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
401 | CLANG_WARN_UNREACHABLE_CODE = YES;
402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
403 | COPY_PHASE_STRIP = NO;
404 | DEBUG_INFORMATION_FORMAT = dwarf;
405 | ENABLE_STRICT_OBJC_MSGSEND = YES;
406 | ENABLE_TESTABILITY = YES;
407 | GCC_C_LANGUAGE_STANDARD = gnu11;
408 | GCC_DYNAMIC_NO_PIC = NO;
409 | GCC_NO_COMMON_BLOCKS = YES;
410 | GCC_OPTIMIZATION_LEVEL = 0;
411 | GCC_PREPROCESSOR_DEFINITIONS = (
412 | "DEBUG=1",
413 | "$(inherited)",
414 | );
415 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
416 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
417 | GCC_WARN_UNDECLARED_SELECTOR = YES;
418 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
419 | GCC_WARN_UNUSED_FUNCTION = YES;
420 | GCC_WARN_UNUSED_VARIABLE = YES;
421 | IPHONEOS_DEPLOYMENT_TARGET = 13;
422 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
423 | MTL_FAST_MATH = YES;
424 | ONLY_ACTIVE_ARCH = YES;
425 | SDKROOT = iphoneos;
426 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
427 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
428 | };
429 | name = Debug;
430 | };
431 | 064502F0247BD47000AA475E /* Release */ = {
432 | isa = XCBuildConfiguration;
433 | buildSettings = {
434 | ALWAYS_SEARCH_USER_PATHS = NO;
435 | CLANG_ANALYZER_NONNULL = YES;
436 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
437 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
438 | CLANG_CXX_LIBRARY = "libc++";
439 | CLANG_ENABLE_MODULES = YES;
440 | CLANG_ENABLE_OBJC_ARC = YES;
441 | CLANG_ENABLE_OBJC_WEAK = YES;
442 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
443 | CLANG_WARN_BOOL_CONVERSION = YES;
444 | CLANG_WARN_COMMA = YES;
445 | CLANG_WARN_CONSTANT_CONVERSION = YES;
446 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
447 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
448 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
449 | CLANG_WARN_EMPTY_BODY = YES;
450 | CLANG_WARN_ENUM_CONVERSION = YES;
451 | CLANG_WARN_INFINITE_RECURSION = YES;
452 | CLANG_WARN_INT_CONVERSION = YES;
453 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
454 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
455 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
456 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
457 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
458 | CLANG_WARN_STRICT_PROTOTYPES = YES;
459 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
460 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
461 | CLANG_WARN_UNREACHABLE_CODE = YES;
462 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
463 | COPY_PHASE_STRIP = NO;
464 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
465 | ENABLE_NS_ASSERTIONS = NO;
466 | ENABLE_STRICT_OBJC_MSGSEND = YES;
467 | GCC_C_LANGUAGE_STANDARD = gnu11;
468 | GCC_NO_COMMON_BLOCKS = YES;
469 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
470 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
471 | GCC_WARN_UNDECLARED_SELECTOR = YES;
472 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
473 | GCC_WARN_UNUSED_FUNCTION = YES;
474 | GCC_WARN_UNUSED_VARIABLE = YES;
475 | IPHONEOS_DEPLOYMENT_TARGET = 13;
476 | MTL_ENABLE_DEBUG_INFO = NO;
477 | MTL_FAST_MATH = YES;
478 | SDKROOT = iphoneos;
479 | SWIFT_COMPILATION_MODE = wholemodule;
480 | SWIFT_OPTIMIZATION_LEVEL = "-O";
481 | VALIDATE_PRODUCT = YES;
482 | };
483 | name = Release;
484 | };
485 | 064502F2247BD47000AA475E /* Debug */ = {
486 | isa = XCBuildConfiguration;
487 | buildSettings = {
488 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
489 | CODE_SIGN_STYLE = Automatic;
490 | DEVELOPMENT_ASSET_PATHS = "\"PersonalLogs/Preview Content\"";
491 | DEVELOPMENT_TEAM = 526YR6WHWC;
492 | ENABLE_PREVIEWS = YES;
493 | INFOPLIST_FILE = PersonalLogs/Info.plist;
494 | LD_RUNPATH_SEARCH_PATHS = (
495 | "$(inherited)",
496 | "@executable_path/Frameworks",
497 | );
498 | PRODUCT_BUNDLE_IDENTIFIER = academy.IFCE.PersonalLogs;
499 | PRODUCT_NAME = "Personal Logs";
500 | SWIFT_VERSION = 5.0;
501 | TARGETED_DEVICE_FAMILY = "1,2";
502 | };
503 | name = Debug;
504 | };
505 | 064502F3247BD47000AA475E /* Release */ = {
506 | isa = XCBuildConfiguration;
507 | buildSettings = {
508 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
509 | CODE_SIGN_STYLE = Automatic;
510 | DEVELOPMENT_ASSET_PATHS = "\"PersonalLogs/Preview Content\"";
511 | DEVELOPMENT_TEAM = 526YR6WHWC;
512 | ENABLE_PREVIEWS = YES;
513 | INFOPLIST_FILE = PersonalLogs/Info.plist;
514 | LD_RUNPATH_SEARCH_PATHS = (
515 | "$(inherited)",
516 | "@executable_path/Frameworks",
517 | );
518 | PRODUCT_BUNDLE_IDENTIFIER = academy.IFCE.PersonalLogs;
519 | PRODUCT_NAME = "Personal Logs";
520 | SWIFT_VERSION = 5.0;
521 | TARGETED_DEVICE_FAMILY = "1,2";
522 | };
523 | name = Release;
524 | };
525 | 064502F5247BD47000AA475E /* Debug */ = {
526 | isa = XCBuildConfiguration;
527 | buildSettings = {
528 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
529 | BUNDLE_LOADER = "$(TEST_HOST)";
530 | CODE_SIGN_STYLE = Automatic;
531 | DEVELOPMENT_TEAM = 526YR6WHWC;
532 | INFOPLIST_FILE = PersonalLogsTests/Info.plist;
533 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
534 | LD_RUNPATH_SEARCH_PATHS = (
535 | "$(inherited)",
536 | "@executable_path/Frameworks",
537 | "@loader_path/Frameworks",
538 | );
539 | PRODUCT_BUNDLE_IDENTIFIER = academy.IFCE.PersonalLogsTests;
540 | PRODUCT_NAME = "$(TARGET_NAME)";
541 | SWIFT_VERSION = 5.0;
542 | TARGETED_DEVICE_FAMILY = "1,2";
543 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PersonalLogs.app/PersonalLogs";
544 | };
545 | name = Debug;
546 | };
547 | 064502F6247BD47000AA475E /* Release */ = {
548 | isa = XCBuildConfiguration;
549 | buildSettings = {
550 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
551 | BUNDLE_LOADER = "$(TEST_HOST)";
552 | CODE_SIGN_STYLE = Automatic;
553 | DEVELOPMENT_TEAM = 526YR6WHWC;
554 | INFOPLIST_FILE = PersonalLogsTests/Info.plist;
555 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
556 | LD_RUNPATH_SEARCH_PATHS = (
557 | "$(inherited)",
558 | "@executable_path/Frameworks",
559 | "@loader_path/Frameworks",
560 | );
561 | PRODUCT_BUNDLE_IDENTIFIER = academy.IFCE.PersonalLogsTests;
562 | PRODUCT_NAME = "$(TARGET_NAME)";
563 | SWIFT_VERSION = 5.0;
564 | TARGETED_DEVICE_FAMILY = "1,2";
565 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PersonalLogs.app/PersonalLogs";
566 | };
567 | name = Release;
568 | };
569 | /* End XCBuildConfiguration section */
570 |
571 | /* Begin XCConfigurationList section */
572 | 064502CD247BD46800AA475E /* Build configuration list for PBXProject "PersonalLogs" */ = {
573 | isa = XCConfigurationList;
574 | buildConfigurations = (
575 | 064502EF247BD47000AA475E /* Debug */,
576 | 064502F0247BD47000AA475E /* Release */,
577 | );
578 | defaultConfigurationIsVisible = 0;
579 | defaultConfigurationName = Release;
580 | };
581 | 064502F1247BD47000AA475E /* Build configuration list for PBXNativeTarget "PersonalLogs" */ = {
582 | isa = XCConfigurationList;
583 | buildConfigurations = (
584 | 064502F2247BD47000AA475E /* Debug */,
585 | 064502F3247BD47000AA475E /* Release */,
586 | );
587 | defaultConfigurationIsVisible = 0;
588 | defaultConfigurationName = Release;
589 | };
590 | 064502F4247BD47000AA475E /* Build configuration list for PBXNativeTarget "PersonalLogsTests" */ = {
591 | isa = XCConfigurationList;
592 | buildConfigurations = (
593 | 064502F5247BD47000AA475E /* Debug */,
594 | 064502F6247BD47000AA475E /* Release */,
595 | );
596 | defaultConfigurationIsVisible = 0;
597 | defaultConfigurationName = Release;
598 | };
599 | /* End XCConfigurationList section */
600 | };
601 | rootObject = 064502CA247BD46800AA475E /* Project object */;
602 | }
603 |
--------------------------------------------------------------------------------