├── init
├── Gemfile
├── swift-ui-baseUITests
├── Resources
│ ├── LogOutSuccessfully.json
│ ├── AuthenticationError.json
│ ├── GetProfileSuccessfully.json
│ ├── LoginSuccessfully.json
│ └── SignUpSuccessfully.json
├── XCTestCaseExtension.swift
├── XCUIElementExtension.swift
├── Info.plist
├── NetworkMockerExtension.swift
├── NetworkMocker.swift
├── XCUIApplicationExtension.swift
└── swift_ui_baseUITests.swift
├── swift-ui-base
├── Assets.xcassets
│ ├── Contents.json
│ ├── Palette
│ │ ├── Contents.json
│ │ ├── Rootstrap
│ │ │ ├── Contents.json
│ │ │ ├── rsBlue.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── rsGreen.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── rsPink.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── rsRed.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── rsWhite.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── rsYellow.colorset
│ │ │ │ └── Contents.json
│ │ │ ├── rsDarkGray.colorset
│ │ │ │ └── Contents.json
│ │ │ └── rsLightGray.colorset
│ │ │ │ └── Contents.json
│ │ ├── darkGray.colorset
│ │ │ └── Contents.json
│ │ ├── altoGray.colorset
│ │ │ └── Contents.json
│ │ ├── athensGray.colorset
│ │ │ └── Contents.json
│ │ ├── cadetBlue.colorset
│ │ │ └── Contents.json
│ │ └── errorRed.colorset
│ │ │ └── Contents.json
│ ├── User
│ │ ├── Contents.json
│ │ ├── edit_icon.imageset
│ │ │ ├── pencilEditButton.png
│ │ │ ├── pencilEditButton@2x.png
│ │ │ ├── pencilEditButton@3x.png
│ │ │ └── Contents.json
│ │ └── user_avatar_placeholder.imageset
│ │ │ ├── avatarUser.png
│ │ │ ├── avatarUser@2x.png
│ │ │ ├── avatarUser@3x.png
│ │ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Networking
│ ├── Model
│ │ ├── HTTPHeader.swift
│ │ ├── Base64Media.swift
│ │ ├── MultipartMedia.swift
│ │ └── Session.swift
│ └── Service
│ │ ├── UserServices.swift
│ │ ├── AuthenticationServices.swift
│ │ └── APIClient.swift
├── Extensions
│ ├── ColorExtension.swift
│ ├── DictionaryExtension.swift
│ ├── ImageExtension.swift
│ └── StringExtension.swift
├── Navigation
│ ├── Model
│ │ └── ViewRouter.swift
│ └── View
│ │ └── RootView.swift
├── Common
│ ├── Model
│ │ ├── App.swift
│ │ └── User.swift
│ ├── Modifier
│ │ ├── TitleModifier.swift
│ │ └── RoundedButtonModifier.swift
│ └── View
│ │ ├── ActivityIndicator
│ │ └── ActivityIndicatorView.swift
│ │ └── TextField
│ │ ├── Model
│ │ └── TextFieldData.swift
│ │ └── View
│ │ └── TextFieldView.swift
├── SceneDelegate.swift
├── Managers
│ ├── UserDataManager.swift
│ ├── ConfigurationManager.swift
│ └── SessionManager.swift
├── Home
│ ├── View
│ │ ├── AvatarView.swift
│ │ ├── HomeView.swift
│ │ └── ProfileView.swift
│ └── ViewModel
│ │ └── ProfileViewModel.swift
├── ImagePicker
│ └── View
│ │ └── ImagePicker.swift
├── Authentication
│ ├── ViewModel
│ │ ├── LoginViewModel.swift
│ │ └── SignUpViewModel.swift
│ └── View
│ │ ├── LoginView.swift
│ │ └── SignUpView.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── AppDelegate.swift
└── Info.plist
├── .slather.yml
├── CODEOWNERS
├── swift-ui-base.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ └── swift-ui-base.xcscheme
└── project.pbxproj
├── swift-ui-base.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Podfile
├── ThirdPartyKeys.example.plist
├── Podfile.lock
├── swift-ui-baseTests
├── Info.plist
├── Extensions
│ └── StringExtensionUnitTests.swift
└── Services
│ └── UserServiceUnitTests.swift
├── .codeclimate.yml
├── .travis.yml
├── LICENSE
├── LICENSE.md
├── .gitignore
├── .swiftlint.yml
├── init.swift
└── README.md
/init:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/swift-ui-base/HEAD/init
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'slather'
4 |
5 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/Resources/LogOutSuccessfully.json:
--------------------------------------------------------------------------------
1 | {
2 | "success": 1
3 | }
4 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/Resources/AuthenticationError.json:
--------------------------------------------------------------------------------
1 | {
2 | "error": "Authentication error"
3 | }
4 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/swift-ui-base/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/edit_icon.imageset/pencilEditButton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/swift-ui-base/HEAD/swift-ui-base/Assets.xcassets/User/edit_icon.imageset/pencilEditButton.png
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/edit_icon.imageset/pencilEditButton@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/swift-ui-base/HEAD/swift-ui-base/Assets.xcassets/User/edit_icon.imageset/pencilEditButton@2x.png
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/edit_icon.imageset/pencilEditButton@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/swift-ui-base/HEAD/swift-ui-base/Assets.xcassets/User/edit_icon.imageset/pencilEditButton@3x.png
--------------------------------------------------------------------------------
/.slather.yml:
--------------------------------------------------------------------------------
1 | coverage_service: cobertura_xml
2 | workspace: swift-ui-base.xcworkspace
3 | xcodeproj: swift-ui-base.xcodeproj
4 | scheme: swift-ui-base
5 | binary_basename: swift-ui-base
6 | ignore:
7 | - Pods/*
8 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/user_avatar_placeholder.imageset/avatarUser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/swift-ui-base/HEAD/swift-ui-base/Assets.xcassets/User/user_avatar_placeholder.imageset/avatarUser.png
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # These owners will be the default owners for everything in
2 | # the repo. Unless a later match takes precedence
3 |
4 | * @pMalvasio @glm4 @CamilaMoscatelli @mato2593 @germanStabile @fernandatoledo @ximenaperez
5 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/user_avatar_placeholder.imageset/avatarUser@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/swift-ui-base/HEAD/swift-ui-base/Assets.xcassets/User/user_avatar_placeholder.imageset/avatarUser@2x.png
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/user_avatar_placeholder.imageset/avatarUser@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/swift-ui-base/HEAD/swift-ui-base/Assets.xcassets/User/user_avatar_placeholder.imageset/avatarUser@3x.png
--------------------------------------------------------------------------------
/swift-ui-base.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/Resources/GetProfileSuccessfully.json:
--------------------------------------------------------------------------------
1 | {
2 | "user": {
3 | "id": 1,
4 | "email": "automation@test.com",
5 | "first_name": "FirstName",
6 | "last_name": "LastName",
7 | "username": "test"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/swift-ui-base.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/swift-ui-base.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/swift-ui-base.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/Resources/LoginSuccessfully.json:
--------------------------------------------------------------------------------
1 | {
2 | "user": {
3 | "id": 1,
4 | "email": "automation@test.com",
5 | "provider": "email",
6 | "uid": "automation@test.com",
7 | "first_name": "FirstName",
8 | "last_name": "LastName",
9 | "username": "test",
10 | "created_at": "2017-02-23T13:54:33.283Z",
11 | "updated_at": "2017-02-23T13:54:33.425Z"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/Resources/SignUpSuccessfully.json:
--------------------------------------------------------------------------------
1 | {
2 | "user": {
3 | "id": 1,
4 | "email": "automation@test.com",
5 | "provider": "email",
6 | "uid": "automation@test.com",
7 | "first_name": "FirstName",
8 | "last_name": "LastName",
9 | "username": "test",
10 | "created_at": "2017-02-23T13:54:33.283Z",
11 | "updated_at": "2017-02-23T13:54:33.425Z"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :ios, '11.0'
3 |
4 | target 'swift-ui-base' do
5 | use_frameworks!
6 |
7 | pod 'Alamofire', '~> 5.0'
8 |
9 | target 'swift-ui-baseTests' do
10 | inherit! :complete
11 | end
12 |
13 | target 'swift-ui-baseUITests' do
14 | inherit! :complete
15 | pod 'Swifter', '~> 1.4.7'
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/ThirdPartyKeys.example.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ServiceKey
6 |
7 | Release
8 |
9 | Staging
10 |
11 | Debug
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Alamofire (5.2.1)
3 | - Swifter (1.4.7)
4 |
5 | DEPENDENCIES:
6 | - Alamofire (~> 5.0)
7 | - Swifter (~> 1.4.7)
8 |
9 | SPEC REPOS:
10 | trunk:
11 | - Alamofire
12 | - Swifter
13 |
14 | SPEC CHECKSUMS:
15 | Alamofire: e911732990610fe89af59ac0077f923d72dc3dfd
16 | Swifter: 2327ef5d872c638aebab79646ce494af508b0c8f
17 |
18 | PODFILE CHECKSUM: 6f4c7e077f8655ff1363aa04d271fec4008c04a2
19 |
20 | COCOAPODS: 1.8.4
21 |
--------------------------------------------------------------------------------
/swift-ui-base/Networking/Model/HTTPHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPHeader.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/1/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum HTTPHeader: String {
12 | case uid = "uid"
13 | case client = "client"
14 | case token = "access-token"
15 | case expiry = "expiry"
16 | case accept = "Accept"
17 | case contentType = "Content-Type"
18 | }
19 |
--------------------------------------------------------------------------------
/swift-ui-base/Extensions/ColorExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorExtension.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/11/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | extension Color {
12 |
13 | static let darkGray = Color("darkGray")
14 | static let athensGray = Color("athensGray")
15 | static let altoGray = Color("altoGray")
16 | static let cadetBlue = Color("cadetBlue")
17 | static let errorRed = Color("errorRed")
18 | }
19 |
--------------------------------------------------------------------------------
/swift-ui-base/Networking/Model/Base64Media.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Base64Media.swift
3 | // ios-base
4 | //
5 | // Created by German on 7/7/17.
6 | // Copyright © 2017 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 |
12 | class Base64Media: MultipartMedia {
13 | var base64: String
14 |
15 | override init(key: String, data: Data, type: MimeType = .jpeg) {
16 | self.base64 = data.asBase64Param(withType: type)
17 | super.init(key: key, data: data, type: type)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/swift-ui-base/Navigation/Model/ViewRouter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewRouter.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/13/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 |
12 | enum Roots {
13 | case home
14 | case profile
15 | }
16 |
17 | class ViewRouter: ObservableObject {
18 | @Published var currentRoot: Roots = SessionManager.isValidSession ? .profile : .home
19 |
20 | static let shared = ViewRouter()
21 |
22 | fileprivate init() { }
23 | }
24 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/user_avatar_placeholder.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "avatarUser.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "avatarUser@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "avatarUser@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/User/edit_icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "pencilEditButton.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "pencilEditButton@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "pencilEditButton@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/XCTestCaseExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCTestCaseExtension.swift
3 | // swift-ui-baseUITests
4 | //
5 | // Created by Germán Stábile on 2/13/20.
6 | // Copyright © 2020 TopTier labs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | extension XCTestCase {
12 | func waitFor(element: XCUIElement, timeOut: TimeInterval = 2) {
13 | let exists = NSPredicate(format: "exists == 1")
14 |
15 | expectation(for: exists, evaluatedWith: element, handler: nil)
16 | waitForExpectations(timeout: timeOut, handler: nil)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/XCUIElementExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCUIElementExtension.swift
3 | // swift-ui-baseUITests
4 | //
5 | // Created by Germán Stábile on 2/13/20.
6 | // Copyright © 2020 TopTier labs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | extension XCUIElement {
12 |
13 | func clearText(text: String? = nil) {
14 | guard let stringValue = value as? String ?? text else {
15 | return
16 | }
17 |
18 | tap()
19 | let deleteString =
20 | stringValue.map { _ in XCUIKeyboardKey.delete.rawValue }.joined()
21 | typeText(deleteString)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/swift-ui-base/Navigation/View/RootView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavigationView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/13/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct RootView: View {
12 |
13 | @EnvironmentObject var router: ViewRouter
14 |
15 | var body: some View {
16 | VStack {
17 | containedView()
18 | .id(router.currentRoot)
19 | .transition(.slide).animation(.linear(duration: 0.2))
20 | }
21 | }
22 |
23 | func containedView() -> AnyView {
24 | switch router.currentRoot {
25 | case .profile:
26 | return AnyView(ProfileView())
27 | default:
28 | return AnyView(HomeView())
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/swift-ui-base/Common/Model/App.swift:
--------------------------------------------------------------------------------
1 | //
2 | // App.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/1/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct App {
12 | static let domain = Bundle.main.bundleIdentifier ?? ""
13 |
14 | static func error(
15 | domain: ErrorDomain = .generic,
16 | code: Int? = nil,
17 | localizedDescription: String = ""
18 | ) -> NSError {
19 | return NSError(domain: App.domain + "." + domain.rawValue,
20 | code: code ?? 0,
21 | userInfo: [NSLocalizedDescriptionKey: localizedDescription])
22 | }
23 | }
24 |
25 | enum ErrorDomain: String {
26 | case generic = "GenericError"
27 | case parsing = "ParsingError"
28 | }
29 |
--------------------------------------------------------------------------------
/swift-ui-base/Common/Modifier/TitleModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TitleModifier.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/11/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct MainTitle: ViewModifier {
12 | func body(content: Content) -> some View {
13 | content
14 | .padding()
15 | .foregroundColor(.darkGray)
16 | .font(.largeTitle)
17 | }
18 | }
19 |
20 | struct MainTitle_Previews: PreviewProvider {
21 | static var previews: some View {
22 | Group {
23 | Text("Hello, World!")
24 | .previewLayout(.sizeThatFits)
25 |
26 | Text("Hello, World!")
27 | .modifier(MainTitle())
28 | .previewLayout(.sizeThatFits)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/swift-ui-baseTests/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 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/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 |
--------------------------------------------------------------------------------
/swift-ui-base/Common/View/ActivityIndicator/ActivityIndicatorView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityIndicatorView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/13/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ActivityIndicatorView: UIViewRepresentable {
12 |
13 | @Binding var isAnimating: Bool
14 | let style: UIActivityIndicatorView.Style
15 |
16 | func makeUIView(
17 | context: UIViewRepresentableContext
18 | ) -> UIActivityIndicatorView {
19 | return UIActivityIndicatorView(style: style)
20 | }
21 |
22 | func updateUIView(
23 | _ uiView: UIActivityIndicatorView,
24 | context: UIViewRepresentableContext
25 | ) {
26 | isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/swift-ui-base/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/10/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func scene(_ scene: UIScene,
17 | willConnectTo session: UISceneSession,
18 | options connectionOptions: UIScene.ConnectionOptions) {
19 |
20 | if let windowScene = scene as? UIWindowScene {
21 | let window = UIWindow(windowScene: windowScene)
22 | window.rootViewController = UIHostingController(
23 | rootView: RootView().environmentObject(ViewRouter.shared)
24 | )
25 | self.window = window
26 | window.makeKeyAndVisible()
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | version: "2" # required to adjust maintainability checks
2 | checks:
3 | argument-count:
4 | config:
5 | threshold: 8
6 | complex-logic:
7 | config:
8 | threshold: 5
9 | file-lines:
10 | config:
11 | threshold: 500
12 | method-complexity:
13 | config:
14 | threshold: 5
15 | method-count:
16 | config:
17 | threshold: 20
18 | method-lines:
19 | config:
20 | threshold: 25
21 | nested-control-flow:
22 | config:
23 | threshold: 3
24 | return-statements:
25 | enabled: false
26 | similar-code:
27 | config:
28 | threshold: # language-specific defaults. an override will affect all languages.
29 | identical-code:
30 | config:
31 | threshold: # language-specific defaults. an override will affect all languages.
32 | plugins:
33 | swiftlint:
34 | enabled: true
35 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/darkGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "79",
9 | "green" : "61",
10 | "red" : "54"
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.310",
27 | "green" : "0.239",
28 | "red" : "0.212"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/altoGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "216",
9 | "green" : "216",
10 | "red" : "216"
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.847",
27 | "green" : "0.847",
28 | "red" : "0.847"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/athensGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "237",
9 | "green" : "221",
10 | "red" : "229"
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.929",
27 | "green" : "0.867",
28 | "red" : "0.898"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/cadetBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "199",
9 | "green" : "175",
10 | "red" : "167"
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.780",
27 | "green" : "0.686",
28 | "red" : "0.655"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "247",
9 | "green" : "54",
10 | "red" : "0"
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.969",
27 | "green" : "0.212",
28 | "red" : "0.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsGreen.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "133",
9 | "green" : "187",
10 | "red" : "44"
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.522",
27 | "green" : "0.733",
28 | "red" : "0.173"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsPink.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "142",
9 | "green" : "145",
10 | "red" : "255"
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.557",
27 | "green" : "0.569",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "49",
9 | "green" : "64",
10 | "red" : "207"
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.192",
27 | "green" : "0.251",
28 | "red" : "0.812"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsWhite.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "245",
9 | "green" : "242",
10 | "red" : "241"
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.961",
27 | "green" : "0.949",
28 | "red" : "0.945"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsYellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "57",
9 | "green" : "230",
10 | "red" : "234"
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.224",
27 | "green" : "0.902",
28 | "red" : "0.918"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/errorRed.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.318",
9 | "green" : "0.318",
10 | "red" : "1.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.318",
27 | "green" : "0.318",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsDarkGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "131",
9 | "green" : "129",
10 | "red" : "128"
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.514",
27 | "green" : "0.506",
28 | "red" : "0.502"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/Palette/Rootstrap/rsLightGray.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "184",
9 | "green" : "181",
10 | "red" : "181"
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.722",
27 | "green" : "0.710",
28 | "red" : "0.710"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/swift-ui-base/Extensions/DictionaryExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DictionaryExtension.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/1/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //+ Operator definition for Dictionary types
12 | func + (left: [K: V], right: [K: V]) -> [K: V] {
13 | var merge = left
14 | for (key, value) in right {
15 | merge[key] = value
16 | }
17 | return merge
18 | }
19 |
20 | // swiftlint:disable shorthand_operator
21 | func += (left: inout [K: V], right: [K: V]) {
22 | left = left + right
23 | }
24 | // swiftlint:enable shorthand_operator
25 |
26 | extension Dictionary where Key: ExpressibleByStringLiteral {
27 | mutating func lowercaseKeys() {
28 | for key in self.keys {
29 | if let loweredKey = String(describing: key).lowercased() as? Key {
30 | self[loweredKey] = self.removeValue(forKey: key)
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/swift-ui-base/Extensions/ImageExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageExtension.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/3/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIImage {
13 | class func random(size: CGSize = CGSize(width: 100, height: 100)) -> UIImage {
14 | let red = CGFloat(arc4random_uniform(255))
15 | let green = CGFloat(arc4random_uniform(255))
16 | let blue = CGFloat(arc4random_uniform(255))
17 | let color = UIColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: 1.0)
18 | UIGraphicsBeginImageContext(size)
19 | let context = UIGraphicsGetCurrentContext()
20 | context?.setFillColor(color.cgColor)
21 | context?.addRect(CGRect(origin: .zero, size: size))
22 | context?.fillPath()
23 | let image = UIGraphicsGetImageFromCurrentImageContext()
24 | UIGraphicsEndImageContext()
25 | return image ?? UIImage()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/swift-ui-base/Common/Modifier/RoundedButtonModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoundedButtonModifier.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 6/9/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct MainRoundedButton: ViewModifier {
12 |
13 | var backgroundColor: Color = .blue
14 | var radius: CGFloat = 8
15 |
16 | func body(content: Content) -> some View {
17 | content
18 | .frame(width: 300, height: 50)
19 | .font(.headline)
20 | .foregroundColor(.white)
21 | .background(backgroundColor)
22 | .cornerRadius(radius)
23 | }
24 | }
25 |
26 | struct MainRoundedButton_Previews: PreviewProvider {
27 | static var previews: some View {
28 | Group {
29 | Text("Hello, World!")
30 | .padding()
31 | .previewLayout(.sizeThatFits)
32 |
33 | Text("Hello, World!")
34 | .modifier(MainRoundedButton())
35 | .padding()
36 | .previewLayout(.sizeThatFits)
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/swift-ui-base/Networking/Service/UserServices.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserServices.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 6/9/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class UserServices {
12 | class func getMyProfile(
13 | success: @escaping (_ user: User) -> Void,
14 | failure: @escaping (_ error: Error) -> Void
15 | ) {
16 | APIClient.request(
17 | .get,
18 | url: "/user/profile",
19 | success: { response, _ in
20 | guard
21 | let userDictionary = response["user"] as? [String: Any],
22 | let user = User(dictionary: userDictionary)
23 | else {
24 | failure(App.error(
25 | domain: .parsing,
26 | localizedDescription: "Could not parse a valid user".localized
27 | ))
28 | return
29 | }
30 |
31 | UserDataManager.currentUser = user
32 | success(user)
33 | },
34 | failure: failure
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/swift-ui-base/Managers/UserDataManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDataManager.swift
3 | // ios-base
4 | //
5 | // Created by Rootstrap on 15/2/16.
6 | // Copyright © 2016 Rootstrap. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class UserDataManager: NSObject {
12 |
13 | static private let userDefaultUserKey = "swift-ui-base-user"
14 |
15 | static var currentUser: User? {
16 | get {
17 | let defaults = UserDefaults.standard
18 | if
19 | let data = defaults.data(forKey: userDefaultUserKey),
20 | let user = try? JSONDecoder().decode(User.self, from: data)
21 | {
22 | return user
23 | }
24 | return nil
25 | }
26 |
27 | set {
28 | let user = try? JSONEncoder().encode(newValue)
29 | UserDefaults.standard.set(user, forKey: userDefaultUserKey)
30 | }
31 | }
32 |
33 | class func deleteUser() {
34 | UserDefaults.standard.removeObject(forKey: userDefaultUserKey)
35 | }
36 |
37 | static var isUserLogged: Bool {
38 | return currentUser != nil
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | env:
2 | global:
3 | - CC_TEST_REPORTER_ID: c160f6f1729bb88e0b34b95f02292fcc9622317cd77b840252462a996a5eb677
4 | - GIT_COMMITTED_AT=$(if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then git log -1 --pretty=format:%ct; else git log -1 --skip 1 --pretty=format:%ct; fi)
5 | language: swift
6 | xcode_workspace: swift-ui-base.xcworkspace
7 | xcode_scheme: swift-ui-base
8 | osx_image: xcode11.4
9 | cache:
10 | - bundler
11 | - cocoapods
12 | before_install:
13 | - pod repo update
14 | before_script:
15 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-darwin-amd64 > ./cc-test-reporter
16 | - chmod +x ./cc-test-reporter
17 | - ./cc-test-reporter before-build
18 | install:
19 | - bundle install
20 | - pod install
21 | script:
22 | - set -o pipefail
23 | - xcodebuild -workspace swift-ui-base.xcworkspace -scheme swift-ui-base -destination 'platform=iOS Simulator,name=iPhone 11,OS=13.3' build test | xcpretty --test --color
24 | after_script:
25 | - slather coverage
26 | - ./cc-test-reporter after-build -t cobertura --exit-code $TRAVIS_TEST_RESULT
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Rootstrap
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 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Rootstrap
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 |
--------------------------------------------------------------------------------
/swift-ui-base/Common/View/TextField/Model/TextFieldData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextFieldData.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/11/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class TextFieldData: ObservableObject {
12 | var value: String = "" {
13 | didSet {
14 | validate()
15 | }
16 | }
17 |
18 | var validationType: ValidationType
19 | var errorMessage: String
20 | var title: String
21 | var isSecure = false
22 |
23 | var isValid = true
24 |
25 | var isEmpty: Bool {
26 | return value.isEmpty
27 | }
28 |
29 | init(title: String,
30 | value: String = "",
31 | validationType: ValidationType = .none,
32 | isSecure: Bool = false,
33 | errorMessage: String = "") {
34 | self.title = title
35 | self.value = value
36 | self.validationType = validationType
37 | self.errorMessage = errorMessage
38 | self.isSecure = isSecure
39 |
40 | validate()
41 | }
42 |
43 | func validate() {
44 | isValid = value.isValid(type: validationType)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/swift-ui-base/Managers/ConfigurationManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfigurationManager.swift
3 | // swift-ui-base
4 | //
5 | // Created by Pablo Malvasio on 7/30/20.
6 | // Copyright © 2017 Rootstrap Inc. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ConfigurationManager: NSObject {
12 |
13 | class func getValue(
14 | for key: String,
15 | on propertyList: String = "ThirdPartyKeys"
16 | ) -> String? {
17 | if
18 | let path = Bundle.main.path(forResource: propertyList, ofType: "plist"),
19 | let dict = NSDictionary(contentsOfFile: path) as? [String: AnyObject]
20 | {
21 | if let configDict = dict[key] as? [String: AnyObject] {
22 | let key = Bundle.main.object(
23 | forInfoDictionaryKey: "ConfigurationName"
24 | ) as? String ?? ""
25 | return configDict[key] as? String
26 | } else if let value = dict[key] as? String {
27 | return value
28 | }
29 | }
30 |
31 | print("""
32 | \(propertyList).plist NOT FOUND -
33 | Please check your project configuration in: \n https://github.com/rootstrap/swift-ui-base
34 | """)
35 | return nil
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/swift-ui-base/Home/View/AvatarView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AvatarView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 6/9/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct AvatarView: View {
12 |
13 | @Binding var image: Image?
14 | @Binding var isShowingImagePicker: Bool
15 |
16 | var body: some View {
17 | ZStack(alignment: .bottomTrailing) {
18 |
19 | avatarImage
20 | .resizable()
21 | .frame(width: 100, height: 100)
22 | .clipShape(Circle())
23 |
24 | Button(action: { self.editAvatarButtonTapped() }) {
25 | Image("edit_icon")
26 | .resizable()
27 | .aspectRatio(contentMode: .fit)
28 | .frame(width: 20, height: 20)
29 | .foregroundColor(.darkGray)
30 |
31 | }
32 | .padding(EdgeInsets(
33 | top: 0,
34 | leading: 0,
35 | bottom: 10,
36 | trailing: 0
37 | ))
38 | }
39 | }
40 |
41 | var avatarImage: Image {
42 | image ?? Image("user_avatar_placeholder")
43 | }
44 |
45 | func editAvatarButtonTapped() {
46 | isShowingImagePicker = true
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ## User settings
3 | xcuserdata/
4 |
5 | ## Obj-C/Swift specific
6 | *.hmap
7 |
8 | ## App packaging
9 | *.ipa
10 | *.dSYM.zip
11 | *.dSYM
12 |
13 | ## Playgrounds
14 | timeline.xctimeline
15 | playground.xcworkspace
16 |
17 | # Swift Package Manager
18 | #
19 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
20 | # Packages/
21 | # Package.pins
22 | # Package.resolved
23 | # *.xcodeproj
24 | #
25 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
26 | # hence it is not needed unless you have added a package configuration file to your project
27 | # .swiftpm
28 |
29 | .build/
30 |
31 |
32 | Pods/
33 | #
34 | # Add this line if you want to avoid checking in source code from the Xcode workspace
35 | # *.xcworkspace
36 |
37 |
38 | fastlane/report.xml
39 | fastlane/Preview.html
40 | fastlane/screenshots/**/*.png
41 | fastlane/test_output
42 |
43 | # Code Injection
44 | #
45 | # After new code Injection tools there's a generated folder /iOSInjectionProject
46 | # https://github.com/johnno1962/injectionforxcode
47 |
48 | iOSInjectionProject/
49 |
50 | **/.DS_Store
51 |
52 | # Private keys
53 | **/ThirdPartyKeys.plist
--------------------------------------------------------------------------------
/swift-ui-base/Common/Model/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // ios-base
4 | //
5 | // Created by Rootstrap on 1/18/17.
6 | // Copyright © 2017 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct User: Codable {
12 | var id: Int
13 | var username: String
14 | var email: String
15 | var imageURL: URL?
16 |
17 | private enum CodingKeys: String, CodingKey {
18 | case id
19 | case username
20 | case email
21 | case imageURL = "profile_picture"
22 | }
23 |
24 | init?(dictionary: [String: Any]) {
25 | guard
26 | let id = dictionary[CodingKeys.id.rawValue] as? Int,
27 | let username = dictionary[CodingKeys.username.rawValue] as? String,
28 | let email = dictionary[CodingKeys.email.rawValue] as? String
29 | else {
30 | return nil
31 | }
32 |
33 | self.id = id
34 | self.username = username
35 | self.email = email
36 | self.imageURL = URL(
37 | string: dictionary[CodingKeys.imageURL.rawValue] as? String ?? ""
38 | )
39 | }
40 |
41 | init(id: Int, username: String, email: String, imageURL: URL? = nil) {
42 | self.id = id
43 | self.username = username
44 | self.email = email
45 | self.imageURL = imageURL
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/swift-ui-base/Managers/SessionManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionDataManager.swift
3 | // swift-ui-base
4 | //
5 | // Created by Juan Pablo Mazza on 11/8/16.
6 | // Copyright © 2016 Rootstrap. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SessionManager: NSObject {
12 |
13 | static private let userDefaultSessionKey = "swift-ui-base-session"
14 |
15 | static var currentSession: Session? {
16 | get {
17 | if
18 | let data = UserDefaults.standard.data(forKey: userDefaultSessionKey),
19 | let session = try? JSONDecoder().decode(Session.self, from: data)
20 | {
21 | return session
22 | }
23 | return nil
24 | }
25 |
26 | set {
27 | let session = try? JSONEncoder().encode(newValue)
28 | UserDefaults.standard.set(session, forKey: userDefaultSessionKey)
29 | }
30 | }
31 |
32 | class func deleteSession() {
33 | UserDefaults.standard.removeObject(forKey: userDefaultSessionKey)
34 | }
35 |
36 | static var isValidSession: Bool {
37 | if
38 | let session = currentSession,
39 | let uid = session.uid,
40 | let token = session.accessToken,
41 | let client = session.client
42 | {
43 | return !uid.isEmpty && !token.isEmpty && !client.isEmpty
44 | }
45 |
46 | return false
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules: # rule identifiers to exclude from running
2 | - todo
3 | - trailing_whitespace
4 | - type_name # deactivated because swift3 need lowercase enums
5 | opt_in_rules: # some rules are only opt-in
6 | - array_init
7 | - empty_count
8 | - contains_over_first_not_nil
9 | - explicit_init
10 | - fatal_error_message
11 | - first_where
12 | - force_unwrapping
13 | - implicit_return
14 | - joined_default_parameter
15 | - literal_expression_end_indentation
16 | - operator_usage_whitespace
17 | - overridden_super_call
18 | - yoda_condition
19 | # Find all the available rules by running:
20 | # swiftlint rules
21 | whitelist_rules:
22 | excluded: # paths to ignore during linting. Takes precedence over `included`.
23 | - Carthage
24 | - Pods
25 | - init.swift
26 | force_cast: error
27 | force_try:
28 | severity: warning # explicitly
29 | line_length:
30 | warning: 90
31 | error: 100
32 | type_body_length:
33 | warning: 300
34 | error: 400
35 | file_length:
36 | warning: 500
37 | error: 850
38 | function_parameter_count:
39 | warning: 6
40 | error: 8
41 | cyclomatic_complexity:
42 | ignores_case_statements: true
43 | warning: 5
44 | error: 7
45 | function_body_length:
46 | warning: 25
47 | error: 32
48 | identifier_name:
49 | excluded: ["id"]
50 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle)
51 |
--------------------------------------------------------------------------------
/swift-ui-base/Home/ViewModel/ProfileViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileViewModel.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/30/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class ProfileViewModel: ObservableObject, Identifiable {
13 |
14 | @Published var image: UIImage?
15 | @Published var isLoading = false
16 | @Published var shouldShowAlert = false
17 | @Published var errorDescription = ""
18 |
19 | var username: String {
20 | return UserDataManager.currentUser?.email ?? ""
21 | }
22 |
23 | func logout() {
24 | AuthenticationServices.logout()
25 | }
26 |
27 | func deleteAccount() {
28 | AuthenticationServices.deleteAccount()
29 | }
30 |
31 | func getMyProfile() {
32 | isLoading = true
33 | UserServices.getMyProfile(
34 | success: { [weak self] _ in
35 | self?.errorDescription = ""
36 | self?.shouldShowAlert = true
37 | self?.isLoading = false
38 | },
39 | failure: { [weak self] error in
40 | self?.isLoading = false
41 | self?.shouldShowAlert = true
42 | self?.errorDescription = error.localizedDescription
43 | })
44 | }
45 |
46 | func saveAvatar() {
47 | //TODO
48 | //guard let image = image else { return }
49 | isLoading = true
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/swift-ui-base/Networking/Model/MultipartMedia.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultipartMedia.swift
3 | // ios-base
4 | //
5 | // Created by German on 7/7/17.
6 | // Copyright © 2017 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 |
12 | //Basic media MIME types, add more if needed.
13 | enum MimeType: String {
14 | case jpeg = "image/jpeg"
15 | case bmp = "image/bmp"
16 | case png = "image/png"
17 |
18 | case mov = "video/quicktime"
19 | case mpeg = "video/mpeg"
20 | case avi = "video/avi"
21 | case json = "application/json"
22 |
23 | var fileExtension: String {
24 | switch self {
25 | case .bmp: return ".bmp"
26 | case .png: return ".png"
27 | case .mov: return ".mov"
28 | case .mpeg: return ".mpeg"
29 | case .avi: return ".avi"
30 | case .json: return ".json"
31 | default: return ".jpg"
32 | }
33 | }
34 | }
35 |
36 | class MultipartMedia {
37 | var key: String
38 | var data: Data
39 | var type: MimeType
40 | var toFile: String {
41 | return key.validFilename + type.fileExtension
42 | }
43 |
44 | init(key: String, data: Data, type: MimeType = .jpeg) {
45 | self.key = key
46 | self.data = data
47 | self.type = type
48 | }
49 |
50 | func embed(inForm multipart: MultipartFormData) {
51 | multipart.append(data, withName: key, fileName: toFile, mimeType: type.rawValue)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/swift-ui-base/Networking/Model/Session.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Session.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/1/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Session: Codable {
12 | var uid: String?
13 | var client: String?
14 | var accessToken: String?
15 | var expiry: Date?
16 |
17 | private enum CodingKeys: String, CodingKey {
18 | case uid
19 | case client
20 | case accessToken = "access-token"
21 | case expiry
22 | }
23 |
24 | init(
25 | uid: String? = nil, client: String? = nil,
26 | token: String? = nil, expires: Date? = nil
27 | ) {
28 | self.uid = uid
29 | self.client = client
30 | self.accessToken = token
31 | self.expiry = expires
32 | }
33 |
34 | init?(headers: [String: Any]) {
35 | var loweredHeaders = headers
36 | loweredHeaders.lowercaseKeys()
37 | guard let stringHeaders = loweredHeaders as? [String: String] else {
38 | return nil
39 | }
40 | if let expiryString = stringHeaders[HTTPHeader.expiry.rawValue],
41 | let expiryNumber = Double(expiryString) {
42 | expiry = Date(timeIntervalSince1970: expiryNumber)
43 | }
44 | uid = stringHeaders[HTTPHeader.uid.rawValue]
45 | client = stringHeaders[HTTPHeader.client.rawValue]
46 | accessToken = stringHeaders[HTTPHeader.token.rawValue]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/swift-ui-base/Home/View/HomeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/10/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct HomeView: View {
12 |
13 | var body: some View {
14 | NavigationView {
15 | VStack {
16 | Spacer()
17 |
18 | Text("Welcome to RS SwiftUI base!")
19 | .modifier(MainTitle())
20 |
21 | Spacer()
22 |
23 | NavigationLink(destination: LoginView()) {
24 | Text("Log In")
25 | .frame(width: 300, height: 50)
26 | .font(.subheadline)
27 | .background(Color.blue)
28 | .foregroundColor(.white)
29 | .cornerRadius(8)
30 | .padding(.bottom, 20)
31 | }
32 | .accessibility(identifier: "GoToLoginLink")
33 |
34 | NavigationLink(destination: SignUpView()) {
35 | Text("Don't have an account? Lets create one! ►")
36 | .frame(width: 300, height: 50)
37 | .font(.subheadline)
38 | .foregroundColor(.gray)
39 | .cornerRadius(8)
40 | }
41 | .accessibility(identifier: "GoToSignUpLink")
42 |
43 | Spacer()
44 | }
45 | }
46 | }
47 | }
48 |
49 | struct HomeView_Previews: PreviewProvider {
50 | static var previews: some View {
51 | HomeView()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/NetworkMockerExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkMockerExtension.swift
3 | // swift-ui-baseUITests
4 | //
5 | // Created by Germán Stábile on 6/30/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NetworkMocker {
12 |
13 | func stubSignUp(shouldSucceed: Bool = true) {
14 | let responseFileName = shouldSucceed ? "SignUpSuccessfully" : "AuthenticationError"
15 |
16 | setupStub(
17 | url: "/users/",
18 | responseFilename: responseFileName,
19 | method: .POST
20 | )
21 | }
22 |
23 | func stubLogOut() {
24 | setupStub(
25 | url: "/users/sign_out",
26 | responseFilename: "LogOutSuccessfully",
27 | method: .DELETE
28 | )
29 | }
30 |
31 | func stubDeleteAccount() {
32 | setupStub(
33 | url: "/user/delete_account",
34 | responseFilename: "LogOutSuccessfully",
35 | method: .DELETE
36 | )
37 | }
38 |
39 | func stubLogIn(shouldSucceed: Bool = true) {
40 | let responseFilename = shouldSucceed ? "LoginSuccessfully" : "AuthenticationError"
41 |
42 | setupStub(
43 | url: "/users/sign_in",
44 | responseFilename: responseFilename,
45 | method: .POST
46 | )
47 | }
48 |
49 | func stubGetProfile() {
50 | setupStub(
51 | url: "/user/profile",
52 | responseFilename: "GetProfileSuccessfully",
53 | method: .GET
54 | )
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/swift-ui-baseTests/Extensions/StringExtensionUnitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringExtensionUnitTests.swift
3 | // ios-baseUnitTests
4 | //
5 | // Created by German on 4/30/20.
6 | // Copyright © 2020 TopTier labs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import swift_ui_base
11 |
12 | class StringExtensionUnitTests: XCTestCase {
13 | func testEmailValidation() {
14 | XCTAssertFalse("username".isEmailFormatted())
15 | XCTAssertFalse("username@test".isEmailFormatted())
16 | XCTAssert("username@test.com".isEmailFormatted())
17 | XCTAssert("username.alias+2@gmail.com".isEmailFormatted())
18 | }
19 |
20 | func testPhoneNumberValidation() {
21 | XCTAssertFalse("dlfkhgakdfs".isPhoneNumber())
22 | XCTAssertFalse("1241353".isPhoneNumber())
23 | XCTAssert("123-123-4314".isPhoneNumber())
24 | XCTAssert("000-000-0000".isPhoneNumber())
25 | }
26 |
27 | func testAlphanumeric() {
28 | XCTAssertFalse("123598 sdg asd".isAlphanumericWithNoSpaces)
29 | XCTAssert("1231234314".isAlphanumericWithNoSpaces)
30 | XCTAssert("asdgasdg".isAlphanumericWithNoSpaces)
31 | XCTAssert("asdgasd28352".isAlphanumericWithNoSpaces)
32 | }
33 |
34 | func testHasNumbers() {
35 | XCTAssertFalse("asdgasdfgkjasf ".hasNumbers)
36 | XCTAssertFalse("$#^&@".hasNumbers)
37 | XCTAssert("asd235".hasNumbers)
38 | XCTAssert("124".hasNumbers)
39 | }
40 |
41 | func testHasPunctuation() {
42 | XCTAssertFalse("asgfasfdhg123".hasPunctuationCharacters)
43 | XCTAssert("asdg,asd !".hasPunctuationCharacters)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/swift-ui-base/ImagePicker/View/ImagePicker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagePicker.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 5/19/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ImagePicker: UIViewControllerRepresentable {
12 |
13 | @Environment(\.presentationMode) var presentationMode
14 | @Binding var image: UIImage?
15 |
16 | func makeCoordinator() -> Coordinator {
17 | Coordinator(self)
18 | }
19 |
20 | func makeUIViewController(
21 | context: UIViewControllerRepresentableContext
22 | ) -> UIImagePickerController {
23 | let picker = UIImagePickerController()
24 | picker.delegate = context.coordinator
25 | return picker
26 | }
27 |
28 | func updateUIViewController(
29 | _ uiViewController: UIImagePickerController,
30 | context: UIViewControllerRepresentableContext
31 | ) {
32 |
33 | }
34 |
35 | class Coordinator: NSObject,
36 | UINavigationControllerDelegate, UIImagePickerControllerDelegate {
37 |
38 | let parent: ImagePicker
39 |
40 | init(_ parent: ImagePicker) {
41 | self.parent = parent
42 | }
43 |
44 | func imagePickerController(
45 | _ picker: UIImagePickerController,
46 | didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
47 | ) {
48 | if let uiImage = info[.originalImage] as? UIImage {
49 | parent.image = uiImage
50 | }
51 |
52 | parent.presentationMode.wrappedValue.dismiss()
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/swift-ui-base/Authentication/ViewModel/LoginViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewModel.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/12/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 | import Combine
12 |
13 | enum LoginViewModelState {
14 | case loading
15 | case idle
16 | case signedIn
17 | case error(String)
18 | }
19 |
20 | class LoginViewModel: ObservableObject, Identifiable {
21 |
22 | @Published var emailData = TextFieldData(
23 | title: "Email",
24 | validationType: .email,
25 | errorMessage: "Please enter a valid email"
26 | )
27 |
28 | @Published var passwordData = TextFieldData(
29 | title: "Password",
30 | validationType: .nonEmpty,
31 | isSecure: true,
32 | errorMessage: "Please enter a valid password"
33 | )
34 |
35 | @Published var isLoading = false
36 | @Published var errored = false
37 | var error: String = ""
38 |
39 | var isValidData: Bool {
40 | return [emailData, passwordData].allSatisfy { $0.isValid }
41 | }
42 |
43 | func attemptSignin() {
44 | isLoading = true
45 |
46 | AuthenticationServices.login(
47 | emailData.value,
48 | password: passwordData.value,
49 | success: { [weak self] in
50 | self?.isLoading = false
51 | ViewRouter.shared.currentRoot = .profile
52 | },
53 | failure: { [weak self] error in
54 | self?.isLoading = false
55 | self?.errored = true
56 | self?.error = error.localizedDescription
57 | })
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/swift-ui-base/Authentication/View/LoginView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/10/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct LoginView: View {
12 |
13 | @ObservedObject var viewModel = LoginViewModel()
14 |
15 | var body: some View {
16 | ZStack {
17 | ActivityIndicatorView(isAnimating: $viewModel.isLoading, style: .medium)
18 |
19 | VStack {
20 | Text("Sign In")
21 | .modifier(MainTitle())
22 |
23 | Spacer()
24 |
25 | VStack(spacing: 20) {
26 | TextFieldView(fieldData: $viewModel.emailData)
27 | TextFieldView(fieldData: $viewModel.passwordData)
28 | }
29 |
30 | Spacer()
31 |
32 | Button(action: loginButtonTapped) {
33 | Text("Sign In")
34 | .font(.headline)
35 | }
36 | .accessibility(identifier: "SignInButton")
37 | .disabled(!viewModel.isValidData)
38 |
39 | Spacer()
40 | }
41 | .disabled(viewModel.isLoading)
42 | .blur(radius: viewModel.isLoading ? 3 : 0)
43 | .alert(isPresented: $viewModel.errored) {
44 | Alert(title: Text("Oops"),
45 | message: Text(viewModel.error),
46 | dismissButton: .default(Text("Got it!")))
47 | }
48 | }
49 | }
50 |
51 | func loginButtonTapped() {
52 | viewModel.attemptSignin()
53 | }
54 | }
55 |
56 | struct LoginView_Previews: PreviewProvider {
57 | static var previews: some View {
58 | LoginView()
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/swift-ui-base/Authentication/View/SignUpView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignUpView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/10/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct SignUpView: View {
12 | @ObservedObject var viewModel = SignUpViewModel()
13 |
14 | var body: some View {
15 | ZStack {
16 | ActivityIndicatorView(isAnimating: $viewModel.isLoading, style: .medium)
17 |
18 | VStack {
19 | Text("Sign Up")
20 | .modifier(MainTitle())
21 |
22 | Spacer()
23 |
24 | VStack(spacing: 20) {
25 | TextFieldView(fieldData: $viewModel.emailData)
26 | TextFieldView(fieldData: $viewModel.passwordData)
27 | TextFieldView(fieldData: $viewModel.confirmPasswordData)
28 | }
29 | Spacer()
30 |
31 | Button(action: signUpButtonTapped, label: {
32 | Text("Sign Up")
33 | .font(.headline)
34 | })
35 | .accessibility(identifier: "SignUpButton")
36 | .disabled(!viewModel.isValidData)
37 |
38 | Spacer()
39 | }
40 | .disabled(viewModel.isLoading)
41 | .blur(radius: viewModel.isLoading ? 3 : 0)
42 | .alert(isPresented: $viewModel.errored) {
43 | Alert(title: Text("Oops"),
44 | message: Text(viewModel.error),
45 | dismissButton: .default(Text("Got it!")))
46 | }
47 | }
48 | }
49 |
50 | func signUpButtonTapped() {
51 | viewModel.attemptSingUp()
52 | }
53 | }
54 |
55 | struct SignUpView_Previews: PreviewProvider {
56 | static var previews: some View {
57 | SignUpView()
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/NetworkMocker.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkMocker.swift
3 | // swift-ui-baseUITests
4 | //
5 | // Created by Germán Stábile on 6/30/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Swifter
11 |
12 | enum HTTPMethod {
13 | case POST
14 | case GET
15 | case PUT
16 | case DELETE
17 | }
18 |
19 | class NetworkMocker {
20 |
21 | var server = HttpServer()
22 |
23 | func setUp() {
24 | try! server.start()
25 | }
26 |
27 | func tearDown() {
28 | server.stop()
29 | }
30 |
31 | public func setupStub(
32 | url: String,
33 | responseFilename: String,
34 | method: HTTPMethod = .GET
35 | ) {
36 | let testBundle = Bundle(for: type(of: self))
37 | let filePath = testBundle.path(forResource: responseFilename, ofType: "json") ?? ""
38 | let fileUrl = URL(fileURLWithPath: filePath)
39 | guard let data = try? Data(contentsOf: fileUrl, options: .uncached) else {
40 | fatalError("Could not parse mocked data")
41 | return
42 | }
43 | let json = dataToJSON(data: data)
44 |
45 | let response: ((HttpRequest) -> HttpResponse) = { _ in
46 | return HttpResponse.ok(.json(json as AnyObject))
47 | }
48 |
49 | switch method {
50 | case .GET: server.GET[url] = response
51 | case .POST: server.POST[url] = response
52 | case .DELETE: server.DELETE[url] = response
53 | case .PUT: server.PUT[url] = response
54 | }
55 | }
56 |
57 | func dataToJSON(data: Data) -> Any? {
58 | do {
59 | return try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
60 | } catch let error {
61 | print(error)
62 | }
63 | return nil
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/swift-ui-base/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 |
--------------------------------------------------------------------------------
/swift-ui-base/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/10/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | static let shared: AppDelegate = {
15 | guard let appD = UIApplication.shared.delegate as? AppDelegate else {
16 | return AppDelegate()
17 | }
18 | return appD
19 | }()
20 |
21 | func application(
22 | _ application: UIApplication,
23 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
24 | ) -> Bool {
25 | // Override point for customization after application launch.
26 | return true
27 | }
28 |
29 | // MARK: UISceneSession Lifecycle
30 |
31 | func application(
32 | _ application: UIApplication,
33 | configurationForConnecting connectingSceneSession: UISceneSession,
34 | options: UIScene.ConnectionOptions
35 | ) -> UISceneConfiguration {
36 | // Called when a new scene session is being created.
37 | // Use this method to select a configuration to create the new scene with.
38 | return UISceneConfiguration(
39 | name: "Default Configuration",
40 | sessionRole: connectingSceneSession.role
41 | )
42 | }
43 |
44 | func unexpectedLogout() {
45 | //TODO
46 | // UserDataManager.deleteUser()
47 | // SessionManager.deleteSession()
48 | // //Clear any local data if needed
49 | // //Take user to onboarding if needed, do NOT redirect the user
50 | // // if is already in the landing to avoid losing the current VC stack state.
51 | // if window?.rootViewController is HomeViewController {
52 | // AppNavigator.shared.navigate(to: OnboardingRoutes.firstScreen, with: .changeRoot)
53 | // }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/swift-ui-base/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/swift-ui-base/Authentication/ViewModel/SignUpViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SignUpViewModel.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/3/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class SignUpViewModel: ObservableObject, Identifiable {
13 | @Published var emailData = TextFieldData(
14 | title: "Email",
15 | validationType: .email,
16 | errorMessage: "Please enter a valid email"
17 | )
18 |
19 | @Published var passwordData = TextFieldData(
20 | title: "Password",
21 | validationType: .nonEmpty,
22 | isSecure: true,
23 | errorMessage: "Passwords don't match"
24 | )
25 |
26 | @Published var confirmPasswordData = TextFieldData(
27 | title: "Confirm Password",
28 | validationType: .nonEmpty,
29 | isSecure: true,
30 | errorMessage: "Passwords don't match"
31 | )
32 |
33 | @Published var isLoading = false
34 | @Published var errored = false
35 | var error: String = ""
36 |
37 | init() {
38 | confirmPasswordData.validationType = .custom(isValid: passwordsMatch)
39 | passwordData.validationType = .custom(isValid: passwordsMatch)
40 | }
41 |
42 | var isValidData: Bool {
43 | return [emailData, passwordData, confirmPasswordData].allSatisfy { $0.isValid }
44 | }
45 |
46 | func passwordsMatch() -> Bool {
47 | guard !passwordData.isEmpty else { return false }
48 | let areValidPasswords = passwordData.value == confirmPasswordData.value
49 | passwordData.isValid = areValidPasswords
50 | confirmPasswordData.isValid = areValidPasswords
51 | return areValidPasswords
52 | }
53 |
54 | func attemptSingUp() {
55 | isLoading = true
56 | AuthenticationServices.signup(
57 | emailData.value,
58 | password: passwordData.value,
59 | avatar64: UIImage.random(),
60 | success: { [weak self] _ in
61 | self?.isLoading = false
62 | ViewRouter.shared.currentRoot = .profile
63 | },
64 | failure: { [weak self] error in
65 | self?.isLoading = false
66 | self?.errored = true
67 | self?.error = error.localizedDescription
68 | })
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/swift-ui-base/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Base URL
6 | $(BASE_URL)
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | ConfigurationName
24 | Debug
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSAllowsLocalNetworking
30 |
31 |
32 | UIApplicationSceneManifest
33 |
34 | UIApplicationSupportsMultipleScenes
35 |
36 | UISceneConfigurations
37 |
38 | UIWindowSceneSessionRoleApplication
39 |
40 |
41 | UISceneConfigurationName
42 | Default Configuration
43 | UISceneDelegateClassName
44 | $(PRODUCT_MODULE_NAME).SceneDelegate
45 |
46 |
47 |
48 |
49 | UILaunchStoryboardName
50 | LaunchScreen
51 | UIRequiredDeviceCapabilities
52 |
53 | armv7
54 |
55 | UISupportedInterfaceOrientations
56 |
57 | UIInterfaceOrientationPortrait
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 | UISupportedInterfaceOrientations~ipad
62 |
63 | UIInterfaceOrientationPortrait
64 | UIInterfaceOrientationPortraitUpsideDown
65 | UIInterfaceOrientationLandscapeLeft
66 | UIInterfaceOrientationLandscapeRight
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/XCUIApplicationExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCUIApplicationExtension.swift
3 | // swift-ui-baseUITests
4 | //
5 | // Created by Germán Stábile on 2/13/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | extension XCUIApplication {
12 | func type(text: String, on fieldName: String, isSecure: Bool = false) {
13 | let fields = isSecure ? secureTextFields : textFields
14 | let field = fields[fieldName]
15 | field.tap()
16 | field.typeText(text)
17 | }
18 |
19 | func dismissKeyboard() {
20 | //ugly hack to dismiss keyboard
21 | let field = textFields.firstMatch
22 | field.tap()
23 | field.typeText("\n")
24 | }
25 |
26 | func clearText(on fieldName: String) {
27 | let field = textFields[fieldName]
28 | field.tap()
29 | field.clearText()
30 | }
31 |
32 | func deleteAccountIfNeeded(in testCase: XCTestCase) {
33 | let deleteAccountButton = buttons["DeleteAccountButton"]
34 | let goToLoginButton = buttons["GoToLoginLink"]
35 |
36 | if deleteAccountButton.exists {
37 | deleteAccountButton.tap()
38 | testCase.waitFor(element: goToLoginButton, timeOut: 5)
39 | }
40 | }
41 |
42 | func attemptSignIn(
43 | in testCase: XCTestCase,
44 | with email: String,
45 | password: String
46 | ) {
47 | let goToSignInButton = buttons["GoToLoginLink"]
48 |
49 | goToSignInButton.tap()
50 |
51 | let signInButton = buttons["SignInButton"]
52 |
53 | testCase.waitFor(element: signInButton, timeOut: 2)
54 |
55 | type(text: email, on: "EmailTextField")
56 | type(text: password, on: "PasswordTextField", isSecure: true)
57 |
58 | dismissKeyboard()
59 |
60 | signInButton.tap()
61 | }
62 |
63 | func attemptSignUp(
64 | in testCase: XCTestCase,
65 | with email: String,
66 | password: String
67 | ) {
68 | buttons["GoToSignUpLink"].tap()
69 |
70 | type(text: email, on: "EmailTextField")
71 |
72 | type(
73 | text: password,
74 | on: "PasswordTextField",
75 | isSecure: true
76 | )
77 |
78 | dismissKeyboard()
79 |
80 | type(
81 | text: password,
82 | on: "ConfirmPasswordTextField",
83 | isSecure: true
84 | )
85 |
86 | dismissKeyboard()
87 |
88 | buttons["SignUpButton"].tap()
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/swift-ui-base/Extensions/StringExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringExtension.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/10/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum ValidationType {
12 | case email
13 | case nonEmpty
14 | case numeric
15 | case date
16 | case phone
17 | case none
18 | case custom(isValid: () -> Bool)
19 | }
20 |
21 | extension String {
22 | var isAlphanumericWithNoSpaces: Bool {
23 | rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil
24 | }
25 |
26 | var hasPunctuationCharacters: Bool {
27 | rangeOfCharacter(from: CharacterSet.punctuationCharacters) != nil
28 | }
29 |
30 | var hasNumbers: Bool {
31 | rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789")) != nil
32 | }
33 |
34 | var localized: String {
35 | localize()
36 | }
37 |
38 | var withNoSpaces: String {
39 | filter { !$0.isWhitespace }
40 | }
41 |
42 | func localize(comment: String = "") -> String {
43 | NSLocalizedString(self, comment: comment)
44 | }
45 |
46 | var validFilename: String {
47 | guard !isEmpty else { return "emptyFilename" }
48 | return addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? "emptyFilename"
49 | }
50 |
51 | func isValid(type: ValidationType, isRequired: Bool = true) -> Bool {
52 | guard isRequired || !isEmpty else { return true }
53 | switch type {
54 | case .email:
55 | return isEmailFormatted()
56 | case .numeric:
57 | return isInteger()
58 | case .phone:
59 | return isPhoneNumber()
60 | case .none:
61 | return true
62 | case .custom(isValid: let validationBlock):
63 | return validationBlock()
64 | default:
65 | return !isEmpty
66 | }
67 | }
68 |
69 | //Regex fulfill RFC 5322 Internet Message format
70 | func isEmailFormatted() -> Bool {
71 | let predicate = NSPredicate(
72 | format: "SELF MATCHES %@",
73 | "[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(\\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\\.)+[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?"
74 | )
75 | return predicate.evaluate(with: self)
76 | }
77 |
78 | func isInteger() -> Bool {
79 | Int(self) != nil
80 | }
81 |
82 | func isPhoneNumber() -> Bool {
83 | let phoneTest = NSPredicate(format: "SELF MATCHES %@", "^\\d{3}-\\d{3}-\\d{4}$")
84 | return phoneTest.evaluate(with: self)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/swift-ui-base/Common/View/TextField/View/TextFieldView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextFieldView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/10/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct TextFieldView: View {
12 |
13 | @Binding var fieldData: TextFieldData
14 |
15 | var body: some View {
16 | VStack {
17 | ZStack {
18 | Text(fieldData.title)
19 | .frame(maxWidth: .infinity, alignment: .leading)
20 | .font(Font.headline.weight(.regular))
21 | .foregroundColor(.cadetBlue)
22 | .offset(CGSize(width: 0, height: fieldData.isEmpty ? 0 : -30))
23 | .opacity(fieldData.isEmpty ? 0.7 : 1)
24 | .animation(.easeOut(duration: 0.2))
25 | .scaleEffect(fieldData.isEmpty ? 1 : 0.8, anchor: .bottomLeading)
26 |
27 | if fieldData.isSecure {
28 | SecureField("", text: $fieldData.value)
29 | .foregroundColor(.darkGray)
30 | .autocapitalization(.none)
31 | .accessibility(identifier: "\(fieldData.title.withNoSpaces)TextField")
32 | } else {
33 | TextField("", text: $fieldData.value)
34 | .foregroundColor(.darkGray)
35 | .autocapitalization(.none)
36 | .opacity(0.8)
37 | .accessibility(identifier: "\(fieldData.title.withNoSpaces)TextField")
38 | }
39 | }
40 |
41 | Rectangle()
42 | .frame(maxWidth: .infinity, maxHeight: 1)
43 | .foregroundColor(
44 | !fieldData.isEmpty && !fieldData.isValid ? .errorRed : .cadetBlue
45 | )
46 | .opacity(0.5)
47 |
48 | Text(fieldData.errorMessage)
49 | .frame(maxWidth: .infinity, alignment: .leading)
50 | .offset(CGSize(width: 0, height: -5))
51 | .font(.footnote)
52 | .foregroundColor(.errorRed)
53 | .opacity(!fieldData.isEmpty && !fieldData.isValid ? 1 : 0)
54 | .animation(.easeOut(duration: 0.2))
55 | }
56 | .padding(.horizontal, 20)
57 | }
58 | }
59 |
60 | struct TextFieldView_Previews: PreviewProvider {
61 |
62 | struct BindingTestHolder: View {
63 | @State var fieldData = TextFieldData(
64 | title: "Email",
65 | value: "testy@testerson",
66 | validationType: .email,
67 | errorMessage: "Please enter a valid email"
68 | )
69 |
70 | var body: some View {
71 | TextFieldView(fieldData: $fieldData)
72 | }
73 | }
74 |
75 | static var previews: some View {
76 | BindingTestHolder()
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/swift-ui-baseTests/Services/UserServiceUnitTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserServiceUnitTests.swift
3 | // ios-baseUnitTests
4 | //
5 | // Created by German on 4/30/20.
6 | // Copyright © 2020 TopTier labs. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import swift_ui_base
11 |
12 | class UserServiceUnitTests: XCTestCase {
13 |
14 | let userResponse: [String: Any] = [
15 | "user": [
16 | "id": 0,
17 | "username": "test",
18 | "email": "test-user@rootstrap.com"
19 | ]
20 | ]
21 |
22 | var testUser: User!
23 |
24 | override func setUp() {
25 | super.setUp()
26 | testUser = User(
27 | id: 0,
28 | username: "test",
29 | email: "test-user@rootstrap.com"
30 | )
31 | SessionManager.deleteSession()
32 | UserDataManager.deleteUser()
33 | }
34 |
35 | func testUserPersistence() {
36 | AuthenticationServices.saveUserSession(fromResponse: userResponse, headers: [:])
37 | guard let persistedUser = UserDataManager.currentUser else {
38 | XCTFail("User should NOT be nil")
39 | return
40 | }
41 | XCTAssert(UserDataManager.isUserLogged)
42 | XCTAssert(persistedUser.id == testUser.id)
43 | XCTAssert(persistedUser.username == testUser.username)
44 | XCTAssert(persistedUser.email == testUser.email)
45 | }
46 |
47 | func testGoodSessionPersistence() {
48 | let client = "dummySessionClient"
49 | let token = "dummySessionToken"
50 | let uid = testUser.email
51 | let expiry = "\(Date.timeIntervalSinceReferenceDate)"
52 | let sessionHeaders: [String: Any] = [
53 | APIClient.HTTPHeader.uid.rawValue: uid,
54 | APIClient.HTTPHeader.client.rawValue: client,
55 | APIClient.HTTPHeader.token.rawValue: token,
56 | APIClient.HTTPHeader.expiry.rawValue: expiry
57 | ]
58 |
59 | AuthenticationServices.saveUserSession(
60 | fromResponse: userResponse,
61 | headers: sessionHeaders
62 | )
63 | guard let persistedSession = SessionManager.currentSession else {
64 | XCTFail("Session should NOT be nil")
65 | return
66 | }
67 |
68 | XCTAssert(persistedSession.client == client)
69 | XCTAssert(persistedSession.accessToken == token)
70 | XCTAssert(persistedSession.uid == uid)
71 | XCTAssert(persistedSession.accessToken == token)
72 | }
73 |
74 | func testBadSessionPersistence() {
75 | // Testing case where shouldn't be session at all
76 | let unusableHeaders = [APIClient.HTTPHeader.client: "badHeaderKey"]
77 | AuthenticationServices.saveUserSession(
78 | fromResponse: userResponse,
79 | headers: unusableHeaders
80 | )
81 | XCTAssert(SessionManager.currentSession == nil)
82 | XCTAssertFalse(SessionManager.isValidSession)
83 |
84 | // Testing case where should be session but not valid
85 | let wrongSessionHeaders = [
86 | "testKey": "testValue",
87 | APIClient.HTTPHeader.uid.rawValue: "",
88 | APIClient.HTTPHeader.client.rawValue: "",
89 | APIClient.HTTPHeader.token.rawValue: ""
90 | ]
91 | AuthenticationServices.saveUserSession(
92 | fromResponse: userResponse,
93 | headers: wrongSessionHeaders
94 | )
95 | XCTAssert(SessionManager.currentSession != nil)
96 | XCTAssertFalse(SessionManager.isValidSession)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/swift-ui-base/Home/View/ProfileView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileView.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 3/24/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ProfileView: View {
12 |
13 | @State var isShowingImagePicker = false
14 | @State private var image: Image?
15 |
16 | @ObservedObject var viewModel = ProfileViewModel()
17 |
18 | var body: some View {
19 | ZStack {
20 | ActivityIndicatorView(isAnimating: $viewModel.isLoading, style: .medium)
21 |
22 | VStack {
23 |
24 | AvatarView(
25 | image: $image,
26 | isShowingImagePicker: $isShowingImagePicker
27 | ).padding(.top, 70)
28 |
29 | Text("Welcome \(viewModel.username)")
30 | .modifier(MainTitle())
31 |
32 | Spacer()
33 |
34 | buttons
35 | .padding(.bottom, 60)
36 |
37 | .sheet(isPresented: $isShowingImagePicker, onDismiss: loadImage) {
38 | ImagePicker(image: self.$viewModel.image)
39 | }
40 |
41 | .alert(isPresented: $viewModel.shouldShowAlert) {
42 | if !viewModel.errorDescription.isEmpty {
43 | return Alert(title: Text("Oops"), message: Text(viewModel.errorDescription))
44 | } else {
45 | return Alert(title: Text("Profile loaded"), message: Text(viewModel.username))
46 | }
47 | }
48 | }
49 | .disabled(viewModel.isLoading)
50 | .blur(radius: viewModel.isLoading ? 3 : 0)
51 | }
52 | }
53 |
54 | var saveAvatarButtonColor: Color {
55 | let opacity = $image.wrappedValue == nil ? 0.5 : 1
56 | return Color.green.opacity(opacity)
57 | }
58 |
59 | var buttons: some View {
60 | VStack(spacing: 10) {
61 | Button(action: saveAvatar) {
62 | Text("Save Avatar")
63 | .modifier(MainRoundedButton(backgroundColor: saveAvatarButtonColor))
64 | }
65 | .disabled($image.wrappedValue == nil)
66 | .accessibility(identifier: "SaveAvatarButton")
67 |
68 | Button(action: getMyProfile) {
69 | Text("Get my profile")
70 | .modifier(MainRoundedButton(backgroundColor: Color.blue))
71 | }
72 | .accessibility(identifier: "GetMyProfileButton")
73 |
74 | Button(action: logoutButtonTapped) {
75 | Text("Log out")
76 | .modifier(MainRoundedButton(backgroundColor: Color.red))
77 | }
78 | .accessibility(identifier: "LogOutButton")
79 |
80 | Button(action: deleteAccountButtonTapped) {
81 | Text("Delete Account")
82 | .modifier(MainRoundedButton(backgroundColor: Color.purple))
83 | }
84 | .accessibility(identifier: "DeleteAccountButton")
85 | }
86 | }
87 |
88 | func logoutButtonTapped() {
89 | viewModel.logout()
90 | ViewRouter.shared.currentRoot = .home
91 | }
92 |
93 | func deleteAccountButtonTapped() {
94 | viewModel.deleteAccount()
95 | ViewRouter.shared.currentRoot = .home
96 | }
97 |
98 | func saveAvatar() {
99 | viewModel.saveAvatar()
100 | }
101 |
102 | func getMyProfile() {
103 | viewModel.getMyProfile()
104 | }
105 |
106 | func loadImage() {
107 | guard let inputImage = viewModel.image else { return }
108 | image = Image(uiImage: inputImage)
109 | }
110 | }
111 |
112 | struct ProfileView_Previews: PreviewProvider {
113 | static var previews: some View {
114 | ProfileView()
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/swift-ui-baseUITests/swift_ui_baseUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // swift-ui-baseUITests
3 | //
4 | // Created by Germán Stábile on 2/13/20.
5 | // Copyright © 2020 TopTier labs. All rights reserved.
6 | //
7 |
8 | import XCTest
9 | @testable import swift_ui_base
10 |
11 | class ios_baseUITests: XCTestCase {
12 |
13 | var app: XCUIApplication!
14 | let networkMocker = NetworkMocker()
15 |
16 | override func setUp() {
17 | super.setUp()
18 | app = XCUIApplication()
19 | app.launchArguments = ["Automation Test"]
20 |
21 | networkMocker.setUp()
22 | }
23 |
24 | override func tearDown() {
25 | super.tearDown()
26 | networkMocker.tearDown()
27 | }
28 |
29 | func testCreateAccountValidations() {
30 | app.launch()
31 |
32 | app.buttons["GoToSignUpLink"].tap()
33 |
34 | let signUpButton = app.buttons["SignUpButton"]
35 | waitFor(element: signUpButton, timeOut: 2)
36 |
37 | XCTAssertFalse(signUpButton.isEnabled)
38 |
39 | app.type(text: "automation@test", on: "EmailTextField")
40 |
41 | app.dismissKeyboard()
42 |
43 | app.type(
44 | text: "holahola",
45 | on: "PasswordTextField",
46 | isSecure: true
47 | )
48 |
49 | app.dismissKeyboard()
50 | XCTAssertFalse(signUpButton.isEnabled)
51 |
52 | app.type(
53 | text: "holahola",
54 | on: "ConfirmPasswordTextField",
55 | isSecure: true
56 | )
57 | XCTAssertFalse(signUpButton.isEnabled)
58 |
59 | app.type(text: ".com", on: "EmailTextField")
60 | XCTAssert(signUpButton.isEnabled)
61 |
62 | app.dismissKeyboard()
63 |
64 | app.type(
65 | text: "holahol",
66 | on: "ConfirmPasswordTextField",
67 | isSecure: true
68 | )
69 | XCTAssertFalse(signUpButton.isEnabled)
70 | }
71 |
72 | func testAccountCreation() {
73 | app.launch()
74 |
75 | networkMocker.stubSignUp()
76 |
77 | app.attemptSignUp(
78 | in: self,
79 | with: "automation@test.com",
80 | password: "holahola"
81 | )
82 |
83 | let logOutButton = app.buttons["LogOutButton"]
84 |
85 | waitFor(element: logOutButton, timeOut: 15)
86 |
87 | networkMocker.stubLogOut()
88 |
89 | logOutButton.tap()
90 |
91 | networkMocker.stubLogIn()
92 |
93 | app.attemptSignIn(
94 | in: self,
95 | with: "automation@test.com",
96 | password: "holahola"
97 | )
98 |
99 | networkMocker.stubGetProfile()
100 |
101 | let getMyProfile = app.buttons["GetMyProfileButton"]
102 | waitFor(element: getMyProfile, timeOut: 10)
103 | getMyProfile.tap()
104 |
105 | sleep(10)
106 | if let alert = app.alerts.allElementsBoundByIndex.first {
107 | waitFor(element: alert, timeOut: 10)
108 |
109 | alert.buttons.allElementsBoundByIndex.first?.tap()
110 |
111 | networkMocker.stubDeleteAccount()
112 | app.deleteAccountIfNeeded(in: self)
113 | }
114 | }
115 |
116 | func testSignInFailure() {
117 | app.launch()
118 |
119 | networkMocker.stubLogIn(shouldSucceed: false)
120 | app.attemptSignIn(
121 | in: self,
122 | with: "automation@test.com",
123 | password: "incorrect password"
124 | )
125 |
126 | if let alert = app.alerts.allElementsBoundByIndex.first {
127 | waitFor(element: alert, timeOut: 2)
128 |
129 | alert.buttons.allElementsBoundByIndex.first?.tap()
130 | }
131 |
132 | let signInButton = app.buttons["SignInButton"]
133 | waitFor(element: signInButton, timeOut: 2)
134 | }
135 |
136 | func testSignInValidations() {
137 | app.launch()
138 |
139 | app.buttons["GoToLoginLink"].tap()
140 |
141 | let signInButton = app.buttons["SignInButton"]
142 |
143 | waitFor(element: signInButton, timeOut: 2)
144 |
145 | XCTAssertFalse(signInButton.isEnabled)
146 |
147 | app.type(text: "automation@test", on: "EmailTextField")
148 | app.type(
149 | text: "holahola",
150 | on: "PasswordTextField",
151 | isSecure: true
152 | )
153 |
154 | XCTAssertFalse(signInButton.isEnabled)
155 |
156 | app.type(text: ".com", on: "EmailTextField")
157 |
158 | XCTAssert(signInButton.isEnabled)
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/swift-ui-base/Networking/Service/AuthenticationServices.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseService.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/1/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | class AuthenticationServices {
13 |
14 | fileprivate static let usersUrl = "/users/"
15 | fileprivate static let currentUserUrl = "/user/"
16 |
17 | class func login(_ email: String,
18 | password: String,
19 | success: @escaping () -> Void,
20 | failure: @escaping (_ error: Error) -> Void) {
21 | let url = usersUrl + "sign_in"
22 | let parameters = [
23 | "user": [
24 | "email": email,
25 | "password": password
26 | ]
27 | ]
28 | APIClient.request(.post, url: url, params: parameters, success: { response, headers in
29 | AuthenticationServices.saveUserSession(fromResponse: response, headers: headers)
30 | success()
31 | }, failure: { error in
32 | failure(error)
33 | })
34 | }
35 |
36 | //Multi part upload example
37 | //TODO: rails base backend not supporting multipart uploads yet
38 | class func signup(_ email: String,
39 | password: String,
40 | avatar: UIImage,
41 | success: @escaping (_ user: User?) -> Void,
42 | failure: @escaping (_ error: Error) -> Void) {
43 | let parameters = [
44 | "user": [
45 | "email": email,
46 | "password": password,
47 | "password_confirmation": password
48 | ]
49 | ]
50 |
51 | guard let picData = avatar.jpegData(compressionQuality: 0.75) else {
52 | failure(App.error(
53 | domain: .parsing,
54 | code: 1000,
55 | localizedDescription: "Could not parse image"
56 | ))
57 | return
58 | }
59 | let image = MultipartMedia(key: "user[avatar]", data: picData)
60 | //Mixed base64 encoded and multipart images are supported in [MultipartMedia] param:
61 | //Example: let image2 = Base64Media(key: "user[image]", data: picData) Then: media [image, image2]
62 | APIClient.multipartRequest(
63 | url: usersUrl,
64 | params: parameters,
65 | paramsRootKey: "",
66 | media: [image],
67 | success: { response, headers in
68 | AuthenticationServices.saveUserSession(fromResponse: response, headers: headers)
69 | success(UserDataManager.currentUser)
70 | },
71 | failure: failure
72 | )
73 | }
74 |
75 | //Example method that uploads base64 encoded image.
76 | class func signup(_ email: String,
77 | password: String,
78 | avatar64: UIImage,
79 | success: @escaping (_ user: User?) -> Void,
80 | failure: @escaping (_ error: Error) -> Void) {
81 | var userParameters: [String: Any] = [
82 | "email": email,
83 | "password": password,
84 | "password_confirmation": password
85 | ]
86 |
87 | if let picData = avatar64.jpegData(compressionQuality: 0.75) {
88 | userParameters["image"] = picData.asBase64Param()
89 | }
90 |
91 | let parameters = [
92 | "user": userParameters
93 | ]
94 |
95 | APIClient.request(
96 | .post,
97 | url: usersUrl,
98 | params: parameters,
99 | success: { response, headers in
100 | AuthenticationServices.saveUserSession(fromResponse: response, headers: headers)
101 | success(UserDataManager.currentUser)
102 | },
103 | failure: failure
104 | )
105 | }
106 |
107 | class func logout(
108 | success: @escaping () -> Void = {},
109 | failure: @escaping (_ error: Error) -> Void = { _ in }
110 | ) {
111 | let url = "\(usersUrl)sign_out"
112 | APIClient.request(
113 | .delete,
114 | url: url,
115 | success: { _, _ in
116 | deleteSession()
117 | success()
118 | },
119 | failure: failure
120 | )
121 | }
122 |
123 | class func deleteAccount(
124 | success: @escaping () -> Void = {},
125 | failure: @escaping (_ error: Error) -> Void = { _ in }
126 | ) {
127 | let url = "\(currentUserUrl)delete_account"
128 | APIClient.request(
129 | .delete,
130 | url: url,
131 | success: { _, _ in
132 | deleteSession()
133 | success()
134 | },
135 | failure: failure
136 | )
137 | }
138 |
139 | class func deleteSession() {
140 | UserDataManager.deleteUser()
141 | SessionManager.deleteSession()
142 | }
143 |
144 | class func saveUserSession(
145 | fromResponse response: [String: Any],
146 | headers: [AnyHashable: Any]
147 | ) {
148 | UserDataManager.currentUser = User(
149 | dictionary: response["user"] as? [String: Any] ?? [:]
150 | )
151 | if let headers = headers as? [String: Any] {
152 | SessionManager.currentSession = Session(headers: headers)
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/init.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | let baseProjectName = "swift-ui-base"
4 | var projectName = "RSDemoProject"
5 | let baseDomain = "com.rootstrap"
6 | var bundleDomain = baseDomain
7 | let baseCompany = "Rootstrap Inc."
8 | var companyName = baseCompany
9 |
10 | let whiteList: [String] = [".DS_Store", "UserInterfaceState.xcuserstate", "init.swift", "init"]
11 | let fileManager = FileManager.default
12 | var currentFolder: String {
13 | return fileManager.currentDirectoryPath
14 | }
15 |
16 | enum SetupStep: Int {
17 | case nameEntry = 1
18 | case bundleDomainEntry
19 | case companyNameEntry
20 |
21 | var question: String {
22 | switch self {
23 | case .nameEntry: return "Enter a name for the project"
24 | case .bundleDomainEntry: return "Enter the reversed domain of your organization"
25 | case .companyNameEntry: return "Enter the Company name to use on file's headers"
26 | }
27 | }
28 | }
29 |
30 | // Helper methods
31 | func prompt(message: String) -> String? {
32 | print("\n" + message)
33 | let answer = readLine()
34 | return answer == nil || answer == "" ? nil : answer!
35 | }
36 |
37 | func setup(step: SetupStep, defaultValue: String) -> String {
38 | let result = prompt(message: "\(step.rawValue). " + step.question + " (leave blank for \(defaultValue)).")
39 | guard let res = result else {
40 | print(defaultValue)
41 | return defaultValue
42 | }
43 | return res
44 | }
45 |
46 | func shell(_ args: String...) -> (output: String, exitCode: Int32) {
47 | let task = Process()
48 | task.launchPath = "/usr/bin/env"
49 | task.arguments = args
50 | task.currentDirectoryPath = currentFolder
51 | let pipe = Pipe()
52 | task.standardOutput = pipe
53 | task.launch()
54 | task.waitUntilExit()
55 | let data = pipe.fileHandleForReading.readDataToEndOfFile()
56 | let output = String(data: data, encoding: .utf8) ?? ""
57 | return (output, task.terminationStatus)
58 | }
59 |
60 | extension URL {
61 | var fileName: String {
62 | let urlValues = try? resourceValues(forKeys: [.nameKey])
63 | return urlValues?.name ?? ""
64 | }
65 |
66 | var isDirectory: Bool {
67 | let urlValues = try? resourceValues(forKeys: [.isDirectoryKey])
68 | return urlValues?.isDirectory ?? false
69 | }
70 |
71 | func rename(from oldName: String, to newName: String) {
72 | if fileName.contains(oldName) {
73 | let newName = fileName.replacingOccurrences(of: oldName, with: newName)
74 | try! fileManager.moveItem(at: self, to: URL(fileURLWithPath: newName, relativeTo: deletingLastPathComponent()))
75 | }
76 | }
77 |
78 | func replaceOccurrences(of value: String, with newValue: String) {
79 | guard let fileContent = try? String(contentsOfFile: path, encoding: .utf8) else {
80 | print("Unable to read file at: \(self)")
81 | return
82 | }
83 | let updatedContent = fileContent.replacingOccurrences(of: value, with: newValue)
84 | try! updatedContent.write(to: self, atomically: true, encoding: .utf8)
85 | }
86 |
87 | func setupForNewProject() {
88 | replaceOccurrences(of: baseProjectName, with: projectName)
89 | replaceOccurrences(of: baseDomain, with: bundleDomain)
90 | rename(from: baseProjectName, to: projectName)
91 | }
92 | }
93 |
94 | // Helper functions
95 | func changeOrganizationName() {
96 | let pbxProjectPath = "\(currentFolder)/\(baseProjectName).xcodeproj/project.pbxproj"
97 | guard
98 | fileManager.fileExists(atPath: pbxProjectPath),
99 | companyName != baseCompany
100 | else { return }
101 |
102 | print("\nUpdating company name to '\(companyName)'...")
103 |
104 | let filterKey = "ORGANIZATIONNAME"
105 | let organizationNameFilter = "\(filterKey) = \"\(baseCompany)\""
106 | let organizationNameReplacement = "\(filterKey) = \"\(companyName)\""
107 | let fileUrl = URL(fileURLWithPath: pbxProjectPath)
108 | fileUrl.replaceOccurrences(of: organizationNameFilter, with: organizationNameReplacement)
109 | }
110 |
111 | // Project Initialization
112 | print("""
113 | +-----------------------------------------+
114 | | |
115 | | < New iOS Project Setup > |
116 | | |
117 | +-----------------------------------------+
118 | """)
119 |
120 | projectName = setup(step: .nameEntry, defaultValue: projectName)
121 | bundleDomain = setup(step: .bundleDomainEntry, defaultValue: baseDomain)
122 | companyName = setup(step: .companyNameEntry, defaultValue: baseCompany)
123 |
124 | //Remove current git tracking
125 | _ = shell("rm", "-rf", ".git")
126 |
127 | changeOrganizationName()
128 |
129 | print("\nRenaming to '\(projectName)'...")
130 | let enumerator = fileManager.enumerator(at: URL(fileURLWithPath: currentFolder), includingPropertiesForKeys: [.nameKey, .isDirectoryKey])!
131 | var directories: [URL] = []
132 | while let itemURL = enumerator.nextObject() as? URL {
133 | guard !whiteList.contains(itemURL.fileName) else { continue }
134 | if itemURL.isDirectory {
135 | directories.append(itemURL)
136 | } else {
137 | itemURL.setupForNewProject()
138 | }
139 | }
140 |
141 | for dir in directories.reversed() {
142 | dir.rename(from: baseProjectName, to: projectName)
143 | }
144 | //TODO: Rename current dir
145 | let currentURL = URL(fileURLWithPath: currentFolder)
146 | currentURL.rename(from: baseProjectName, to: projectName)
147 |
148 | print("Installing pods...")
149 | _ = shell("pod", "install")
150 | print("Opening new project...")
151 | _ = shell("open", "\(projectName).xcworkspace")
152 | // Initialization Done!
153 | print("************** ALL SET! *******************")
154 |
--------------------------------------------------------------------------------
/swift-ui-base.xcodeproj/xcshareddata/xcschemes/swift-ui-base.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
11 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
51 |
57 |
58 |
59 |
60 |
61 |
67 |
68 |
70 |
76 |
77 |
78 |
80 |
86 |
87 |
88 |
89 |
90 |
100 |
102 |
108 |
109 |
110 |
111 |
117 |
119 |
125 |
126 |
127 |
128 |
130 |
131 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://codeclimate.com/repos/5eda90fd7beea953360073d0/maintainability)
2 | [](https://codeclimate.com/repos/5eda90fd7beea953360073d0/test_coverage)
3 | [](https://github.com/rootstrap/swift-ui-base/blob/master/LICENSE.md)
4 |
5 | # SwiftUI Base Template
6 | **SwiftUI base** is a boilerplate project created by Rootstrap for new projects using SwiftUI. The main objective is helping any new projects jump start into feature development by providing a handful of functionalities and helpers.
7 |
8 | ## Features
9 | This template comes with:
10 | #### Main
11 | - Complete **API service** class to easily communicate with **REST services**.
12 | - Examples for **account creation** and **Login**.
13 | - Useful classes to **manage User and Session data**.
14 | - **Secure** way to store keys of your **third party integrations**.
15 | - Generic implementation of **navigation** between views.
16 | - Handy **helpers** and **extensions** to make your coding experience faster and easier.
17 |
18 | ## How to use
19 | 1. Clone repo.
20 | 2. Run `./init` from the recently created folder.
21 | 3. Initialize a new git repo and add your remote url.
22 | 4. Done!
23 |
24 | To manage user and session persistence after the original sign in/up we store that information in the native UserDefaults. The parameters that we save are due to the usage of [Devise Token Auth](https://github.com/lynndylanhurley/devise_token_auth) for authentication on the server side. Suffice to say that this can be modified to be on par with the server authentication of your choice.
25 |
26 | ## Pods
27 |
28 | - [Alamofire](https://github.com/Alamofire/Alamofire) for easy and elegant connection with an API.
29 |
30 | ## Optional configuration
31 |
32 | ## Code Quality Standards
33 | In order to meet the required code quality standards, this project runs [SwiftLint](https://github.com/realm/SwiftLint )
34 | during the build phase and reports warnings/errors directly through XCode.
35 | The current SwiftLint rule configuration is based on [Rootstrap's Swift style guides](https://rootstrap.github.io/swift) and is synced with
36 | the CodeCliemate's configuration file.
37 |
38 | **NOTE:** Make sure you have SwiftLint version 0.35.0 or greater installed to avoid known false-positives with some of the rules.
39 |
40 | ## Security recommendations
41 | #### Third Party Keys
42 |
43 | We strongly recommend that all private keys be added to a `.plist` file that will remain locally and not be committed to your project repo. An example file is already provided, these are the final steps to set it up:
44 |
45 | 1. Rename the `ThirdPartyKeys.example.plist` file on your project so that it is called `ThirdPartyKeys.plist`.
46 | To add a set of keys simply add a dictionary with the name you want the key to have and add the corresponding **Debug**, **Staging** and **Release** keys as items.
47 | 2. Remove the reference of `ThirdPartyKeys.plist` from XCode but do not delete the file. This way, you will keep the file locally(it is already in the .gitignore list) in the project directory.
48 | **Note: Do NOT move the file from the current location, the script uses the $(PROJECT_DIR) directory.**
49 | 3. Go to **Product** -> **Scheme** -> **Edit scheme**. Then select **Pre-actions** for the Build stage and make sure that the `Provided build setting` is set to your current target.
50 | **Repeat this step for the Post-actions script.**
51 | 4. Done :)
52 |
53 |
54 | ## Automated Build and Deployment using Fastlane //TODO!
55 |
56 | We use [Fastlane](https://docs.fastlane.tools) to automate code signing, building and deployment.
57 |
58 |
59 | ### New Project Setup
60 |
61 | Several steps need to be executed before building a project for the first time.
62 |
63 | 1. Install latest Xcode command line tools
64 | ```
65 | xcode-select --install
66 | ```
67 |
68 | 2. Install latest Fastlane
69 | ```
70 | # Using RubyGems
71 | sudo gem install fastlane -NV
72 | # Alternatively using Homebrew
73 | brew install fastlane
74 | ```
75 |
76 | 3. Create required App Ids in the Apple Developer Portal and App Store Connect
77 | This can be done on the Portal itself or using [fastlane produce](https://docs.fastlane.tools/actions/produce/)
78 | ```
79 | fastlane produce -u {apple_id} --app-name {app_name} --team-id {team_id} --app-identifier {app_id}
80 | ```
81 | 4. Create private empty repository to store signing certificates, eg. `git@github.com:rootstrap/{app_name}-certificates.git`
82 |
83 | 5. Generate Matchfile with [fastlane match](https://docs.fastlane.tools/actions/match/)
84 | ```
85 | fastlane match init
86 | ```
87 | select `git` as storage mode and specify the URL of the certificates git repo
88 |
89 | 6. **optional** If the Developer account has old/invalid certificates which are not shared, it is recommended to use the `nuke` action to clear the existing certificates (*use with caution*):
90 | ```
91 | fastlane match nuke development
92 | fastlane match nuke distribution
93 | ```
94 |
95 | 7. **optional** If wanting to reuse existing distribution certificates, these can be imported into the certificates repository using match with the `import` action (fastlane will prompt for location of the `.cer` and `.p12` files):
96 | ```
97 | fastlane match import \
98 | --username {{username}} \
99 | --git_url {{certificates_git_url}} \
100 | --team_id {{team_id}} \
101 | --type appstore \
102 | --app_identifier com.{{company}}.{{app_name}} \
103 | ```
104 |
105 | 8. Check the `fastlane/Appfile` and `fastlane/Fastfile`; set and/or validate the following values before use:
106 | - `app_name` # this will match with application name in App Store and target schemes in the project
107 | - `username` # The apple id used to manage the certificates
108 | - `certificates_git_url` # The repo to store and sync certs and provisioning profiles
109 | - `team_id` # The organization's team id
110 | - `itc_team_id` # App Store Connect team id
111 | - `devices` # List of devices to launch simulators for running tests on
112 |
113 |
114 | ### Fastlane usage
115 | Lanes for each deployment target are provided with some basic behavior, which can be modified as needed:
116 |
117 | - Each target has two options: `build_x` and `release_x`.
118 | - The `build` lane will build the application signed with an **Ad Hoc** certificate and keep the `.ipa` in the local folder for upload.
119 | - The `release` lane will:
120 | - Check the repo status (it has to be clean, with no pending changes)
121 | - Increment the build number.
122 | - Tag the new release and push it to the set branch (dev and staging push to develop and production to master by default, but it's configurable).
123 | - Build the app signed with an **App Store** certificate
124 | - Generate a changelog from the commit diff between this new version and the previous.
125 | - Upload to testflight and wait until it's processed.
126 |
127 | - Additionally, lane `test_develop` can be used by the CI job to only run the unit test against simulators (specified by `devices` variable in Fastfile)
128 |
129 |
130 | ## License
131 |
132 | SwiftUI base is available under the MIT license. See the LICENSE file for more info.
133 |
134 | **NOTE:** Remove the free LICENSE file for private projects or replace it with the corresponding license.
135 |
136 | ## Credits
137 |
138 | **SwiftUI Base** is maintained by [Rootstrap](http://www.rootstrap.com) with the help of our [contributors](https://github.com/rootstrap/swift-ui-base/graphs/contributors).
139 |
140 | [
](http://www.rootstrap.com)
141 |
--------------------------------------------------------------------------------
/swift-ui-base/Networking/Service/APIClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIClient.swift
3 | // swift-ui-base
4 | //
5 | // Created by Germán Stábile on 4/1/20.
6 | // Copyright © 2020 Rootstrap. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 |
12 | public enum SwiftBaseErrorCode: Int {
13 | case inputStreamReadFailed = -6000
14 | case outputStreamWriteFailed = -6001
15 | case contentTypeValidationFailed = -6002
16 | case statusCodeValidationFailed = -6003
17 | case dataSerializationFailed = -6004
18 | case stringSerializationFailed = -6005
19 | case jsonSerializationFailed = -6006
20 | case propertyListSerializationFailed = -6007
21 | }
22 |
23 | class BaseURLConvertible: URLConvertible {
24 |
25 | let path: String
26 | let baseUrl: String
27 |
28 | init(path: String, baseUrl: String = APIClient.getBaseUrl()) {
29 | self.path = path
30 | self.baseUrl = baseUrl
31 | }
32 |
33 | func asURL() throws -> URL {
34 | return URL(string: "\(baseUrl)\(path)")!
35 | }
36 | }
37 |
38 | class BaseURLRequestConvertible: URLRequestConvertible {
39 | let url: URLConvertible
40 | let method: HTTPMethod
41 | let headers: HTTPHeaders
42 | let params: [String: Any]?
43 | let encoding: ParameterEncoding?
44 |
45 | func asURLRequest() throws -> URLRequest {
46 | let request = try URLRequest(url: url,
47 | method: method,
48 | headers: headers)
49 | if let params = params, let encoding = encoding {
50 | return try encoding.encode(request, with: params)
51 | }
52 |
53 | return request
54 | }
55 |
56 | init(path: String,
57 | baseUrl: String = APIClient.getBaseUrl(),
58 | method: HTTPMethod,
59 | encoding: ParameterEncoding? = nil,
60 | params: [String: Any]? = nil,
61 | headers: [String: String] = [:]) {
62 | url = BaseURLConvertible(path: path, baseUrl: baseUrl)
63 | self.method = method
64 | self.headers = HTTPHeaders(headers)
65 | self.params = params
66 | self.encoding = encoding
67 | }
68 | }
69 |
70 | public typealias SuccessCallback = (
71 | _ responseObject: [String: Any],
72 | _ responseHeaders: [AnyHashable: Any]
73 | ) -> Void
74 | public typealias FailureCallback = (_ error: Error) -> Void
75 |
76 | class APIClient {
77 |
78 | enum HTTPHeader: String {
79 | case uid = "uid"
80 | case client = "client"
81 | case token = "access-token"
82 | case expiry = "expiry"
83 | case accept = "Accept"
84 | case contentType = "Content-Type"
85 | }
86 |
87 | private static let emptyDataStatusCodes: Set = [204, 205]
88 |
89 | //Mandatory headers for Rails 5 API
90 | static let baseHeaders: [String: String] = [
91 | HTTPHeader.accept.rawValue: "application/json",
92 | HTTPHeader.contentType.rawValue: "application/json"
93 | ]
94 |
95 | fileprivate class func getHeaders() -> [String: String] {
96 | if let session = SessionManager.currentSession {
97 | return baseHeaders + [
98 | HTTPHeader.uid.rawValue: session.uid ?? "",
99 | HTTPHeader.client.rawValue: session.client ?? "",
100 | HTTPHeader.token.rawValue: session.accessToken ?? ""
101 | ]
102 | }
103 | return baseHeaders
104 | }
105 |
106 | fileprivate class func getBaseUrl() -> String {
107 | return Bundle.main.object(forInfoDictionaryKey: "Base URL") as? String ?? ""
108 | }
109 |
110 | //Recursively build multipart params to send along with media in upload requests.
111 | //If params includes the desired root key, call this method with an empty String for rootKey param.
112 | class func multipartFormData(
113 | _ multipartForm: MultipartFormData,
114 | params: Any, rootKey: String
115 | ) {
116 | switch params.self {
117 | case let array as [Any]:
118 | for val in array {
119 | let forwardRootKey = rootKey.isEmpty ? "array[]" : rootKey + "[]"
120 | multipartFormData(multipartForm, params: val, rootKey: forwardRootKey)
121 | }
122 | case let dict as [String: Any]:
123 | for (k, v) in dict {
124 | let forwardRootKey = rootKey.isEmpty ? k : rootKey + "[\(k)]"
125 | multipartFormData(multipartForm, params: v, rootKey: forwardRootKey)
126 | }
127 | default:
128 | if let uploadData = "\(params)".data(
129 | using: String.Encoding.utf8, allowLossyConversion: false
130 | ) {
131 | let forwardRootKey = rootKey.isEmpty ?
132 | "\(type(of: params))".lowercased() : rootKey
133 | multipartForm.append(uploadData, withName: forwardRootKey)
134 | }
135 | }
136 | }
137 |
138 | //Multipart-form base request. Used to upload media along with desired params.
139 | //Note: Multipart request does not support Content-Type = application/json.
140 | //If your API requires this header do not use this method or change backend to skip this validation.
141 | class func multipartRequest(_ method: HTTPMethod = .post,
142 | url: String,
143 | headers: [String: String] = APIClient.getHeaders(),
144 | params: [String: Any]?,
145 | paramsRootKey: String,
146 | media: [MultipartMedia],
147 | success: @escaping SuccessCallback,
148 | failure: @escaping FailureCallback) {
149 |
150 | let requestConvertible = BaseURLRequestConvertible(
151 | path: url,
152 | method: method,
153 | headers: headers
154 | )
155 |
156 | AF.upload(
157 | multipartFormData: { (multipartForm) -> Void in
158 | if let parameters = params {
159 | multipartFormData(multipartForm, params: parameters, rootKey: paramsRootKey)
160 | }
161 | for elem in media {
162 | elem.embed(inForm: multipartForm)
163 | }
164 | },
165 | with: requestConvertible)
166 | .responseJSON(completionHandler: { result in
167 | validateResult(result: result, success: success, failure: failure)
168 | })
169 | }
170 |
171 | class func defaultEncoding(forMethod method: HTTPMethod) -> ParameterEncoding {
172 | switch method {
173 | case .post, .put, .patch:
174 | return JSONEncoding.default
175 | default:
176 | return URLEncoding.default
177 | }
178 | }
179 |
180 | class func request(_ method: HTTPMethod,
181 | url: String,
182 | params: [String: Any]? = nil,
183 | paramsEncoding: ParameterEncoding? = nil,
184 | success: @escaping SuccessCallback,
185 | failure: @escaping FailureCallback) {
186 | let encoding = paramsEncoding ?? defaultEncoding(forMethod: method)
187 | let headers = APIClient.getHeaders()
188 | let requestConvertible = BaseURLRequestConvertible(
189 | path: url,
190 | method: method,
191 | encoding: encoding,
192 | params: params,
193 | headers: headers
194 | )
195 |
196 | let request = AF.request(requestConvertible)
197 |
198 | request.responseJSON(
199 | completionHandler: { result in
200 | validateResult(result: result, success: success, failure: failure)
201 | })
202 | }
203 |
204 | //Handle rails-API-base errors if any
205 | class func handleCustomError(_ code: Int?, dictionary: [String: Any]) -> NSError? {
206 | if let messageDict = dictionary["errors"] as? [String: [String]] {
207 | let errorsList = messageDict[messageDict.keys.first!]!
208 | return NSError(
209 | domain: "\(messageDict.keys.first!) \(errorsList.first!)",
210 | code: code ?? 500, userInfo: nil
211 | )
212 | } else if let error = dictionary["error"] as? String {
213 | return NSError(domain: error, code: code ?? 500, userInfo: nil)
214 | } else if let errors = dictionary["errors"] as? [String: Any] {
215 | let errorDesc = errors[errors.keys.first!]!
216 | return NSError(
217 | domain: "\(errors.keys.first!) " + "\(errorDesc)",
218 | code: code ?? 500, userInfo: nil
219 | )
220 | } else if dictionary["errors"] != nil || dictionary["error"] != nil {
221 | return NSError(
222 | domain: "Something went wrong. Try again later.",
223 | code: code ?? 500, userInfo: nil
224 | )
225 | }
226 | return nil
227 | }
228 |
229 | fileprivate class func validateResult(result: AFDataResponse,
230 | success: @escaping SuccessCallback,
231 | failure: @escaping FailureCallback) {
232 | let defaultError = App.error(
233 | domain: .generic,
234 | localizedDescription: "Error parsing response".localized
235 | )
236 | guard let response = result.response else {
237 | failure(defaultError)
238 | return
239 | }
240 |
241 | guard
242 | let data = result.data,
243 | !data.isEmpty
244 | else {
245 | let emptyResponseAllowed = emptyDataStatusCodes.contains(
246 | result.response?.statusCode ?? 0
247 | )
248 | emptyResponseAllowed ?
249 | success([:], response.allHeaderFields) : failure(defaultError)
250 | return
251 | }
252 |
253 | var dictionary: [String: Any]?
254 | var serializationError: NSError?
255 | do {
256 | dictionary = try JSONSerialization.jsonObject(
257 | with: data, options: .allowFragments
258 | ) as? [String: Any]
259 | } catch let exceptionError as NSError {
260 | serializationError = exceptionError
261 | }
262 | //Check for errors in validate() or API
263 | if let errorOcurred = APIClient.handleCustomError(
264 | response.statusCode, dictionary: dictionary ?? [:]
265 | ) ?? result.error as NSError? {
266 | failure(errorOcurred)
267 | return
268 | }
269 | //Check for JSON serialization errors if any data received
270 | if let serializationError = serializationError {
271 | if (serializationError as NSError).code == 401 {
272 | AppDelegate.shared.unexpectedLogout()
273 | }
274 | failure(serializationError)
275 | } else {
276 | success(dictionary ?? [:], response.allHeaderFields)
277 | }
278 | }
279 | }
280 |
281 | //Helper to retrieve the right string value for base64 API uploaders
282 | extension Data {
283 | func asBase64Param(withType type: MimeType = .jpeg) -> String {
284 | return "data:\(type.rawValue);base64,\(self.base64EncodedString())"
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/swift-ui-base.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 071D6B682419576500571DE9 /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071D6B672419576500571DE9 /* ColorExtension.swift */; };
11 | 071D6B6C241976C200571DE9 /* TitleModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 071D6B6B241976C200571DE9 /* TitleModifier.swift */; };
12 | 073CF2A324AB9CD300885A4D /* NetworkMocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 073CF2A224AB9CD300885A4D /* NetworkMocker.swift */; };
13 | 073CF2A624AB9FA000885A4D /* SignUpSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 073CF2A524AB9FA000885A4D /* SignUpSuccessfully.json */; };
14 | 073CF2A824ABA11D00885A4D /* AuthenticationError.json in Resources */ = {isa = PBXBuildFile; fileRef = 073CF2A724ABA11D00885A4D /* AuthenticationError.json */; };
15 | 073CF2AC24ABA62C00885A4D /* LogOutSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 073CF2AB24ABA62C00885A4D /* LogOutSuccessfully.json */; };
16 | 073CF2AE24ABA6B800885A4D /* LoginSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 073CF2AD24ABA6B800885A4D /* LoginSuccessfully.json */; };
17 | 073CF2B424ABD09300885A4D /* NetworkMockerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 073CF2B324ABD09300885A4D /* NetworkMockerExtension.swift */; };
18 | 073CF2B624ABD33900885A4D /* GetProfileSuccessfully.json in Resources */ = {isa = PBXBuildFile; fileRef = 073CF2B524ABD33900885A4D /* GetProfileSuccessfully.json */; };
19 | 07415FE524181D8800486885 /* SignUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07415FE424181D8800486885 /* SignUpView.swift */; };
20 | 07415FE9241832E700486885 /* TextFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07415FE8241832E700486885 /* TextFieldView.swift */; };
21 | 07415FEC24183A7E00486885 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07415FEB24183A7E00486885 /* StringExtension.swift */; };
22 | 07442D0B241C08E700A235E7 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07442D0A241C08E700A235E7 /* ActivityIndicatorView.swift */; };
23 | 07442D11241C186100A235E7 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07442D10241C186100A235E7 /* RootView.swift */; };
24 | 07442D13241C189300A235E7 /* ViewRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07442D12241C189300A235E7 /* ViewRouter.swift */; };
25 | 0748BD9C249BE7F900D5DAF9 /* UserServiceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748BD9B249BE7F900D5DAF9 /* UserServiceUnitTests.swift */; };
26 | 0748BD9F249BE87C00D5DAF9 /* StringExtensionUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748BD9E249BE87C00D5DAF9 /* StringExtensionUnitTests.swift */; };
27 | 0748BDA1249BEBA400D5DAF9 /* XCUIApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748BDA0249BEBA300D5DAF9 /* XCUIApplicationExtension.swift */; };
28 | 0748BDA3249BEBE500D5DAF9 /* XCUIElementExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748BDA2249BEBE500D5DAF9 /* XCUIElementExtension.swift */; };
29 | 0748BDA5249BEC1F00D5DAF9 /* XCTestCaseExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0748BDA4249BEC1F00D5DAF9 /* XCTestCaseExtension.swift */; };
30 | 0749E1B0242A743300340F8B /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0749E1AF242A743300340F8B /* ProfileView.swift */; };
31 | 074AADB924192895002C6F85 /* TextFieldData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074AADB824192895002C6F85 /* TextFieldData.swift */; };
32 | 074B1440241AC6ED000CFFA0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074B143F241AC6ED000CFFA0 /* LoginViewModel.swift */; };
33 | 074F38DD245B6AA00042984E /* ProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074F38DC245B6AA00042984E /* ProfileViewModel.swift */; };
34 | 0750F701248FE225004B1D32 /* AvatarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0750F700248FE225004B1D32 /* AvatarView.swift */; };
35 | 0750F703248FEE59004B1D32 /* UserServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0750F702248FEE59004B1D32 /* UserServices.swift */; };
36 | 0750F70524900A9E004B1D32 /* RoundedButtonModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0750F70424900A9E004B1D32 /* RoundedButtonModifier.swift */; };
37 | 0768431F2434D9B40052FD64 /* AuthenticationServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0768431E2434D9B40052FD64 /* AuthenticationServices.swift */; };
38 | 076843222434DF8C0052FD64 /* Session.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076843212434DF8C0052FD64 /* Session.swift */; };
39 | 076843242434DFBC0052FD64 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076843232434DFBC0052FD64 /* DictionaryExtension.swift */; };
40 | 076843262434DFD30052FD64 /* HTTPHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076843252434DFD30052FD64 /* HTTPHeader.swift */; };
41 | 076843292434E03B0052FD64 /* SessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076843282434E03B0052FD64 /* SessionManager.swift */; };
42 | 0768432E2434E5170052FD64 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0768432D2434E5170052FD64 /* APIClient.swift */; };
43 | 076843302434E6660052FD64 /* MultipartMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0768432F2434E6660052FD64 /* MultipartMedia.swift */; };
44 | 076843322434E6B70052FD64 /* Base64Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076843312434E6B70052FD64 /* Base64Media.swift */; };
45 | 076843372434FFC70052FD64 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 076843362434FFC70052FD64 /* App.swift */; };
46 | 07684339243523740052FD64 /* UserDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07684338243523740052FD64 /* UserDataManager.swift */; };
47 | 0768433B243523AF0052FD64 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0768433A243523AF0052FD64 /* User.swift */; };
48 | 0778F5C1247436BC00EFCF10 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0778F5C0247436BC00EFCF10 /* ImagePicker.swift */; };
49 | 07849A77243783CE00321FB9 /* SignUpViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07849A76243783CE00321FB9 /* SignUpViewModel.swift */; };
50 | 07849A7924378E4B00321FB9 /* ImageExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07849A7824378E4B00321FB9 /* ImageExtension.swift */; };
51 | 079BE4DC241816F700241627 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 079BE4DB241816F700241627 /* LoginView.swift */; };
52 | 07A649FC2417E137003C6753 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A649FB2417E137003C6753 /* AppDelegate.swift */; };
53 | 07A649FE2417E137003C6753 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A649FD2417E137003C6753 /* SceneDelegate.swift */; };
54 | 07A64A002417E137003C6753 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A649FF2417E137003C6753 /* HomeView.swift */; };
55 | 07A64A022417E13A003C6753 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07A64A012417E13A003C6753 /* Assets.xcassets */; };
56 | 07A64A052417E13A003C6753 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07A64A042417E13A003C6753 /* Preview Assets.xcassets */; };
57 | 07A64A082417E13A003C6753 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 07A64A062417E13A003C6753 /* LaunchScreen.storyboard */; };
58 | 07A64A1E2417E13B003C6753 /* swift_ui_baseUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07A64A1D2417E13B003C6753 /* swift_ui_baseUITests.swift */; };
59 | 6E032E0D24D347FC00952748 /* ConfigurationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E032E0C24D347FC00952748 /* ConfigurationManager.swift */; };
60 | B1CFA0536E99A1673CB5E72C /* Pods_swift_ui_base.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 600B3D530407C25A0816E505 /* Pods_swift_ui_base.framework */; };
61 | C7605DADCC1160F192626202 /* Pods_swift_ui_base_swift_ui_baseUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50BDE825033233BE4D2E9001 /* Pods_swift_ui_base_swift_ui_baseUITests.framework */; };
62 | D6B7714F4A3569620113FDA6 /* Pods_swift_ui_base_swift_ui_baseTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DD29B33DB909BD08DE4E0B34 /* Pods_swift_ui_base_swift_ui_baseTests.framework */; };
63 | /* End PBXBuildFile section */
64 |
65 | /* Begin PBXContainerItemProxy section */
66 | 07A64A0F2417E13B003C6753 /* PBXContainerItemProxy */ = {
67 | isa = PBXContainerItemProxy;
68 | containerPortal = 07A649F02417E137003C6753 /* Project object */;
69 | proxyType = 1;
70 | remoteGlobalIDString = 07A649F72417E137003C6753;
71 | remoteInfo = "swift-ui-base";
72 | };
73 | 07A64A1A2417E13B003C6753 /* PBXContainerItemProxy */ = {
74 | isa = PBXContainerItemProxy;
75 | containerPortal = 07A649F02417E137003C6753 /* Project object */;
76 | proxyType = 1;
77 | remoteGlobalIDString = 07A649F72417E137003C6753;
78 | remoteInfo = "swift-ui-base";
79 | };
80 | /* End PBXContainerItemProxy section */
81 |
82 | /* Begin PBXFileReference section */
83 | 071D6B672419576500571DE9 /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; };
84 | 071D6B6B241976C200571DE9 /* TitleModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleModifier.swift; sourceTree = ""; };
85 | 073CF2A224AB9CD300885A4D /* NetworkMocker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMocker.swift; sourceTree = ""; };
86 | 073CF2A524AB9FA000885A4D /* SignUpSuccessfully.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = SignUpSuccessfully.json; sourceTree = ""; };
87 | 073CF2A724ABA11D00885A4D /* AuthenticationError.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = AuthenticationError.json; sourceTree = ""; };
88 | 073CF2AB24ABA62C00885A4D /* LogOutSuccessfully.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LogOutSuccessfully.json; sourceTree = ""; };
89 | 073CF2AD24ABA6B800885A4D /* LoginSuccessfully.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = LoginSuccessfully.json; sourceTree = ""; };
90 | 073CF2B324ABD09300885A4D /* NetworkMockerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMockerExtension.swift; sourceTree = ""; };
91 | 073CF2B524ABD33900885A4D /* GetProfileSuccessfully.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = GetProfileSuccessfully.json; sourceTree = ""; };
92 | 07415FE424181D8800486885 /* SignUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpView.swift; sourceTree = ""; };
93 | 07415FE8241832E700486885 /* TextFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldView.swift; sourceTree = ""; };
94 | 07415FEB24183A7E00486885 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; };
95 | 07442D0A241C08E700A235E7 /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = ""; };
96 | 07442D10241C186100A235E7 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; };
97 | 07442D12241C189300A235E7 /* ViewRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewRouter.swift; sourceTree = ""; };
98 | 0748BD9B249BE7F900D5DAF9 /* UserServiceUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserServiceUnitTests.swift; sourceTree = ""; };
99 | 0748BD9E249BE87C00D5DAF9 /* StringExtensionUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensionUnitTests.swift; sourceTree = ""; };
100 | 0748BDA0249BEBA300D5DAF9 /* XCUIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIApplicationExtension.swift; sourceTree = ""; };
101 | 0748BDA2249BEBE500D5DAF9 /* XCUIElementExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCUIElementExtension.swift; sourceTree = ""; };
102 | 0748BDA4249BEC1F00D5DAF9 /* XCTestCaseExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCaseExtension.swift; sourceTree = ""; };
103 | 0749E1AF242A743300340F8B /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; };
104 | 074AADB824192895002C6F85 /* TextFieldData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldData.swift; sourceTree = ""; };
105 | 074B143F241AC6ED000CFFA0 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; };
106 | 074F38DC245B6AA00042984E /* ProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewModel.swift; sourceTree = ""; };
107 | 0750F700248FE225004B1D32 /* AvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarView.swift; sourceTree = ""; };
108 | 0750F702248FEE59004B1D32 /* UserServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserServices.swift; sourceTree = ""; };
109 | 0750F70424900A9E004B1D32 /* RoundedButtonModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedButtonModifier.swift; sourceTree = ""; };
110 | 0768431E2434D9B40052FD64 /* AuthenticationServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServices.swift; sourceTree = ""; };
111 | 076843212434DF8C0052FD64 /* Session.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Session.swift; sourceTree = ""; };
112 | 076843232434DFBC0052FD64 /* DictionaryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = ""; };
113 | 076843252434DFD30052FD64 /* HTTPHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPHeader.swift; sourceTree = ""; };
114 | 076843282434E03B0052FD64 /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; };
115 | 0768432D2434E5170052FD64 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = ""; };
116 | 0768432F2434E6660052FD64 /* MultipartMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipartMedia.swift; sourceTree = ""; };
117 | 076843312434E6B70052FD64 /* Base64Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64Media.swift; sourceTree = ""; };
118 | 076843362434FFC70052FD64 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; };
119 | 07684338243523740052FD64 /* UserDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDataManager.swift; sourceTree = ""; };
120 | 0768433A243523AF0052FD64 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; };
121 | 0778F5C0247436BC00EFCF10 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; };
122 | 07849A76243783CE00321FB9 /* SignUpViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpViewModel.swift; sourceTree = ""; };
123 | 07849A7824378E4B00321FB9 /* ImageExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageExtension.swift; sourceTree = ""; };
124 | 079BE4DB241816F700241627 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; };
125 | 07A649F82417E137003C6753 /* swift-ui-base.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "swift-ui-base.app"; sourceTree = BUILT_PRODUCTS_DIR; };
126 | 07A649FB2417E137003C6753 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
127 | 07A649FD2417E137003C6753 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
128 | 07A649FF2417E137003C6753 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; };
129 | 07A64A012417E13A003C6753 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
130 | 07A64A042417E13A003C6753 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
131 | 07A64A072417E13A003C6753 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
132 | 07A64A092417E13A003C6753 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
133 | 07A64A0E2417E13B003C6753 /* swift-ui-baseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "swift-ui-baseTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
134 | 07A64A142417E13B003C6753 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
135 | 07A64A192417E13B003C6753 /* swift-ui-baseUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "swift-ui-baseUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
136 | 07A64A1D2417E13B003C6753 /* swift_ui_baseUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = swift_ui_baseUITests.swift; sourceTree = ""; };
137 | 07A64A1F2417E13B003C6753 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
138 | 1D5A66BD2D392E3008B86AE5 /* Pods-swift-ui-base.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base.debug.xcconfig"; path = "Target Support Files/Pods-swift-ui-base/Pods-swift-ui-base.debug.xcconfig"; sourceTree = ""; };
139 | 50BDE825033233BE4D2E9001 /* Pods_swift_ui_base_swift_ui_baseUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_swift_ui_base_swift_ui_baseUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
140 | 600B3D530407C25A0816E505 /* Pods_swift_ui_base.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_swift_ui_base.framework; sourceTree = BUILT_PRODUCTS_DIR; };
141 | 6E032E0B24D347BE00952748 /* ThirdPartyKeys.example.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = ThirdPartyKeys.example.plist; sourceTree = ""; };
142 | 6E032E0C24D347FC00952748 /* ConfigurationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; };
143 | 7E03CB48557E6A834D246B24 /* Pods-swift-ui-base.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base.release.xcconfig"; path = "Target Support Files/Pods-swift-ui-base/Pods-swift-ui-base.release.xcconfig"; sourceTree = ""; };
144 | 7EC313E43672C416F121D319 /* Pods-swift-ui-base-swift-ui-baseUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base-swift-ui-baseUITests.debug.xcconfig"; path = "Target Support Files/Pods-swift-ui-base-swift-ui-baseUITests/Pods-swift-ui-base-swift-ui-baseUITests.debug.xcconfig"; sourceTree = ""; };
145 | 9A0F24E413CA6A32C483EDA7 /* Pods-swift-ui-base-swift-ui-baseTests.uitest.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base-swift-ui-baseTests.uitest.xcconfig"; path = "Target Support Files/Pods-swift-ui-base-swift-ui-baseTests/Pods-swift-ui-base-swift-ui-baseTests.uitest.xcconfig"; sourceTree = ""; };
146 | A44E706D7F79796D17992D5E /* Pods-swift-ui-base.uitest.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base.uitest.xcconfig"; path = "Target Support Files/Pods-swift-ui-base/Pods-swift-ui-base.uitest.xcconfig"; sourceTree = ""; };
147 | B06E4CC4A34CD8B26B3128AF /* Pods-swift-ui-base-swift-ui-baseTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base-swift-ui-baseTests.debug.xcconfig"; path = "Target Support Files/Pods-swift-ui-base-swift-ui-baseTests/Pods-swift-ui-base-swift-ui-baseTests.debug.xcconfig"; sourceTree = ""; };
148 | B1D57A97908D6E25EB01EAF7 /* Pods-swift-ui-base-swift-ui-baseTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base-swift-ui-baseTests.release.xcconfig"; path = "Target Support Files/Pods-swift-ui-base-swift-ui-baseTests/Pods-swift-ui-base-swift-ui-baseTests.release.xcconfig"; sourceTree = ""; };
149 | D77EFC245B3FA3AAB543A6A3 /* Pods-swift-ui-base-swift-ui-baseUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base-swift-ui-baseUITests.release.xcconfig"; path = "Target Support Files/Pods-swift-ui-base-swift-ui-baseUITests/Pods-swift-ui-base-swift-ui-baseUITests.release.xcconfig"; sourceTree = ""; };
150 | DD29B33DB909BD08DE4E0B34 /* Pods_swift_ui_base_swift_ui_baseTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_swift_ui_base_swift_ui_baseTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
151 | FFF599804C76164B16B725B4 /* Pods-swift-ui-base-swift-ui-baseUITests.uitest.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swift-ui-base-swift-ui-baseUITests.uitest.xcconfig"; path = "Target Support Files/Pods-swift-ui-base-swift-ui-baseUITests/Pods-swift-ui-base-swift-ui-baseUITests.uitest.xcconfig"; sourceTree = ""; };
152 | /* End PBXFileReference section */
153 |
154 | /* Begin PBXFrameworksBuildPhase section */
155 | 07A649F52417E137003C6753 /* Frameworks */ = {
156 | isa = PBXFrameworksBuildPhase;
157 | buildActionMask = 2147483647;
158 | files = (
159 | B1CFA0536E99A1673CB5E72C /* Pods_swift_ui_base.framework in Frameworks */,
160 | );
161 | runOnlyForDeploymentPostprocessing = 0;
162 | };
163 | 07A64A0B2417E13B003C6753 /* Frameworks */ = {
164 | isa = PBXFrameworksBuildPhase;
165 | buildActionMask = 2147483647;
166 | files = (
167 | D6B7714F4A3569620113FDA6 /* Pods_swift_ui_base_swift_ui_baseTests.framework in Frameworks */,
168 | );
169 | runOnlyForDeploymentPostprocessing = 0;
170 | };
171 | 07A64A162417E13B003C6753 /* Frameworks */ = {
172 | isa = PBXFrameworksBuildPhase;
173 | buildActionMask = 2147483647;
174 | files = (
175 | C7605DADCC1160F192626202 /* Pods_swift_ui_base_swift_ui_baseUITests.framework in Frameworks */,
176 | );
177 | runOnlyForDeploymentPostprocessing = 0;
178 | };
179 | /* End PBXFrameworksBuildPhase section */
180 |
181 | /* Begin PBXGroup section */
182 | 071D6B692419765600571DE9 /* View */ = {
183 | isa = PBXGroup;
184 | children = (
185 | 07442D09241C08D700A235E7 /* ActivityIndicator */,
186 | 07415FE7241832D000486885 /* TextField */,
187 | );
188 | path = View;
189 | sourceTree = "";
190 | };
191 | 071D6B6A2419765B00571DE9 /* Modifier */ = {
192 | isa = PBXGroup;
193 | children = (
194 | 071D6B6B241976C200571DE9 /* TitleModifier.swift */,
195 | 0750F70424900A9E004B1D32 /* RoundedButtonModifier.swift */,
196 | );
197 | path = Modifier;
198 | sourceTree = "";
199 | };
200 | 073CF2A424AB9F8200885A4D /* Resources */ = {
201 | isa = PBXGroup;
202 | children = (
203 | 073CF2A524AB9FA000885A4D /* SignUpSuccessfully.json */,
204 | 073CF2A724ABA11D00885A4D /* AuthenticationError.json */,
205 | 073CF2AB24ABA62C00885A4D /* LogOutSuccessfully.json */,
206 | 073CF2AD24ABA6B800885A4D /* LoginSuccessfully.json */,
207 | 073CF2B524ABD33900885A4D /* GetProfileSuccessfully.json */,
208 | );
209 | path = Resources;
210 | sourceTree = "";
211 | };
212 | 07415FE62418325A00486885 /* Common */ = {
213 | isa = PBXGroup;
214 | children = (
215 | 076843352434FFBD0052FD64 /* Model */,
216 | 071D6B6A2419765B00571DE9 /* Modifier */,
217 | 071D6B692419765600571DE9 /* View */,
218 | );
219 | path = Common;
220 | sourceTree = "";
221 | };
222 | 07415FE7241832D000486885 /* TextField */ = {
223 | isa = PBXGroup;
224 | children = (
225 | 074AADB72419286F002C6F85 /* View */,
226 | 074AADB62419286A002C6F85 /* Model */,
227 | );
228 | path = TextField;
229 | sourceTree = "";
230 | };
231 | 07415FEA24183A2700486885 /* Extensions */ = {
232 | isa = PBXGroup;
233 | children = (
234 | 07415FEB24183A7E00486885 /* StringExtension.swift */,
235 | 071D6B672419576500571DE9 /* ColorExtension.swift */,
236 | 076843232434DFBC0052FD64 /* DictionaryExtension.swift */,
237 | 07849A7824378E4B00321FB9 /* ImageExtension.swift */,
238 | );
239 | path = Extensions;
240 | sourceTree = "";
241 | };
242 | 07442D09241C08D700A235E7 /* ActivityIndicator */ = {
243 | isa = PBXGroup;
244 | children = (
245 | 07442D0A241C08E700A235E7 /* ActivityIndicatorView.swift */,
246 | );
247 | path = ActivityIndicator;
248 | sourceTree = "";
249 | };
250 | 07442D0D241C169F00A235E7 /* Navigation */ = {
251 | isa = PBXGroup;
252 | children = (
253 | 07442D0F241C184700A235E7 /* Model */,
254 | 07442D0E241C184100A235E7 /* View */,
255 | );
256 | path = Navigation;
257 | sourceTree = "";
258 | };
259 | 07442D0E241C184100A235E7 /* View */ = {
260 | isa = PBXGroup;
261 | children = (
262 | 07442D10241C186100A235E7 /* RootView.swift */,
263 | );
264 | path = View;
265 | sourceTree = "";
266 | };
267 | 07442D0F241C184700A235E7 /* Model */ = {
268 | isa = PBXGroup;
269 | children = (
270 | 07442D12241C189300A235E7 /* ViewRouter.swift */,
271 | );
272 | path = Model;
273 | sourceTree = "";
274 | };
275 | 0748BD9A249BE7AF00D5DAF9 /* Services */ = {
276 | isa = PBXGroup;
277 | children = (
278 | 0748BD9B249BE7F900D5DAF9 /* UserServiceUnitTests.swift */,
279 | );
280 | path = Services;
281 | sourceTree = "";
282 | };
283 | 0748BD9D249BE84F00D5DAF9 /* Extensions */ = {
284 | isa = PBXGroup;
285 | children = (
286 | 0748BD9E249BE87C00D5DAF9 /* StringExtensionUnitTests.swift */,
287 | );
288 | path = Extensions;
289 | sourceTree = "";
290 | };
291 | 074AADB62419286A002C6F85 /* Model */ = {
292 | isa = PBXGroup;
293 | children = (
294 | 074AADB824192895002C6F85 /* TextFieldData.swift */,
295 | );
296 | path = Model;
297 | sourceTree = "";
298 | };
299 | 074AADB72419286F002C6F85 /* View */ = {
300 | isa = PBXGroup;
301 | children = (
302 | 07415FE8241832E700486885 /* TextFieldView.swift */,
303 | );
304 | path = View;
305 | sourceTree = "";
306 | };
307 | 074B143E241AC6D7000CFFA0 /* ViewModel */ = {
308 | isa = PBXGroup;
309 | children = (
310 | 074B143F241AC6ED000CFFA0 /* LoginViewModel.swift */,
311 | 07849A76243783CE00321FB9 /* SignUpViewModel.swift */,
312 | );
313 | path = ViewModel;
314 | sourceTree = "";
315 | };
316 | 074F38DB245B6A8A0042984E /* ViewModel */ = {
317 | isa = PBXGroup;
318 | children = (
319 | 074F38DC245B6AA00042984E /* ProfileViewModel.swift */,
320 | );
321 | path = ViewModel;
322 | sourceTree = "";
323 | };
324 | 0768431B2434D9520052FD64 /* Networking */ = {
325 | isa = PBXGroup;
326 | children = (
327 | 0768431D2434D9960052FD64 /* Service */,
328 | 0768431C2434D98B0052FD64 /* Model */,
329 | );
330 | path = Networking;
331 | sourceTree = "";
332 | };
333 | 0768431C2434D98B0052FD64 /* Model */ = {
334 | isa = PBXGroup;
335 | children = (
336 | 076843212434DF8C0052FD64 /* Session.swift */,
337 | 076843252434DFD30052FD64 /* HTTPHeader.swift */,
338 | 0768432F2434E6660052FD64 /* MultipartMedia.swift */,
339 | 076843312434E6B70052FD64 /* Base64Media.swift */,
340 | );
341 | path = Model;
342 | sourceTree = "";
343 | };
344 | 0768431D2434D9960052FD64 /* Service */ = {
345 | isa = PBXGroup;
346 | children = (
347 | 0768431E2434D9B40052FD64 /* AuthenticationServices.swift */,
348 | 0768432D2434E5170052FD64 /* APIClient.swift */,
349 | 0750F702248FEE59004B1D32 /* UserServices.swift */,
350 | );
351 | path = Service;
352 | sourceTree = "";
353 | };
354 | 076843272434E01C0052FD64 /* Managers */ = {
355 | isa = PBXGroup;
356 | children = (
357 | 07684338243523740052FD64 /* UserDataManager.swift */,
358 | 6E032E0C24D347FC00952748 /* ConfigurationManager.swift */,
359 | 076843282434E03B0052FD64 /* SessionManager.swift */,
360 | );
361 | path = Managers;
362 | sourceTree = "";
363 | };
364 | 076843352434FFBD0052FD64 /* Model */ = {
365 | isa = PBXGroup;
366 | children = (
367 | 076843362434FFC70052FD64 /* App.swift */,
368 | 0768433A243523AF0052FD64 /* User.swift */,
369 | );
370 | path = Model;
371 | sourceTree = "";
372 | };
373 | 0778F5BE2474369F00EFCF10 /* ImagePicker */ = {
374 | isa = PBXGroup;
375 | children = (
376 | 0778F5BF247436AE00EFCF10 /* View */,
377 | );
378 | path = ImagePicker;
379 | sourceTree = "";
380 | };
381 | 0778F5BF247436AE00EFCF10 /* View */ = {
382 | isa = PBXGroup;
383 | children = (
384 | 0778F5C0247436BC00EFCF10 /* ImagePicker.swift */,
385 | );
386 | path = View;
387 | sourceTree = "";
388 | };
389 | 079BE4D7241816B100241627 /* Home */ = {
390 | isa = PBXGroup;
391 | children = (
392 | 074F38DB245B6A8A0042984E /* ViewModel */,
393 | 079BE4D8241816B700241627 /* View */,
394 | );
395 | path = Home;
396 | sourceTree = "";
397 | };
398 | 079BE4D8241816B700241627 /* View */ = {
399 | isa = PBXGroup;
400 | children = (
401 | 07A649FF2417E137003C6753 /* HomeView.swift */,
402 | 0749E1AF242A743300340F8B /* ProfileView.swift */,
403 | 0750F700248FE225004B1D32 /* AvatarView.swift */,
404 | );
405 | path = View;
406 | sourceTree = "";
407 | };
408 | 079BE4D9241816C800241627 /* Authentication */ = {
409 | isa = PBXGroup;
410 | children = (
411 | 074B143E241AC6D7000CFFA0 /* ViewModel */,
412 | 079BE4DA241816DE00241627 /* View */,
413 | );
414 | path = Authentication;
415 | sourceTree = "";
416 | };
417 | 079BE4DA241816DE00241627 /* View */ = {
418 | isa = PBXGroup;
419 | children = (
420 | 079BE4DB241816F700241627 /* LoginView.swift */,
421 | 07415FE424181D8800486885 /* SignUpView.swift */,
422 | );
423 | path = View;
424 | sourceTree = "";
425 | };
426 | 07A649EF2417E137003C6753 = {
427 | isa = PBXGroup;
428 | children = (
429 | 6E032E0B24D347BE00952748 /* ThirdPartyKeys.example.plist */,
430 | 07A649FA2417E137003C6753 /* swift-ui-base */,
431 | 07A64A112417E13B003C6753 /* swift-ui-baseTests */,
432 | 07A64A1C2417E13B003C6753 /* swift-ui-baseUITests */,
433 | 07A649F92417E137003C6753 /* Products */,
434 | 6CB79ED3831C28F2FA96610D /* Pods */,
435 | 76C04F8605B8EFC077D4B5FB /* Frameworks */,
436 | );
437 | sourceTree = "";
438 | };
439 | 07A649F92417E137003C6753 /* Products */ = {
440 | isa = PBXGroup;
441 | children = (
442 | 07A649F82417E137003C6753 /* swift-ui-base.app */,
443 | 07A64A0E2417E13B003C6753 /* swift-ui-baseTests.xctest */,
444 | 07A64A192417E13B003C6753 /* swift-ui-baseUITests.xctest */,
445 | );
446 | name = Products;
447 | sourceTree = "";
448 | };
449 | 07A649FA2417E137003C6753 /* swift-ui-base */ = {
450 | isa = PBXGroup;
451 | children = (
452 | 07A649FB2417E137003C6753 /* AppDelegate.swift */,
453 | 07A649FD2417E137003C6753 /* SceneDelegate.swift */,
454 | 07415FEA24183A2700486885 /* Extensions */,
455 | 07415FE62418325A00486885 /* Common */,
456 | 079BE4D9241816C800241627 /* Authentication */,
457 | 079BE4D7241816B100241627 /* Home */,
458 | 0768431B2434D9520052FD64 /* Networking */,
459 | 0778F5BE2474369F00EFCF10 /* ImagePicker */,
460 | 07442D0D241C169F00A235E7 /* Navigation */,
461 | 076843272434E01C0052FD64 /* Managers */,
462 | 07A64A012417E13A003C6753 /* Assets.xcassets */,
463 | 07A64A062417E13A003C6753 /* LaunchScreen.storyboard */,
464 | 07A64A092417E13A003C6753 /* Info.plist */,
465 | 07A64A032417E13A003C6753 /* Preview Content */,
466 | );
467 | path = "swift-ui-base";
468 | sourceTree = "";
469 | };
470 | 07A64A032417E13A003C6753 /* Preview Content */ = {
471 | isa = PBXGroup;
472 | children = (
473 | 07A64A042417E13A003C6753 /* Preview Assets.xcassets */,
474 | );
475 | path = "Preview Content";
476 | sourceTree = "";
477 | };
478 | 07A64A112417E13B003C6753 /* swift-ui-baseTests */ = {
479 | isa = PBXGroup;
480 | children = (
481 | 0748BD9D249BE84F00D5DAF9 /* Extensions */,
482 | 0748BD9A249BE7AF00D5DAF9 /* Services */,
483 | 07A64A142417E13B003C6753 /* Info.plist */,
484 | );
485 | path = "swift-ui-baseTests";
486 | sourceTree = "";
487 | };
488 | 07A64A1C2417E13B003C6753 /* swift-ui-baseUITests */ = {
489 | isa = PBXGroup;
490 | children = (
491 | 073CF2A424AB9F8200885A4D /* Resources */,
492 | 07A64A1D2417E13B003C6753 /* swift_ui_baseUITests.swift */,
493 | 07A64A1F2417E13B003C6753 /* Info.plist */,
494 | 0748BDA0249BEBA300D5DAF9 /* XCUIApplicationExtension.swift */,
495 | 0748BDA2249BEBE500D5DAF9 /* XCUIElementExtension.swift */,
496 | 0748BDA4249BEC1F00D5DAF9 /* XCTestCaseExtension.swift */,
497 | 073CF2A224AB9CD300885A4D /* NetworkMocker.swift */,
498 | 073CF2B324ABD09300885A4D /* NetworkMockerExtension.swift */,
499 | );
500 | path = "swift-ui-baseUITests";
501 | sourceTree = "";
502 | };
503 | 6CB79ED3831C28F2FA96610D /* Pods */ = {
504 | isa = PBXGroup;
505 | children = (
506 | 1D5A66BD2D392E3008B86AE5 /* Pods-swift-ui-base.debug.xcconfig */,
507 | A44E706D7F79796D17992D5E /* Pods-swift-ui-base.uitest.xcconfig */,
508 | 7E03CB48557E6A834D246B24 /* Pods-swift-ui-base.release.xcconfig */,
509 | B06E4CC4A34CD8B26B3128AF /* Pods-swift-ui-base-swift-ui-baseTests.debug.xcconfig */,
510 | 9A0F24E413CA6A32C483EDA7 /* Pods-swift-ui-base-swift-ui-baseTests.uitest.xcconfig */,
511 | B1D57A97908D6E25EB01EAF7 /* Pods-swift-ui-base-swift-ui-baseTests.release.xcconfig */,
512 | 7EC313E43672C416F121D319 /* Pods-swift-ui-base-swift-ui-baseUITests.debug.xcconfig */,
513 | FFF599804C76164B16B725B4 /* Pods-swift-ui-base-swift-ui-baseUITests.uitest.xcconfig */,
514 | D77EFC245B3FA3AAB543A6A3 /* Pods-swift-ui-base-swift-ui-baseUITests.release.xcconfig */,
515 | );
516 | path = Pods;
517 | sourceTree = "";
518 | };
519 | 76C04F8605B8EFC077D4B5FB /* Frameworks */ = {
520 | isa = PBXGroup;
521 | children = (
522 | 600B3D530407C25A0816E505 /* Pods_swift_ui_base.framework */,
523 | DD29B33DB909BD08DE4E0B34 /* Pods_swift_ui_base_swift_ui_baseTests.framework */,
524 | 50BDE825033233BE4D2E9001 /* Pods_swift_ui_base_swift_ui_baseUITests.framework */,
525 | );
526 | name = Frameworks;
527 | sourceTree = "";
528 | };
529 | /* End PBXGroup section */
530 |
531 | /* Begin PBXNativeTarget section */
532 | 07A649F72417E137003C6753 /* swift-ui-base */ = {
533 | isa = PBXNativeTarget;
534 | buildConfigurationList = 07A64A222417E13B003C6753 /* Build configuration list for PBXNativeTarget "swift-ui-base" */;
535 | buildPhases = (
536 | 4B075428A0F5713362B4C139 /* [CP] Check Pods Manifest.lock */,
537 | 07A649F42417E137003C6753 /* Sources */,
538 | 07A649F52417E137003C6753 /* Frameworks */,
539 | 07A649F62417E137003C6753 /* Resources */,
540 | 186470753BA1BD174DEB8FBC /* [CP] Embed Pods Frameworks */,
541 | );
542 | buildRules = (
543 | );
544 | dependencies = (
545 | );
546 | name = "swift-ui-base";
547 | productName = "swift-ui-base";
548 | productReference = 07A649F82417E137003C6753 /* swift-ui-base.app */;
549 | productType = "com.apple.product-type.application";
550 | };
551 | 07A64A0D2417E13B003C6753 /* swift-ui-baseTests */ = {
552 | isa = PBXNativeTarget;
553 | buildConfigurationList = 07A64A252417E13B003C6753 /* Build configuration list for PBXNativeTarget "swift-ui-baseTests" */;
554 | buildPhases = (
555 | 223F65BAAF9D5EE795660241 /* [CP] Check Pods Manifest.lock */,
556 | 07A64A0A2417E13B003C6753 /* Sources */,
557 | 07A64A0B2417E13B003C6753 /* Frameworks */,
558 | 07A64A0C2417E13B003C6753 /* Resources */,
559 | 3F79AD3E3E8D2FD2A6B5D10A /* [CP] Embed Pods Frameworks */,
560 | );
561 | buildRules = (
562 | );
563 | dependencies = (
564 | 07A64A102417E13B003C6753 /* PBXTargetDependency */,
565 | );
566 | name = "swift-ui-baseTests";
567 | productName = "swift-ui-baseTests";
568 | productReference = 07A64A0E2417E13B003C6753 /* swift-ui-baseTests.xctest */;
569 | productType = "com.apple.product-type.bundle.unit-test";
570 | };
571 | 07A64A182417E13B003C6753 /* swift-ui-baseUITests */ = {
572 | isa = PBXNativeTarget;
573 | buildConfigurationList = 07A64A282417E13B003C6753 /* Build configuration list for PBXNativeTarget "swift-ui-baseUITests" */;
574 | buildPhases = (
575 | 53F4300D7B90091BCE83CDCB /* [CP] Check Pods Manifest.lock */,
576 | 07A64A152417E13B003C6753 /* Sources */,
577 | 07A64A162417E13B003C6753 /* Frameworks */,
578 | 07A64A172417E13B003C6753 /* Resources */,
579 | 6DA6DE36880E1CB9D374C91C /* [CP] Embed Pods Frameworks */,
580 | );
581 | buildRules = (
582 | );
583 | dependencies = (
584 | 07A64A1B2417E13B003C6753 /* PBXTargetDependency */,
585 | );
586 | name = "swift-ui-baseUITests";
587 | productName = "swift-ui-baseUITests";
588 | productReference = 07A64A192417E13B003C6753 /* swift-ui-baseUITests.xctest */;
589 | productType = "com.apple.product-type.bundle.ui-testing";
590 | };
591 | /* End PBXNativeTarget section */
592 |
593 | /* Begin PBXProject section */
594 | 07A649F02417E137003C6753 /* Project object */ = {
595 | isa = PBXProject;
596 | attributes = {
597 | LastSwiftUpdateCheck = 1120;
598 | LastUpgradeCheck = 1120;
599 | ORGANIZATIONNAME = Rootstrap;
600 | TargetAttributes = {
601 | 07A649F72417E137003C6753 = {
602 | CreatedOnToolsVersion = 11.2.1;
603 | };
604 | 07A64A0D2417E13B003C6753 = {
605 | CreatedOnToolsVersion = 11.2.1;
606 | LastSwiftMigration = 1140;
607 | TestTargetID = 07A649F72417E137003C6753;
608 | };
609 | 07A64A182417E13B003C6753 = {
610 | CreatedOnToolsVersion = 11.2.1;
611 | TestTargetID = 07A649F72417E137003C6753;
612 | };
613 | };
614 | };
615 | buildConfigurationList = 07A649F32417E137003C6753 /* Build configuration list for PBXProject "swift-ui-base" */;
616 | compatibilityVersion = "Xcode 9.3";
617 | developmentRegion = en;
618 | hasScannedForEncodings = 0;
619 | knownRegions = (
620 | en,
621 | Base,
622 | );
623 | mainGroup = 07A649EF2417E137003C6753;
624 | productRefGroup = 07A649F92417E137003C6753 /* Products */;
625 | projectDirPath = "";
626 | projectRoot = "";
627 | targets = (
628 | 07A649F72417E137003C6753 /* swift-ui-base */,
629 | 07A64A0D2417E13B003C6753 /* swift-ui-baseTests */,
630 | 07A64A182417E13B003C6753 /* swift-ui-baseUITests */,
631 | );
632 | };
633 | /* End PBXProject section */
634 |
635 | /* Begin PBXResourcesBuildPhase section */
636 | 07A649F62417E137003C6753 /* Resources */ = {
637 | isa = PBXResourcesBuildPhase;
638 | buildActionMask = 2147483647;
639 | files = (
640 | 07A64A082417E13A003C6753 /* LaunchScreen.storyboard in Resources */,
641 | 07A64A052417E13A003C6753 /* Preview Assets.xcassets in Resources */,
642 | 07A64A022417E13A003C6753 /* Assets.xcassets in Resources */,
643 | );
644 | runOnlyForDeploymentPostprocessing = 0;
645 | };
646 | 07A64A0C2417E13B003C6753 /* Resources */ = {
647 | isa = PBXResourcesBuildPhase;
648 | buildActionMask = 2147483647;
649 | files = (
650 | );
651 | runOnlyForDeploymentPostprocessing = 0;
652 | };
653 | 07A64A172417E13B003C6753 /* Resources */ = {
654 | isa = PBXResourcesBuildPhase;
655 | buildActionMask = 2147483647;
656 | files = (
657 | 073CF2B624ABD33900885A4D /* GetProfileSuccessfully.json in Resources */,
658 | 073CF2AE24ABA6B800885A4D /* LoginSuccessfully.json in Resources */,
659 | 073CF2AC24ABA62C00885A4D /* LogOutSuccessfully.json in Resources */,
660 | 073CF2A824ABA11D00885A4D /* AuthenticationError.json in Resources */,
661 | 073CF2A624AB9FA000885A4D /* SignUpSuccessfully.json in Resources */,
662 | );
663 | runOnlyForDeploymentPostprocessing = 0;
664 | };
665 | /* End PBXResourcesBuildPhase section */
666 |
667 | /* Begin PBXShellScriptBuildPhase section */
668 | 186470753BA1BD174DEB8FBC /* [CP] Embed Pods Frameworks */ = {
669 | isa = PBXShellScriptBuildPhase;
670 | buildActionMask = 2147483647;
671 | files = (
672 | );
673 | inputFileListPaths = (
674 | "${PODS_ROOT}/Target Support Files/Pods-swift-ui-base/Pods-swift-ui-base-frameworks-${CONFIGURATION}-input-files.xcfilelist",
675 | );
676 | name = "[CP] Embed Pods Frameworks";
677 | outputFileListPaths = (
678 | "${PODS_ROOT}/Target Support Files/Pods-swift-ui-base/Pods-swift-ui-base-frameworks-${CONFIGURATION}-output-files.xcfilelist",
679 | );
680 | runOnlyForDeploymentPostprocessing = 0;
681 | shellPath = /bin/sh;
682 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-swift-ui-base/Pods-swift-ui-base-frameworks.sh\"\n";
683 | showEnvVarsInLog = 0;
684 | };
685 | 223F65BAAF9D5EE795660241 /* [CP] Check Pods Manifest.lock */ = {
686 | isa = PBXShellScriptBuildPhase;
687 | buildActionMask = 2147483647;
688 | files = (
689 | );
690 | inputFileListPaths = (
691 | );
692 | inputPaths = (
693 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
694 | "${PODS_ROOT}/Manifest.lock",
695 | );
696 | name = "[CP] Check Pods Manifest.lock";
697 | outputFileListPaths = (
698 | );
699 | outputPaths = (
700 | "$(DERIVED_FILE_DIR)/Pods-swift-ui-base-swift-ui-baseTests-checkManifestLockResult.txt",
701 | );
702 | runOnlyForDeploymentPostprocessing = 0;
703 | shellPath = /bin/sh;
704 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
705 | showEnvVarsInLog = 0;
706 | };
707 | 3F79AD3E3E8D2FD2A6B5D10A /* [CP] Embed Pods Frameworks */ = {
708 | isa = PBXShellScriptBuildPhase;
709 | buildActionMask = 2147483647;
710 | files = (
711 | );
712 | inputFileListPaths = (
713 | "${PODS_ROOT}/Target Support Files/Pods-swift-ui-base-swift-ui-baseTests/Pods-swift-ui-base-swift-ui-baseTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
714 | );
715 | name = "[CP] Embed Pods Frameworks";
716 | outputFileListPaths = (
717 | "${PODS_ROOT}/Target Support Files/Pods-swift-ui-base-swift-ui-baseTests/Pods-swift-ui-base-swift-ui-baseTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
718 | );
719 | runOnlyForDeploymentPostprocessing = 0;
720 | shellPath = /bin/sh;
721 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-swift-ui-base-swift-ui-baseTests/Pods-swift-ui-base-swift-ui-baseTests-frameworks.sh\"\n";
722 | showEnvVarsInLog = 0;
723 | };
724 | 4B075428A0F5713362B4C139 /* [CP] Check Pods Manifest.lock */ = {
725 | isa = PBXShellScriptBuildPhase;
726 | buildActionMask = 2147483647;
727 | files = (
728 | );
729 | inputFileListPaths = (
730 | );
731 | inputPaths = (
732 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
733 | "${PODS_ROOT}/Manifest.lock",
734 | );
735 | name = "[CP] Check Pods Manifest.lock";
736 | outputFileListPaths = (
737 | );
738 | outputPaths = (
739 | "$(DERIVED_FILE_DIR)/Pods-swift-ui-base-checkManifestLockResult.txt",
740 | );
741 | runOnlyForDeploymentPostprocessing = 0;
742 | shellPath = /bin/sh;
743 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
744 | showEnvVarsInLog = 0;
745 | };
746 | 53F4300D7B90091BCE83CDCB /* [CP] Check Pods Manifest.lock */ = {
747 | isa = PBXShellScriptBuildPhase;
748 | buildActionMask = 2147483647;
749 | files = (
750 | );
751 | inputFileListPaths = (
752 | );
753 | inputPaths = (
754 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
755 | "${PODS_ROOT}/Manifest.lock",
756 | );
757 | name = "[CP] Check Pods Manifest.lock";
758 | outputFileListPaths = (
759 | );
760 | outputPaths = (
761 | "$(DERIVED_FILE_DIR)/Pods-swift-ui-base-swift-ui-baseUITests-checkManifestLockResult.txt",
762 | );
763 | runOnlyForDeploymentPostprocessing = 0;
764 | shellPath = /bin/sh;
765 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
766 | showEnvVarsInLog = 0;
767 | };
768 | 6DA6DE36880E1CB9D374C91C /* [CP] Embed Pods Frameworks */ = {
769 | isa = PBXShellScriptBuildPhase;
770 | buildActionMask = 2147483647;
771 | files = (
772 | );
773 | inputFileListPaths = (
774 | "${PODS_ROOT}/Target Support Files/Pods-swift-ui-base-swift-ui-baseUITests/Pods-swift-ui-base-swift-ui-baseUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
775 | );
776 | name = "[CP] Embed Pods Frameworks";
777 | outputFileListPaths = (
778 | "${PODS_ROOT}/Target Support Files/Pods-swift-ui-base-swift-ui-baseUITests/Pods-swift-ui-base-swift-ui-baseUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
779 | );
780 | runOnlyForDeploymentPostprocessing = 0;
781 | shellPath = /bin/sh;
782 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-swift-ui-base-swift-ui-baseUITests/Pods-swift-ui-base-swift-ui-baseUITests-frameworks.sh\"\n";
783 | showEnvVarsInLog = 0;
784 | };
785 | /* End PBXShellScriptBuildPhase section */
786 |
787 | /* Begin PBXSourcesBuildPhase section */
788 | 07A649F42417E137003C6753 /* Sources */ = {
789 | isa = PBXSourcesBuildPhase;
790 | buildActionMask = 2147483647;
791 | files = (
792 | 07A649FC2417E137003C6753 /* AppDelegate.swift in Sources */,
793 | 0750F703248FEE59004B1D32 /* UserServices.swift in Sources */,
794 | 076843222434DF8C0052FD64 /* Session.swift in Sources */,
795 | 07442D13241C189300A235E7 /* ViewRouter.swift in Sources */,
796 | 074AADB924192895002C6F85 /* TextFieldData.swift in Sources */,
797 | 074B1440241AC6ED000CFFA0 /* LoginViewModel.swift in Sources */,
798 | 0750F701248FE225004B1D32 /* AvatarView.swift in Sources */,
799 | 0768433B243523AF0052FD64 /* User.swift in Sources */,
800 | 07849A77243783CE00321FB9 /* SignUpViewModel.swift in Sources */,
801 | 076843302434E6660052FD64 /* MultipartMedia.swift in Sources */,
802 | 6E032E0D24D347FC00952748 /* ConfigurationManager.swift in Sources */,
803 | 0768432E2434E5170052FD64 /* APIClient.swift in Sources */,
804 | 07442D0B241C08E700A235E7 /* ActivityIndicatorView.swift in Sources */,
805 | 07415FEC24183A7E00486885 /* StringExtension.swift in Sources */,
806 | 071D6B6C241976C200571DE9 /* TitleModifier.swift in Sources */,
807 | 076843322434E6B70052FD64 /* Base64Media.swift in Sources */,
808 | 0749E1B0242A743300340F8B /* ProfileView.swift in Sources */,
809 | 076843372434FFC70052FD64 /* App.swift in Sources */,
810 | 07849A7924378E4B00321FB9 /* ImageExtension.swift in Sources */,
811 | 076843242434DFBC0052FD64 /* DictionaryExtension.swift in Sources */,
812 | 0768431F2434D9B40052FD64 /* AuthenticationServices.swift in Sources */,
813 | 071D6B682419576500571DE9 /* ColorExtension.swift in Sources */,
814 | 07415FE524181D8800486885 /* SignUpView.swift in Sources */,
815 | 07415FE9241832E700486885 /* TextFieldView.swift in Sources */,
816 | 07442D11241C186100A235E7 /* RootView.swift in Sources */,
817 | 0750F70524900A9E004B1D32 /* RoundedButtonModifier.swift in Sources */,
818 | 07A649FE2417E137003C6753 /* SceneDelegate.swift in Sources */,
819 | 0778F5C1247436BC00EFCF10 /* ImagePicker.swift in Sources */,
820 | 07684339243523740052FD64 /* UserDataManager.swift in Sources */,
821 | 074F38DD245B6AA00042984E /* ProfileViewModel.swift in Sources */,
822 | 079BE4DC241816F700241627 /* LoginView.swift in Sources */,
823 | 076843262434DFD30052FD64 /* HTTPHeader.swift in Sources */,
824 | 076843292434E03B0052FD64 /* SessionManager.swift in Sources */,
825 | 07A64A002417E137003C6753 /* HomeView.swift in Sources */,
826 | );
827 | runOnlyForDeploymentPostprocessing = 0;
828 | };
829 | 07A64A0A2417E13B003C6753 /* Sources */ = {
830 | isa = PBXSourcesBuildPhase;
831 | buildActionMask = 2147483647;
832 | files = (
833 | 0748BD9F249BE87C00D5DAF9 /* StringExtensionUnitTests.swift in Sources */,
834 | 0748BD9C249BE7F900D5DAF9 /* UserServiceUnitTests.swift in Sources */,
835 | );
836 | runOnlyForDeploymentPostprocessing = 0;
837 | };
838 | 07A64A152417E13B003C6753 /* Sources */ = {
839 | isa = PBXSourcesBuildPhase;
840 | buildActionMask = 2147483647;
841 | files = (
842 | 0748BDA5249BEC1F00D5DAF9 /* XCTestCaseExtension.swift in Sources */,
843 | 07A64A1E2417E13B003C6753 /* swift_ui_baseUITests.swift in Sources */,
844 | 0748BDA1249BEBA400D5DAF9 /* XCUIApplicationExtension.swift in Sources */,
845 | 073CF2B424ABD09300885A4D /* NetworkMockerExtension.swift in Sources */,
846 | 073CF2A324AB9CD300885A4D /* NetworkMocker.swift in Sources */,
847 | 0748BDA3249BEBE500D5DAF9 /* XCUIElementExtension.swift in Sources */,
848 | );
849 | runOnlyForDeploymentPostprocessing = 0;
850 | };
851 | /* End PBXSourcesBuildPhase section */
852 |
853 | /* Begin PBXTargetDependency section */
854 | 07A64A102417E13B003C6753 /* PBXTargetDependency */ = {
855 | isa = PBXTargetDependency;
856 | target = 07A649F72417E137003C6753 /* swift-ui-base */;
857 | targetProxy = 07A64A0F2417E13B003C6753 /* PBXContainerItemProxy */;
858 | };
859 | 07A64A1B2417E13B003C6753 /* PBXTargetDependency */ = {
860 | isa = PBXTargetDependency;
861 | target = 07A649F72417E137003C6753 /* swift-ui-base */;
862 | targetProxy = 07A64A1A2417E13B003C6753 /* PBXContainerItemProxy */;
863 | };
864 | /* End PBXTargetDependency section */
865 |
866 | /* Begin PBXVariantGroup section */
867 | 07A64A062417E13A003C6753 /* LaunchScreen.storyboard */ = {
868 | isa = PBXVariantGroup;
869 | children = (
870 | 07A64A072417E13A003C6753 /* Base */,
871 | );
872 | name = LaunchScreen.storyboard;
873 | sourceTree = "";
874 | };
875 | /* End PBXVariantGroup section */
876 |
877 | /* Begin XCBuildConfiguration section */
878 | 073CF2AF24ABB0FF00885A4D /* UITest */ = {
879 | isa = XCBuildConfiguration;
880 | buildSettings = {
881 | ALWAYS_SEARCH_USER_PATHS = NO;
882 | CLANG_ANALYZER_NONNULL = YES;
883 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
884 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
885 | CLANG_CXX_LIBRARY = "libc++";
886 | CLANG_ENABLE_MODULES = YES;
887 | CLANG_ENABLE_OBJC_ARC = YES;
888 | CLANG_ENABLE_OBJC_WEAK = YES;
889 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
890 | CLANG_WARN_BOOL_CONVERSION = YES;
891 | CLANG_WARN_COMMA = YES;
892 | CLANG_WARN_CONSTANT_CONVERSION = YES;
893 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
894 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
895 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
896 | CLANG_WARN_EMPTY_BODY = YES;
897 | CLANG_WARN_ENUM_CONVERSION = YES;
898 | CLANG_WARN_INFINITE_RECURSION = YES;
899 | CLANG_WARN_INT_CONVERSION = YES;
900 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
901 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
902 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
903 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
904 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
905 | CLANG_WARN_STRICT_PROTOTYPES = YES;
906 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
907 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
908 | CLANG_WARN_UNREACHABLE_CODE = YES;
909 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
910 | COPY_PHASE_STRIP = NO;
911 | DEBUG_INFORMATION_FORMAT = dwarf;
912 | ENABLE_STRICT_OBJC_MSGSEND = YES;
913 | ENABLE_TESTABILITY = YES;
914 | GCC_C_LANGUAGE_STANDARD = gnu11;
915 | GCC_DYNAMIC_NO_PIC = NO;
916 | GCC_NO_COMMON_BLOCKS = YES;
917 | GCC_OPTIMIZATION_LEVEL = 0;
918 | GCC_PREPROCESSOR_DEFINITIONS = (
919 | "DEBUG=1",
920 | "$(inherited)",
921 | );
922 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
923 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
924 | GCC_WARN_UNDECLARED_SELECTOR = YES;
925 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
926 | GCC_WARN_UNUSED_FUNCTION = YES;
927 | GCC_WARN_UNUSED_VARIABLE = YES;
928 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
929 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
930 | MTL_FAST_MATH = YES;
931 | ONLY_ACTIVE_ARCH = YES;
932 | SDKROOT = iphoneos;
933 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
934 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
935 | };
936 | name = UITest;
937 | };
938 | 073CF2B024ABB0FF00885A4D /* UITest */ = {
939 | isa = XCBuildConfiguration;
940 | baseConfigurationReference = A44E706D7F79796D17992D5E /* Pods-swift-ui-base.uitest.xcconfig */;
941 | buildSettings = {
942 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
943 | BASE_URL = "http://localhost:8080";
944 | CODE_SIGN_STYLE = Automatic;
945 | DEVELOPMENT_ASSET_PATHS = "\"swift-ui-base/Preview Content\"";
946 | DEVELOPMENT_TEAM = V9839GT2TL;
947 | ENABLE_PREVIEWS = YES;
948 | INFOPLIST_FILE = "swift-ui-base/Info.plist";
949 | LD_RUNPATH_SEARCH_PATHS = (
950 | "$(inherited)",
951 | "@executable_path/Frameworks",
952 | );
953 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-base";
954 | PRODUCT_NAME = "$(TARGET_NAME)";
955 | SWIFT_VERSION = 5.0;
956 | TARGETED_DEVICE_FAMILY = "1,2";
957 | };
958 | name = UITest;
959 | };
960 | 073CF2B124ABB0FF00885A4D /* UITest */ = {
961 | isa = XCBuildConfiguration;
962 | baseConfigurationReference = 9A0F24E413CA6A32C483EDA7 /* Pods-swift-ui-base-swift-ui-baseTests.uitest.xcconfig */;
963 | buildSettings = {
964 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
965 | BUNDLE_LOADER = "$(TEST_HOST)";
966 | CLANG_ENABLE_MODULES = YES;
967 | CODE_SIGN_STYLE = Automatic;
968 | DEVELOPMENT_TEAM = V9839GT2TL;
969 | INFOPLIST_FILE = "swift-ui-baseTests/Info.plist";
970 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
971 | LD_RUNPATH_SEARCH_PATHS = (
972 | "$(inherited)",
973 | "@executable_path/Frameworks",
974 | "@loader_path/Frameworks",
975 | );
976 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-baseTests";
977 | PRODUCT_NAME = "$(TARGET_NAME)";
978 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
979 | SWIFT_VERSION = 5.0;
980 | TARGETED_DEVICE_FAMILY = "1,2";
981 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/swift-ui-base.app/swift-ui-base";
982 | };
983 | name = UITest;
984 | };
985 | 073CF2B224ABB0FF00885A4D /* UITest */ = {
986 | isa = XCBuildConfiguration;
987 | baseConfigurationReference = FFF599804C76164B16B725B4 /* Pods-swift-ui-base-swift-ui-baseUITests.uitest.xcconfig */;
988 | buildSettings = {
989 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
990 | CODE_SIGN_STYLE = Automatic;
991 | DEVELOPMENT_TEAM = V9839GT2TL;
992 | INFOPLIST_FILE = "swift-ui-baseUITests/Info.plist";
993 | IPHONEOS_DEPLOYMENT_TARGET = 13.3;
994 | LD_RUNPATH_SEARCH_PATHS = (
995 | "$(inherited)",
996 | "@executable_path/Frameworks",
997 | "@loader_path/Frameworks",
998 | );
999 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-baseUITests";
1000 | PRODUCT_NAME = "$(TARGET_NAME)";
1001 | SWIFT_VERSION = 5.0;
1002 | TARGETED_DEVICE_FAMILY = "1,2";
1003 | TEST_TARGET_NAME = "swift-ui-base";
1004 | };
1005 | name = UITest;
1006 | };
1007 | 07A64A202417E13B003C6753 /* Debug */ = {
1008 | isa = XCBuildConfiguration;
1009 | buildSettings = {
1010 | ALWAYS_SEARCH_USER_PATHS = NO;
1011 | CLANG_ANALYZER_NONNULL = YES;
1012 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
1013 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
1014 | CLANG_CXX_LIBRARY = "libc++";
1015 | CLANG_ENABLE_MODULES = YES;
1016 | CLANG_ENABLE_OBJC_ARC = YES;
1017 | CLANG_ENABLE_OBJC_WEAK = YES;
1018 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
1019 | CLANG_WARN_BOOL_CONVERSION = YES;
1020 | CLANG_WARN_COMMA = YES;
1021 | CLANG_WARN_CONSTANT_CONVERSION = YES;
1022 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
1023 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
1024 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
1025 | CLANG_WARN_EMPTY_BODY = YES;
1026 | CLANG_WARN_ENUM_CONVERSION = YES;
1027 | CLANG_WARN_INFINITE_RECURSION = YES;
1028 | CLANG_WARN_INT_CONVERSION = YES;
1029 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
1030 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
1031 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
1032 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
1033 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
1034 | CLANG_WARN_STRICT_PROTOTYPES = YES;
1035 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
1036 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
1037 | CLANG_WARN_UNREACHABLE_CODE = YES;
1038 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
1039 | COPY_PHASE_STRIP = NO;
1040 | DEBUG_INFORMATION_FORMAT = dwarf;
1041 | ENABLE_STRICT_OBJC_MSGSEND = YES;
1042 | ENABLE_TESTABILITY = YES;
1043 | GCC_C_LANGUAGE_STANDARD = gnu11;
1044 | GCC_DYNAMIC_NO_PIC = NO;
1045 | GCC_NO_COMMON_BLOCKS = YES;
1046 | GCC_OPTIMIZATION_LEVEL = 0;
1047 | GCC_PREPROCESSOR_DEFINITIONS = (
1048 | "DEBUG=1",
1049 | "$(inherited)",
1050 | );
1051 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
1052 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
1053 | GCC_WARN_UNDECLARED_SELECTOR = YES;
1054 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
1055 | GCC_WARN_UNUSED_FUNCTION = YES;
1056 | GCC_WARN_UNUSED_VARIABLE = YES;
1057 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
1058 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
1059 | MTL_FAST_MATH = YES;
1060 | ONLY_ACTIVE_ARCH = YES;
1061 | SDKROOT = iphoneos;
1062 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
1063 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
1064 | };
1065 | name = Debug;
1066 | };
1067 | 07A64A212417E13B003C6753 /* Release */ = {
1068 | isa = XCBuildConfiguration;
1069 | buildSettings = {
1070 | ALWAYS_SEARCH_USER_PATHS = NO;
1071 | CLANG_ANALYZER_NONNULL = YES;
1072 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
1073 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
1074 | CLANG_CXX_LIBRARY = "libc++";
1075 | CLANG_ENABLE_MODULES = YES;
1076 | CLANG_ENABLE_OBJC_ARC = YES;
1077 | CLANG_ENABLE_OBJC_WEAK = YES;
1078 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
1079 | CLANG_WARN_BOOL_CONVERSION = YES;
1080 | CLANG_WARN_COMMA = YES;
1081 | CLANG_WARN_CONSTANT_CONVERSION = YES;
1082 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
1083 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
1084 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
1085 | CLANG_WARN_EMPTY_BODY = YES;
1086 | CLANG_WARN_ENUM_CONVERSION = YES;
1087 | CLANG_WARN_INFINITE_RECURSION = YES;
1088 | CLANG_WARN_INT_CONVERSION = YES;
1089 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
1090 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
1091 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
1092 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
1093 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
1094 | CLANG_WARN_STRICT_PROTOTYPES = YES;
1095 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
1096 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
1097 | CLANG_WARN_UNREACHABLE_CODE = YES;
1098 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
1099 | COPY_PHASE_STRIP = NO;
1100 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
1101 | ENABLE_NS_ASSERTIONS = NO;
1102 | ENABLE_STRICT_OBJC_MSGSEND = YES;
1103 | GCC_C_LANGUAGE_STANDARD = gnu11;
1104 | GCC_NO_COMMON_BLOCKS = YES;
1105 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
1106 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
1107 | GCC_WARN_UNDECLARED_SELECTOR = YES;
1108 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
1109 | GCC_WARN_UNUSED_FUNCTION = YES;
1110 | GCC_WARN_UNUSED_VARIABLE = YES;
1111 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
1112 | MTL_ENABLE_DEBUG_INFO = NO;
1113 | MTL_FAST_MATH = YES;
1114 | SDKROOT = iphoneos;
1115 | SWIFT_COMPILATION_MODE = wholemodule;
1116 | SWIFT_OPTIMIZATION_LEVEL = "-O";
1117 | VALIDATE_PRODUCT = YES;
1118 | };
1119 | name = Release;
1120 | };
1121 | 07A64A232417E13B003C6753 /* Debug */ = {
1122 | isa = XCBuildConfiguration;
1123 | baseConfigurationReference = 1D5A66BD2D392E3008B86AE5 /* Pods-swift-ui-base.debug.xcconfig */;
1124 | buildSettings = {
1125 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1126 | BASE_URL = "https://ios-bases-api.herokuapp.com/api/v1";
1127 | CODE_SIGN_STYLE = Automatic;
1128 | DEVELOPMENT_ASSET_PATHS = "\"swift-ui-base/Preview Content\"";
1129 | DEVELOPMENT_TEAM = V9839GT2TL;
1130 | ENABLE_PREVIEWS = YES;
1131 | INFOPLIST_FILE = "swift-ui-base/Info.plist";
1132 | LD_RUNPATH_SEARCH_PATHS = (
1133 | "$(inherited)",
1134 | "@executable_path/Frameworks",
1135 | );
1136 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-base";
1137 | PRODUCT_NAME = "$(TARGET_NAME)";
1138 | SWIFT_VERSION = 5.0;
1139 | TARGETED_DEVICE_FAMILY = "1,2";
1140 | };
1141 | name = Debug;
1142 | };
1143 | 07A64A242417E13B003C6753 /* Release */ = {
1144 | isa = XCBuildConfiguration;
1145 | baseConfigurationReference = 7E03CB48557E6A834D246B24 /* Pods-swift-ui-base.release.xcconfig */;
1146 | buildSettings = {
1147 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1148 | BASE_URL = "https://ios-bases-api.herokuapp.com/api/v1";
1149 | CODE_SIGN_STYLE = Automatic;
1150 | DEVELOPMENT_ASSET_PATHS = "\"swift-ui-base/Preview Content\"";
1151 | DEVELOPMENT_TEAM = V9839GT2TL;
1152 | ENABLE_PREVIEWS = YES;
1153 | INFOPLIST_FILE = "swift-ui-base/Info.plist";
1154 | LD_RUNPATH_SEARCH_PATHS = (
1155 | "$(inherited)",
1156 | "@executable_path/Frameworks",
1157 | );
1158 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-base";
1159 | PRODUCT_NAME = "$(TARGET_NAME)";
1160 | SWIFT_VERSION = 5.0;
1161 | TARGETED_DEVICE_FAMILY = "1,2";
1162 | };
1163 | name = Release;
1164 | };
1165 | 07A64A262417E13B003C6753 /* Debug */ = {
1166 | isa = XCBuildConfiguration;
1167 | baseConfigurationReference = B06E4CC4A34CD8B26B3128AF /* Pods-swift-ui-base-swift-ui-baseTests.debug.xcconfig */;
1168 | buildSettings = {
1169 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1170 | BUNDLE_LOADER = "$(TEST_HOST)";
1171 | CLANG_ENABLE_MODULES = YES;
1172 | CODE_SIGN_STYLE = Automatic;
1173 | DEVELOPMENT_TEAM = V9839GT2TL;
1174 | INFOPLIST_FILE = "swift-ui-baseTests/Info.plist";
1175 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
1176 | LD_RUNPATH_SEARCH_PATHS = (
1177 | "$(inherited)",
1178 | "@executable_path/Frameworks",
1179 | "@loader_path/Frameworks",
1180 | );
1181 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-baseTests";
1182 | PRODUCT_NAME = "$(TARGET_NAME)";
1183 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
1184 | SWIFT_VERSION = 5.0;
1185 | TARGETED_DEVICE_FAMILY = "1,2";
1186 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/swift-ui-base.app/swift-ui-base";
1187 | };
1188 | name = Debug;
1189 | };
1190 | 07A64A272417E13B003C6753 /* Release */ = {
1191 | isa = XCBuildConfiguration;
1192 | baseConfigurationReference = B1D57A97908D6E25EB01EAF7 /* Pods-swift-ui-base-swift-ui-baseTests.release.xcconfig */;
1193 | buildSettings = {
1194 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1195 | BUNDLE_LOADER = "$(TEST_HOST)";
1196 | CLANG_ENABLE_MODULES = YES;
1197 | CODE_SIGN_STYLE = Automatic;
1198 | DEVELOPMENT_TEAM = V9839GT2TL;
1199 | INFOPLIST_FILE = "swift-ui-baseTests/Info.plist";
1200 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
1201 | LD_RUNPATH_SEARCH_PATHS = (
1202 | "$(inherited)",
1203 | "@executable_path/Frameworks",
1204 | "@loader_path/Frameworks",
1205 | );
1206 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-baseTests";
1207 | PRODUCT_NAME = "$(TARGET_NAME)";
1208 | SWIFT_VERSION = 5.0;
1209 | TARGETED_DEVICE_FAMILY = "1,2";
1210 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/swift-ui-base.app/swift-ui-base";
1211 | };
1212 | name = Release;
1213 | };
1214 | 07A64A292417E13B003C6753 /* Debug */ = {
1215 | isa = XCBuildConfiguration;
1216 | baseConfigurationReference = 7EC313E43672C416F121D319 /* Pods-swift-ui-base-swift-ui-baseUITests.debug.xcconfig */;
1217 | buildSettings = {
1218 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1219 | CODE_SIGN_STYLE = Automatic;
1220 | DEVELOPMENT_TEAM = V9839GT2TL;
1221 | INFOPLIST_FILE = "swift-ui-baseUITests/Info.plist";
1222 | IPHONEOS_DEPLOYMENT_TARGET = 13.3;
1223 | LD_RUNPATH_SEARCH_PATHS = (
1224 | "$(inherited)",
1225 | "@executable_path/Frameworks",
1226 | "@loader_path/Frameworks",
1227 | );
1228 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-baseUITests";
1229 | PRODUCT_NAME = "$(TARGET_NAME)";
1230 | SWIFT_VERSION = 5.0;
1231 | TARGETED_DEVICE_FAMILY = "1,2";
1232 | TEST_TARGET_NAME = "swift-ui-base";
1233 | };
1234 | name = Debug;
1235 | };
1236 | 07A64A2A2417E13B003C6753 /* Release */ = {
1237 | isa = XCBuildConfiguration;
1238 | baseConfigurationReference = D77EFC245B3FA3AAB543A6A3 /* Pods-swift-ui-base-swift-ui-baseUITests.release.xcconfig */;
1239 | buildSettings = {
1240 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1241 | CODE_SIGN_STYLE = Automatic;
1242 | DEVELOPMENT_TEAM = V9839GT2TL;
1243 | INFOPLIST_FILE = "swift-ui-baseUITests/Info.plist";
1244 | IPHONEOS_DEPLOYMENT_TARGET = 13.3;
1245 | LD_RUNPATH_SEARCH_PATHS = (
1246 | "$(inherited)",
1247 | "@executable_path/Frameworks",
1248 | "@loader_path/Frameworks",
1249 | );
1250 | PRODUCT_BUNDLE_IDENTIFIER = "com.rootstrap.swift-ui-baseUITests";
1251 | PRODUCT_NAME = "$(TARGET_NAME)";
1252 | SWIFT_VERSION = 5.0;
1253 | TARGETED_DEVICE_FAMILY = "1,2";
1254 | TEST_TARGET_NAME = "swift-ui-base";
1255 | };
1256 | name = Release;
1257 | };
1258 | /* End XCBuildConfiguration section */
1259 |
1260 | /* Begin XCConfigurationList section */
1261 | 07A649F32417E137003C6753 /* Build configuration list for PBXProject "swift-ui-base" */ = {
1262 | isa = XCConfigurationList;
1263 | buildConfigurations = (
1264 | 07A64A202417E13B003C6753 /* Debug */,
1265 | 073CF2AF24ABB0FF00885A4D /* UITest */,
1266 | 07A64A212417E13B003C6753 /* Release */,
1267 | );
1268 | defaultConfigurationIsVisible = 0;
1269 | defaultConfigurationName = Release;
1270 | };
1271 | 07A64A222417E13B003C6753 /* Build configuration list for PBXNativeTarget "swift-ui-base" */ = {
1272 | isa = XCConfigurationList;
1273 | buildConfigurations = (
1274 | 07A64A232417E13B003C6753 /* Debug */,
1275 | 073CF2B024ABB0FF00885A4D /* UITest */,
1276 | 07A64A242417E13B003C6753 /* Release */,
1277 | );
1278 | defaultConfigurationIsVisible = 0;
1279 | defaultConfigurationName = Release;
1280 | };
1281 | 07A64A252417E13B003C6753 /* Build configuration list for PBXNativeTarget "swift-ui-baseTests" */ = {
1282 | isa = XCConfigurationList;
1283 | buildConfigurations = (
1284 | 07A64A262417E13B003C6753 /* Debug */,
1285 | 073CF2B124ABB0FF00885A4D /* UITest */,
1286 | 07A64A272417E13B003C6753 /* Release */,
1287 | );
1288 | defaultConfigurationIsVisible = 0;
1289 | defaultConfigurationName = Release;
1290 | };
1291 | 07A64A282417E13B003C6753 /* Build configuration list for PBXNativeTarget "swift-ui-baseUITests" */ = {
1292 | isa = XCConfigurationList;
1293 | buildConfigurations = (
1294 | 07A64A292417E13B003C6753 /* Debug */,
1295 | 073CF2B224ABB0FF00885A4D /* UITest */,
1296 | 07A64A2A2417E13B003C6753 /* Release */,
1297 | );
1298 | defaultConfigurationIsVisible = 0;
1299 | defaultConfigurationName = Release;
1300 | };
1301 | /* End XCConfigurationList section */
1302 | };
1303 | rootObject = 07A649F02417E137003C6753 /* Project object */;
1304 | }
1305 |
--------------------------------------------------------------------------------