├── .gitignore
├── .swiftlint.yml
├── Brisk iOS.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── WorkspaceSettings.xcsettings
│ └── xcuserdata
│ │ └── florian.xcuserdatad
│ │ └── WorkspaceSettings.xcsettings
├── xcshareddata
│ └── xcschemes
│ │ └── Brisk.xcscheme
└── xcuserdata
│ └── florian.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── Brisk iOS.xcworkspace
├── contents.xcworkspacedata
├── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
└── xcuserdata
│ └── florian.xcuserdatad
│ └── WorkspaceSettings.xcsettings
├── Brisk iOS
├── App
│ ├── AppCoordinator.swift
│ ├── AppDelegate.swift
│ └── StatusDisplay.swift
├── Dupe
│ ├── DupeViewController.storyboard
│ └── DupeViewController.swift
├── Emptiness.swift
├── Keychain.swift
├── Login
│ ├── Email.swift
│ ├── LoginCoordinator.swift
│ ├── LoginError.swift
│ ├── LoginViewController.storyboard
│ ├── LoginViewController.swift
│ ├── OpenRadarViewController.storyboard
│ ├── OpenRadarViewController.swift
│ ├── Token.swift
│ └── User.swift
├── Menu
│ ├── MenuViewController.storyboard
│ └── MenuViewController.swift
├── Model
│ ├── APIController.swift
│ ├── Choice.swift
│ ├── Dictionary+Validation.swift
│ ├── KeyboardObservable.swift
│ ├── Localizable.swift
│ ├── OpenRadar.swift
│ ├── QuickAction.swift
│ ├── Radar+OpenRadar.swift
│ └── String+Extension.swift
├── Radar
│ ├── EnterDetailsViewController.storyboard
│ ├── EnterDetailsViewController.swift
│ ├── RadarCoordinator.swift
│ ├── RadarViewController.storyboard
│ ├── RadarViewController.swift
│ ├── RadarViewModel.swift
│ ├── SingleChoiceTableViewController.swift
│ ├── SubmitRadarDelegate.swift
│ └── TwoFactorAuthentication.swift
├── Resources
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon.png
│ │ │ ├── icon_60pt@2x-1.png
│ │ │ ├── icon_60pt@3x-1.png
│ │ │ ├── icon_76pt.png
│ │ │ ├── icon_76pt@2x.png
│ │ │ └── icon_83.5@2x.png
│ │ ├── Compose.imageset
│ │ │ ├── Contents.json
│ │ │ └── alert.pdf
│ │ ├── Contents.json
│ │ ├── Duplicate.imageset
│ │ │ ├── Contents.json
│ │ │ └── copy.pdf
│ │ ├── Logo.imageset
│ │ │ ├── 256-1.png
│ │ │ ├── 512.png
│ │ │ └── Contents.json
│ │ └── Settings.imageset
│ │ │ ├── Contents.json
│ │ │ └── slidersvertical.pdf
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Info.plist
│ └── en.lproj
│ │ └── Localizable.strings
├── Settings
│ ├── SettingsViewController.storyboard
│ └── SettingsViewController.swift
└── String+Extension.swift
├── Brisk iOSTests
├── EmailTests.swift
├── Info.plist
├── RadarNumberEtractionTests.swift
├── SubmitRadarTests.swift
└── TokenTests.swift
├── CHANGELOG.md
├── Gemfile
├── Gemfile.lock
├── License
├── Podfile
├── Podfile.lock
├── Pods
├── AcknowList
│ ├── LICENSE.txt
│ ├── README.md
│ ├── Resources
│ │ └── AcknowList.bundle
│ │ │ ├── da.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── de.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── en.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── es.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── fr.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── it.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── ja.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── nl.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── pt-BR.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── pt-PT.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── sv.lproj
│ │ │ └── Localizable.strings
│ │ │ ├── zh-Hans.lproj
│ │ │ └── Localizable.strings
│ │ │ └── zh-Hant.lproj
│ │ │ └── Localizable.strings
│ └── Source
│ │ ├── Acknow.swift
│ │ ├── AcknowListViewController.swift
│ │ ├── AcknowLocalization.swift
│ │ ├── AcknowParser.swift
│ │ └── AcknowViewController.swift
├── Alamofire
│ ├── LICENSE
│ ├── README.md
│ └── Source
│ │ ├── AFError.swift
│ │ ├── Alamofire.swift
│ │ ├── DispatchQueue+Alamofire.swift
│ │ ├── MultipartFormData.swift
│ │ ├── NetworkReachabilityManager.swift
│ │ ├── Notifications.swift
│ │ ├── ParameterEncoding.swift
│ │ ├── Request.swift
│ │ ├── Response.swift
│ │ ├── ResponseSerialization.swift
│ │ ├── Result.swift
│ │ ├── ServerTrustPolicy.swift
│ │ ├── SessionDelegate.swift
│ │ ├── SessionManager.swift
│ │ ├── TaskDelegate.swift
│ │ ├── Timeline.swift
│ │ └── Validation.swift
├── InterfaceBacked
│ ├── InterfaceBacked
│ │ ├── NibBacked.swift
│ │ └── StoryboardBacked.swift
│ ├── LICENSE
│ └── Readme.md
├── Local Podspecs
│ └── Sonar.podspec.json
├── Manifest.lock
├── Pods.xcodeproj
│ └── project.pbxproj
├── SVProgressHUD
│ ├── LICENSE
│ ├── README.md
│ └── SVProgressHUD
│ │ ├── SVIndefiniteAnimatedView.h
│ │ ├── SVIndefiniteAnimatedView.m
│ │ ├── SVProgressAnimatedView.h
│ │ ├── SVProgressAnimatedView.m
│ │ ├── SVProgressHUD.bundle
│ │ ├── angle-mask.png
│ │ ├── angle-mask@2x.png
│ │ ├── angle-mask@3x.png
│ │ ├── error.png
│ │ ├── error@2x.png
│ │ ├── error@3x.png
│ │ ├── info.png
│ │ ├── info@2x.png
│ │ ├── info@3x.png
│ │ ├── success.png
│ │ ├── success@2x.png
│ │ └── success@3x.png
│ │ ├── SVProgressHUD.h
│ │ ├── SVProgressHUD.m
│ │ ├── SVRadialGradientLayer.h
│ │ └── SVRadialGradientLayer.m
├── Sonar
│ ├── LICENSE
│ ├── README.md
│ └── Sources
│ │ └── Sonar
│ │ ├── APIs
│ │ ├── AppleRadar.swift
│ │ ├── AppleRadarRouter.swift
│ │ ├── BugTracker.swift
│ │ ├── OpenRadar.swift
│ │ ├── OpenRadarRouter.swift
│ │ ├── Result.swift
│ │ └── SonarError.swift
│ │ ├── Extensions
│ │ └── String+Regex.swift
│ │ ├── Models
│ │ ├── Area.swift
│ │ ├── Attachment.swift
│ │ ├── Classification.swift
│ │ ├── ModelsData.swift
│ │ ├── Product.swift
│ │ ├── Radar.swift
│ │ └── Reproducibility.swift
│ │ └── Sonar.swift
├── SwiftLint
│ ├── LICENSE
│ └── swiftlint
└── Target Support Files
│ ├── AcknowList
│ ├── AcknowList-dummy.m
│ ├── AcknowList-prefix.pch
│ ├── AcknowList-umbrella.h
│ ├── AcknowList.modulemap
│ ├── AcknowList.xcconfig
│ └── Info.plist
│ ├── Alamofire
│ ├── Alamofire-dummy.m
│ ├── Alamofire-prefix.pch
│ ├── Alamofire-umbrella.h
│ ├── Alamofire.modulemap
│ ├── Alamofire.xcconfig
│ └── Info.plist
│ ├── InterfaceBacked
│ ├── Info.plist
│ ├── InterfaceBacked-dummy.m
│ ├── InterfaceBacked-prefix.pch
│ ├── InterfaceBacked-umbrella.h
│ ├── InterfaceBacked.modulemap
│ └── InterfaceBacked.xcconfig
│ ├── Pods-Brisk iOS
│ ├── Info.plist
│ ├── Pods-Brisk iOS-acknowledgements.markdown
│ ├── Pods-Brisk iOS-acknowledgements.plist
│ ├── Pods-Brisk iOS-dummy.m
│ ├── Pods-Brisk iOS-frameworks.sh
│ ├── Pods-Brisk iOS-resources.sh
│ ├── Pods-Brisk iOS-umbrella.h
│ ├── Pods-Brisk iOS.debug.xcconfig
│ ├── Pods-Brisk iOS.modulemap
│ └── Pods-Brisk iOS.release.xcconfig
│ ├── Pods-Brisk iOSTests
│ ├── Info.plist
│ ├── Pods-Brisk iOSTests-acknowledgements.markdown
│ ├── Pods-Brisk iOSTests-acknowledgements.plist
│ ├── Pods-Brisk iOSTests-dummy.m
│ ├── Pods-Brisk iOSTests-frameworks.sh
│ ├── Pods-Brisk iOSTests-resources.sh
│ ├── Pods-Brisk iOSTests-umbrella.h
│ ├── Pods-Brisk iOSTests.debug.xcconfig
│ ├── Pods-Brisk iOSTests.modulemap
│ └── Pods-Brisk iOSTests.release.xcconfig
│ ├── SVProgressHUD
│ ├── Info.plist
│ ├── SVProgressHUD-dummy.m
│ ├── SVProgressHUD-prefix.pch
│ ├── SVProgressHUD-umbrella.h
│ ├── SVProgressHUD.modulemap
│ └── SVProgressHUD.xcconfig
│ └── Sonar
│ ├── Info.plist
│ ├── Sonar-dummy.m
│ ├── Sonar-prefix.pch
│ ├── Sonar-umbrella.h
│ ├── Sonar.modulemap
│ └── Sonar.xcconfig
├── Readme.md
├── Resources
├── Assets
│ ├── Brisk Icon.sketch
│ └── Brisk.sketch
└── Screenshots
│ ├── 01-start.png
│ ├── 02-dupe.png
│ └── 03-file.png
└── fastlane
├── AppStore_com.florianbuerger.brisk.ios.mobileprovision
├── Appfile
├── Deliverfile
├── Fastfile
├── README.md
└── metadata
├── copyright.txt
├── en-US
├── description.txt
├── keywords.txt
├── marketing_url.txt
├── name.txt
├── privacy_url.txt
├── promotional_text.txt
├── release_notes.txt
├── subtitle.txt
└── support_url.txt
├── primary_category.txt
├── primary_first_sub_category.txt
├── primary_second_sub_category.txt
├── review_information
├── demo_password.txt
├── demo_user.txt
├── email_address.txt
├── first_name.txt
├── last_name.txt
├── notes.txt
└── phone_number.txt
├── secondary_category.txt
├── secondary_first_sub_category.txt
├── secondary_second_sub_category.txt
└── trade_representative_contact_information
├── address_line1.txt
├── address_line2.txt
├── address_line3.txt
├── city_name.txt
├── country.txt
├── email_address.txt
├── first_name.txt
├── is_displayed_on_app_store.txt
├── last_name.txt
├── phone_number.txt
├── postal_code.txt
├── state.txt
└── trade_name.txt
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/macos,swift
3 |
4 | ### macOS ###
5 | *.DS_Store
6 | .AppleDouble
7 | .LSOverride
8 |
9 | # Icon must end with two \r
10 | Icon
11 |
12 | # Thumbnails
13 | ._*
14 |
15 | # Files that might appear in the root of a volume
16 | .DocumentRevisions-V100
17 | .fseventsd
18 | .Spotlight-V100
19 | .TemporaryItems
20 | .Trashes
21 | .VolumeIcon.icns
22 | .com.apple.timemachine.donotpresent
23 |
24 | # Directories potentially created on remote AFP share
25 | .AppleDB
26 | .AppleDesktop
27 | Network Trash Folder
28 | Temporary Items
29 | .apdisk
30 |
31 | ### Swift ###
32 | # Xcode
33 | #
34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
35 |
36 | ## Build generated
37 | build/
38 | DerivedData/
39 |
40 | ## Various settings
41 | *.pbxuser
42 | !default.pbxuser
43 | *.mode1v3
44 | !default.mode1v3
45 | *.mode2v3
46 | !default.mode2v3
47 | *.perspectivev3
48 | !default.perspectivev3
49 | xcuserdata/
50 |
51 | ## Other
52 | *.moved-aside
53 | *.xccheckout
54 | *.xcscmblueprint
55 |
56 | ## Obj-C/Swift specific
57 | *.hmap
58 | *.ipa
59 | *.dSYM.zip
60 | *.dSYM
61 |
62 | ## Playgrounds
63 | timeline.xctimeline
64 | playground.xcworkspace
65 |
66 | # Swift Package Manager
67 | #
68 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
69 | # Packages/
70 | # Package.pins
71 | .build/
72 |
73 | # CocoaPods - Refactored to standalone file
74 |
75 | # Carthage - Refactored to standalone file
76 |
77 | # fastlane
78 | #
79 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
80 | # screenshots whenever they are needed.
81 | # For more information about the recommended setup visit:
82 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
83 |
84 | fastlane/report.xml
85 | fastlane/Preview.html
86 | fastlane/screenshots
87 | fastlane/test_output
88 |
89 | ### Swift.Carthage Stack ###
90 | # Carthage
91 | #
92 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
93 | # Carthage/Checkouts
94 |
95 | Carthage/Build
96 |
97 | ### Swift.CocoaPods Stack ###
98 | ## CocoaPods GitIgnore Template
99 |
100 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing
101 | # - Also handy if you have a lage number of dependant pods
102 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGONRE THE LOCK FILE
103 | # Pods/
104 |
105 | # End of https://www.gitignore.io/api/macos,swift
106 |
107 | buildkite-script*
108 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | excluded:
2 | - Pods
3 |
4 | disabled_rules:
5 | - line_length
6 | - todo
7 | - nesting
8 |
9 | # limit whitespace only lines to 2
10 | vertical_whitespace:
11 | max_empty_lines: 2
12 |
--------------------------------------------------------------------------------
/Brisk iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Brisk iOS.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildSystemType
6 | Latest
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Brisk iOS.xcodeproj/project.xcworkspace/xcuserdata/florian.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | EnabledFullIndexStoreVisibility
12 |
13 | IssueFilterStyle
14 | ShowActiveSchemeOnly
15 | LiveSourceIssuesEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Brisk iOS.xcodeproj/xcshareddata/xcschemes/Brisk.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
65 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
86 |
92 |
93 |
94 |
95 |
97 |
98 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/Brisk iOS.xcodeproj/xcuserdata/florian.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/Brisk iOS.xcodeproj/xcuserdata/florian.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Brisk.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 7
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 177A32DB1F05C6CD0044D836
16 |
17 | primary
18 |
19 |
20 | 177A32EF1F05C6CE0044D836
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Brisk iOS.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Brisk iOS.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Brisk iOS.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Brisk iOS.xcworkspace/xcuserdata/florian.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | EnabledFullIndexStoreVisibility
12 |
13 | IssueFilterStyle
14 | ShowActiveSchemeOnly
15 | LiveSourceIssuesEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Brisk iOS/App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @UIApplicationMain
4 | class AppDelegate: UIResponder, UIApplicationDelegate {
5 |
6 |
7 | // MARK: - Properties
8 |
9 | var window: UIWindow?
10 | var appCoordinator: AppCoordinator?
11 |
12 | // MARK: - UIApplicationDelegate Methods
13 |
14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
15 | let coordinator = AppCoordinator()
16 | window = coordinator.start()
17 | appCoordinator = coordinator
18 | return true
19 | }
20 |
21 | func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
22 | if let type = QuickAction.Radar(rawValue: shortcutItem.type) {
23 | appCoordinator?.handleQuick(action: type)
24 | completionHandler(true)
25 | }
26 | }
27 |
28 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any] = [:]) -> Bool {
29 | guard let coordinator = appCoordinator else { return false }
30 | return coordinator.handle(url: url)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Brisk iOS/App/StatusDisplay.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SVProgressHUD
3 |
4 | protocol StatusDisplay {
5 | func showLoading()
6 | func showSuccess(message: String, autoDismissAfter delay: TimeInterval)
7 | func showError(title: String?, message: String, dismissButtonTitle: String?, completion: (() -> Void)?)
8 | func hideLoading()
9 | }
10 |
11 | extension StatusDisplay where Self: UIViewController {
12 |
13 | func showLoading() {
14 | SVProgressHUD.show()
15 | }
16 |
17 | func showSuccess(message: String, autoDismissAfter delay: TimeInterval = 3.0) {
18 | SVProgressHUD.setMinimumDismissTimeInterval(delay)
19 | SVProgressHUD.showSuccess(withStatus: message)
20 | }
21 |
22 | func showError(title: String? = Localizable.Global.error.localized,
23 | message: String,
24 | dismissButtonTitle: String? = Localizable.Global.dismiss.localized,
25 | completion: (() -> Void)? = nil) {
26 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
27 | alert.addAction(UIAlertAction(title: dismissButtonTitle, style: .cancel, handler: nil))
28 | present(alert, animated: true, completion: completion)
29 | }
30 |
31 | func hideLoading() {
32 | SVProgressHUD.dismiss()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Brisk iOS/Dupe/DupeViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import InterfaceBacked
3 |
4 | protocol DupeViewDelegate: class {
5 | func controllerDidCancel(_ controller: DupeViewController)
6 | func controller(_ controller: DupeViewController, didSubmit number: String)
7 | }
8 |
9 | final class DupeViewController: UIViewController, StoryboardBacked, StatusDisplay {
10 |
11 |
12 | // MARK: - Properties
13 |
14 | @IBOutlet private weak var numberField: UITextField!
15 | @IBOutlet private weak var hintLabel: UILabel!
16 | var number: String = ""
17 | weak var delegate: DupeViewDelegate?
18 |
19 |
20 | // MARK: - UIViewController Methods
21 |
22 | override func viewWillAppear(_ animated: Bool) {
23 | if let content = UIPasteboard.general.string, content.isOpenRadar && number.isEmpty {
24 | numberField.text = content.extractRadarNumber()
25 | hintLabel.text = "Found \(content) on your clipboard"
26 | } else {
27 | hintLabel.text = "You can also post rdar:// or https://openradar.appspot.com/ links"
28 | }
29 |
30 | if number.isNotEmpty && number.isOpenRadar {
31 | numberField.text = number
32 | }
33 | }
34 |
35 | // MARK: - User Actions
36 |
37 | @IBAction func submitTapped() {
38 | // TODO: Show error if invalid
39 | guard let text = numberField.text, text.isOpenRadar else { return }
40 | let number = text.extractRadarNumber() ?? ""
41 | delegate?.controller(self, didSubmit: number)
42 | }
43 |
44 | @IBAction func cancelTapped() {
45 | delegate?.controllerDidCancel(self)
46 | }
47 |
48 | }
49 |
50 |
51 | // MARK: - UITextFieldDelegate Methods
52 |
53 | extension DupeViewController: UITextFieldDelegate {
54 |
55 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
56 | textField.resignFirstResponder()
57 | return true
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Brisk iOS/Emptiness.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension String {
4 |
5 | var isNotEmpty: Bool {
6 | return !isEmpty
7 | }
8 | }
9 |
10 | extension Array {
11 |
12 | var isNotEmpty: Bool {
13 | return !isEmpty
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Brisk iOS/Keychain.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Security
3 |
4 | private let kAccessibilityLevel = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
5 | private let kService = "Brisk"
6 |
7 | enum KeychainKey: String {
8 | case radar = "Radar Login"
9 | case openRadar = "Open Radar Token"
10 | }
11 |
12 | struct Keychain {
13 | static func get(_ key: KeychainKey) -> (String, String)? {
14 | let attributes: [CFString: Any] = [
15 | kSecAttrAccessible: kAccessibilityLevel,
16 | kSecAttrLabel: key.rawValue,
17 | kSecAttrService: kService,
18 | kSecClass: kSecClassGenericPassword,
19 | kSecMatchLimit: kSecMatchLimitOne,
20 | kSecReturnAttributes: kCFBooleanTrue,
21 | kSecReturnData: kCFBooleanTrue
22 | ]
23 |
24 | var result: CFTypeRef?
25 | let status = SecItemCopyMatching(attributes as CFDictionary, &result)
26 |
27 | guard let dictionary = result as? NSDictionary, status.success else {
28 | return nil
29 | }
30 |
31 | let username = dictionary[kSecAttrAccount as String] as? String
32 | let passwordData = dictionary[kSecValueData as String] as? Data
33 | let password = passwordData.flatMap { String(data: $0, encoding: String.Encoding.utf8) }
34 | if let username = username, let password = password {
35 | return (username, password)
36 | }
37 |
38 | return nil
39 | }
40 |
41 | @discardableResult
42 | static func set(username: String, password: String, forKey key: KeychainKey) -> Bool {
43 | guard let passwordData = password.data(using: String.Encoding.utf8) else {
44 | return false
45 | }
46 |
47 | self.delete(key)
48 |
49 | let attributes: [CFString: Any] = [
50 | kSecAttrAccessible: kAccessibilityLevel,
51 | kSecAttrAccount: username,
52 | kSecAttrLabel: key.rawValue,
53 | kSecAttrService: kService,
54 | kSecClass: kSecClassGenericPassword,
55 | kSecValueData: passwordData
56 | ]
57 |
58 | let status = SecItemAdd(attributes as CFDictionary, nil)
59 | return status.success
60 | }
61 |
62 | @discardableResult
63 | static func delete(_ key: KeychainKey) -> Bool {
64 | let attributes: [CFString: Any] = [
65 | kSecAttrAccessible: kAccessibilityLevel,
66 | kSecAttrLabel: key.rawValue,
67 | kSecAttrService: kService,
68 | kSecClass: kSecClassGenericPassword
69 | ]
70 |
71 | let status = SecItemDelete(attributes as CFDictionary)
72 | return status.success
73 | }
74 | }
75 |
76 | extension CFString: Hashable {
77 | public var hashValue: Int {
78 | return (self as String).hashValue
79 | }
80 | }
81 |
82 | public func == (lhs: CFString, rhs: CFString) -> Bool {
83 | return lhs as String == rhs as String
84 | }
85 |
86 | private extension OSStatus {
87 | var success: Bool {
88 | return self == noErr
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Brisk iOS/Login/Email.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | final class Email {
4 |
5 | let value: String
6 |
7 | init(_ value: String) {
8 | self.value = value
9 | }
10 |
11 | var isValid: Bool {
12 | let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
13 | let match = value.range(of: pattern, options: .regularExpression)
14 | return match != nil
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Brisk iOS/Login/LoginCoordinator.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SafariServices
3 |
4 | private let kAPIKeyURL = URL(string: "https://openradar.appspot.com/apikey")!
5 | private let kOpenRadarUsername = "openradar"
6 |
7 | final class LoginCoordinator {
8 |
9 |
10 | // MARK: - Properties
11 |
12 | let source: UIViewController
13 | var loginController: LoginViewController?
14 | let root = UINavigationController()
15 |
16 |
17 | // MARK: - Init/Deinit
18 |
19 | init(from source: UIViewController) {
20 | self.source = source
21 | }
22 |
23 | // MARK: - Public API
24 |
25 | func start() {
26 | let controller = LoginViewController.newFromStoryboard()
27 | controller.delegate = self
28 | root.viewControllers = [controller]
29 | root.modalPresentationStyle = .formSheet
30 | source.present(root, animated: true)
31 | loginController = controller
32 | }
33 |
34 | func finish() {
35 | source.dismiss(animated: true)
36 | }
37 |
38 | // MARK: - Private
39 |
40 | fileprivate func showError(_ error: LoginError) {
41 | let global = Localizable.Global.self
42 |
43 | let alert = UIAlertController(title: global.error.localized, message: error.localizedDescription, preferredStyle: .alert)
44 | let cancel = UIAlertAction(title: global.tryAgain.localized, style: .cancel) { [weak self] _ in
45 | self?.loginController?.dismiss(animated: true, completion: nil)
46 | }
47 | alert.addAction(cancel)
48 | loginController?.present(alert, animated: true, completion: nil)
49 | }
50 | }
51 |
52 |
53 | // MARK: - LoginViewDelegate Methods
54 |
55 | extension LoginCoordinator: LoginViewDelegate {
56 |
57 | func submitTapped(user: User) {
58 | guard user.email.isValid else {
59 | showError(.invalidEmail)
60 | return
61 | }
62 |
63 |
64 | // Save to keychain
65 | Keychain.set(username: user.email.value, password: user.password, forKey: .radar)
66 |
67 | // Continue to open radar
68 | let openradar = OpenRadarViewController.newFromStoryboard()
69 | openradar.delegate = self
70 | root.show(openradar, sender: self)
71 | }
72 |
73 | }
74 |
75 |
76 | // MARK: - OpenRadarViewDelegate Method
77 |
78 | extension LoginCoordinator: OpenRadarViewDelegate {
79 |
80 | func openSafariTapped() {
81 | let safari = SFSafariViewController(url: kAPIKeyURL)
82 | root.showDetailViewController(safari, sender: self)
83 | }
84 |
85 | func continueTapped(token: String) {
86 | // Save to keychain
87 | if token.isEmpty {
88 | Keychain.delete(.openRadar)
89 | } else {
90 | Keychain.set(username: kOpenRadarUsername, password: token, forKey: .openRadar)
91 | }
92 |
93 | finish()
94 | }
95 |
96 | func skipTapped() {
97 | finish()
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/Brisk iOS/Login/LoginError.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum LoginError: Error {
4 | case invalidEmail
5 | }
6 | extension LoginError: LocalizedError {
7 | var errorDescription: String? {
8 | switch self {
9 | case .invalidEmail:
10 | return Localizable.Login.Error.invalidEmail.localized
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Brisk iOS/Login/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import InterfaceBacked
3 |
4 | protocol LoginViewDelegate: class {
5 | func submitTapped(user: User)
6 | }
7 |
8 |
9 | final class LoginViewController: UIViewController, StoryboardBacked {
10 |
11 |
12 | // MARK: - Properties
13 |
14 | @IBOutlet weak var emailField: UITextField!
15 | @IBOutlet weak var passwordField: UITextField!
16 | @IBOutlet weak var submitButton: UIButton!
17 | weak var delegate: LoginViewDelegate?
18 |
19 |
20 | // MARK: - UIViewController Methods
21 |
22 | override func viewWillAppear(_ animated: Bool) {
23 | emailField.becomeFirstResponder()
24 | validateSubmitButton()
25 | }
26 |
27 |
28 | // MARK: - User Actions
29 |
30 | @IBAction func submitTapped() {
31 | guard let rawEmail = emailField.text, let password = passwordField.text else { return }
32 | let email = Email(rawEmail)
33 | let user = User(email: email, password: password)
34 | delegate?.submitTapped(user: user)
35 |
36 | let shortcuts = QuickAction.Shortcut.self
37 | UIApplication.shared.shortcutItems = [shortcuts.new, shortcuts.duplicate]
38 | }
39 |
40 | @IBAction func textFieldChanged() {
41 | validateSubmitButton()
42 | }
43 |
44 |
45 | // MARK: - Private
46 |
47 | private func validateSubmitButton() {
48 | if let email = emailField.text, email.isNotEmpty, let password = passwordField.text, password.isNotEmpty {
49 | submitButton.isEnabled = true
50 | } else {
51 | submitButton.isEnabled = false
52 | }
53 | }
54 | }
55 |
56 |
57 | // MARK: - Extension
58 |
59 | extension LoginViewController: UITextFieldDelegate {
60 |
61 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
62 | if textField === emailField {
63 | textField.resignFirstResponder()
64 | passwordField.becomeFirstResponder()
65 | }
66 | if textField === passwordField {
67 | passwordField.resignFirstResponder()
68 | submitTapped()
69 | }
70 | return true
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/Brisk iOS/Login/OpenRadarViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import InterfaceBacked
3 |
4 | protocol OpenRadarViewDelegate: class {
5 | func openSafariTapped()
6 | func continueTapped(token: String)
7 | func skipTapped()
8 | }
9 |
10 | final class OpenRadarViewController: UITableViewController, StoryboardBacked {
11 |
12 |
13 | // MARK: - Properties
14 |
15 | @IBOutlet weak var tokenField: UITextField!
16 | weak var delegate: OpenRadarViewDelegate?
17 |
18 | let openSafariRow = 0
19 | let fieldRow = 1
20 | let finishRow = 2
21 |
22 |
23 | // MARK: - User Actions
24 |
25 | @IBAction func skipTapped() { delegate?.skipTapped() }
26 |
27 |
28 | // MARK: - UITableViewController Methods
29 |
30 | override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
31 | switch indexPath.row {
32 | case fieldRow: return false
33 | case finishRow:
34 | let token = tokenField.text ?? ""
35 | let validator = Token(token)
36 | return validator.isValid
37 | default: return true
38 | }
39 | }
40 |
41 | override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
42 | let selectable = self.tableView(tableView, shouldHighlightRowAt: indexPath)
43 | cell.contentView.alpha = selectable || indexPath.row == fieldRow ? 1.0 : 0.5
44 | }
45 |
46 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
47 | switch indexPath.row {
48 | case openSafariRow: delegate?.openSafariTapped()
49 | case finishRow: delegate?.continueTapped(token: tokenField.text ?? "")
50 | default: break
51 | }
52 | }
53 | }
54 |
55 |
56 | // MARK: - UITextFieldDelegate Methods
57 |
58 | extension OpenRadarViewController: UITextFieldDelegate {
59 |
60 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
61 | textField.resignFirstResponder()
62 | return true
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Brisk iOS/Login/Token.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | final class Token {
4 |
5 | let value: String
6 |
7 | init(_ value: String) {
8 | self.value = value
9 | }
10 |
11 | var isValid: Bool {
12 | let pattern = "[\\w]{8}-[\\w]{4}-[\\w]{4}-[\\w]{4}-[\\w]{12}"
13 | let match = value.range(of: pattern, options: .regularExpression)
14 | return match != nil
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Brisk iOS/Login/User.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct User {
4 | let email: Email
5 | let password: String
6 | }
7 |
--------------------------------------------------------------------------------
/Brisk iOS/Menu/MenuViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import InterfaceBacked
3 |
4 | protocol MenuViewDelegate: class {
5 | func dupeTapped()
6 | func fileTapped()
7 | func settingsTapped()
8 | }
9 |
10 | final class MenuViewController: UIViewController, StoryboardBacked {
11 |
12 |
13 | // MARK: - Properties
14 |
15 | weak var delegate: MenuViewDelegate?
16 | @IBOutlet var duplicateButton: UIButton!
17 | @IBOutlet var newButton: UIButton!
18 | @IBOutlet var settingsButton: UIButton!
19 |
20 | // MARK: - UIViewController Methods
21 |
22 | override func viewDidLoad() {
23 | super.viewDidLoad()
24 |
25 | for button in [duplicateButton, newButton, settingsButton] {
26 | button?.layer.cornerRadius = 6
27 | button?.layer.masksToBounds = true
28 | }
29 | }
30 |
31 | // MARK: - User Actions
32 |
33 | @IBAction func dupeButtonTapped() {
34 | delegate?.dupeTapped()
35 | }
36 |
37 | @IBAction func fileButtonTapped() {
38 | delegate?.fileTapped()
39 | }
40 |
41 | @IBAction func settingsTapped() {
42 | delegate?.settingsTapped()
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/APIController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Sonar
3 | import Alamofire
4 |
5 | protocol APIDelegate: class {
6 | func didStartLoading()
7 | func didFail(with error: SonarError)
8 | func didPostToAppleRadar()
9 | func didPostToOpenRadar()
10 | }
11 |
12 | protocol TwoFactorAuthenticationHandler: class {
13 | func askForCode(completion: @escaping (String) -> Void)
14 | }
15 |
16 | final class APIController {
17 |
18 |
19 | // MARK: - Properties
20 |
21 | weak var delegate: APIDelegate?
22 | weak var twoFactorHandler: TwoFactorAuthenticationHandler?
23 |
24 |
25 | // MARK: - Init/Deinit
26 |
27 | init(delegate: APIDelegate? = nil, twoFactorHandler: TwoFactorAuthenticationHandler? = nil) {
28 | self.delegate = delegate
29 | self.twoFactorHandler = twoFactorHandler
30 | }
31 |
32 |
33 | // MARK: - Duping
34 |
35 |
36 | // TODO: Use delegate
37 |
38 | func search(forRadarWithId radarId: String, loading: @escaping (Bool) -> Void, success: @escaping (Radar) -> Void, failure: @escaping (String, String) -> Void) {
39 |
40 | // Fetch existing radar
41 | guard let url = URL(string: "https://openradar.appspot.com/api/radar?number=\(radarId)") else {
42 | preconditionFailure()
43 | }
44 |
45 | loading(true)
46 |
47 | Alamofire.request(url)
48 | .validate()
49 | .responseJSON { result in
50 |
51 | loading(false)
52 |
53 | if let error = result.error {
54 | failure("Error", error.localizedDescription)
55 | return
56 | }
57 |
58 | guard let json = result.value as? [String: Any], let result = json["result"] as? [String: Any], !result.isEmpty else {
59 | failure("No OpenRadar found", "Couldn't find an OpenRadar with ID #\(radarId)")
60 | return
61 | }
62 |
63 | guard let radar = try? Radar(openRadar: json) else {
64 | failure("Invalid OpenRadar", "OpenRadar is missing required fields")
65 | return
66 | }
67 |
68 | success(radar)
69 | }
70 | }
71 |
72 |
73 | // MARK: - Filing
74 |
75 | func file(radar: Radar) {
76 | guard let (username, password) = Keychain.get(.radar) else {
77 | preconditionFailure("Shouldn't be able to submit a radar without credentials")
78 | }
79 |
80 | delegate?.didStartLoading()
81 |
82 | let handleTwoFactorAuth: (@escaping (String?) -> Void) -> Void = { [weak self] closure in
83 | self?.twoFactorHandler?.askForCode(completion: closure)
84 | }
85 |
86 | var radar = radar
87 |
88 | // Post to Apple Radar
89 |
90 | let appleRadar = Sonar(service: .appleRadar(appleID: username, password: password))
91 | appleRadar.loginThenCreate(radar: radar, getTwoFactorCode: handleTwoFactorAuth) { [weak self] result in
92 | switch result {
93 | case .success(let radarID):
94 |
95 | // Don't post to OpenRadar if no token is present.
96 | // TODO: Should we show a confirmation/login for OpenRadar?
97 | guard let (_, token) = Keychain.get(.openRadar) else {
98 | self?.delegate?.didPostToAppleRadar()
99 | return
100 | }
101 |
102 | // Post to open radar
103 |
104 | radar.ID = radarID
105 | let openRadar = Sonar(service: .openRadar(token: token))
106 | openRadar.loginThenCreate(radar: radar, getTwoFactorCode: handleTwoFactorAuth) { [weak self] result in
107 | switch result {
108 | case .success:
109 | self?.delegate?.didPostToAppleRadar()
110 | self?.delegate?.didPostToOpenRadar()
111 | case .failure(let error):
112 | self?.delegate?.didFail(with: error)
113 | }
114 | }
115 | case .failure(let error):
116 | self?.delegate?.didFail(with: error)
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/Choice.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Sonar
3 |
4 | protocol Choice {
5 | var name: String { get }
6 | }
7 |
8 | extension Product: Choice {}
9 | extension Area: Choice {}
10 | extension Classification: Choice {}
11 | extension Reproducibility: Choice {}
12 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/Dictionary+Validation.swift:
--------------------------------------------------------------------------------
1 | public extension Dictionary where Key == String {
2 | public func onlyStrings() -> [String: String] {
3 | var newDictionary = [String: String]()
4 | for (key, value) in self {
5 | newDictionary[key] = value as? String
6 | }
7 |
8 | return newDictionary
9 | }
10 | }
11 |
12 | public extension Dictionary where Key == String, Value == String {
13 | public func filterEmpty() -> [String: String] {
14 | var newDictionary = [String: String]()
15 | for (key, value) in self where !value.isEmpty {
16 | newDictionary[key] = value
17 | }
18 |
19 | return newDictionary
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/KeyboardObservable.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | protocol KeyboardObservable {
4 | func keyboardObservers() -> [Any]
5 | func removeObservers(_ observers: [Any])
6 | }
7 |
8 | extension KeyboardObservable where Self: UITextView {
9 | func keyboardObservers() -> [Any] {
10 | let observer1 = NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
11 | self?.keyboardWillShow(notification)
12 | }
13 | let observer2 = NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in
14 | self?.keyboardWillHide(notification)
15 | }
16 | return [observer1, observer2]
17 | }
18 | func removeObservers(_ observers: [Any]) {
19 | observers.forEach {
20 | NotificationCenter.default.removeObserver($0)
21 | }
22 | }
23 | private var currentInset: UIEdgeInsets {
24 | if #available(iOS 11.0, *) {
25 | return adjustedContentInset
26 | }
27 | return contentInset
28 | }
29 | private var textIsEmpty: Bool {
30 | switch text {
31 | case .some(let content):
32 | return content.isEmpty
33 | case .none:
34 | return true
35 | }
36 | }
37 | private var textHeight: CGFloat {
38 | guard let font = font else {
39 | fatalError()
40 | }
41 | let size = (text ?? "").size(with: font, constrainedToWidth: frame.width)
42 | return size.height
43 | }
44 | private func keyboardWillShow(_ notification: Notification) {
45 | let viewPort = establishViewPort(.show, with: notification)
46 | adjust(viewPort)
47 | }
48 | private func keyboardWillHide(_ notification: Notification) {
49 | let viewPort = establishViewPort(.hide, with: notification)
50 | adjust(viewPort)
51 | }
52 | private typealias ViewPort = (height: CGFloat, offset: CGFloat, inset: UIEdgeInsets)
53 | private func establishViewPort(_ state: State, with notification: Notification) -> ViewPort {
54 | let height = self.height(for: state, with: notification)
55 | let offset: CGFloat
56 | var inset = currentInset
57 | switch state {
58 | case .show:
59 | offset = height
60 | inset.bottom = height
61 | case .hide:
62 | offset = -height
63 | inset.bottom = 0
64 | }
65 | return (height, offset, inset)
66 | }
67 | private func adjust(_ viewPort: ViewPort) {
68 | let height = viewPort.height
69 | let offset = viewPort.offset
70 | let inset = viewPort.inset
71 | if textHeight >= height {
72 | let contentOffset = CGPoint(x: self.contentOffset.x, y: self.contentOffset.y + offset)
73 | UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseIn], animations: {
74 | self.contentInset = inset
75 | self.scrollIndicatorInsets = inset
76 | self.contentOffset = contentOffset
77 | }, completion: nil)
78 | } else {
79 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
80 | self?.contentInset = inset
81 | self?.scrollIndicatorInsets = inset
82 | }
83 | }
84 | }
85 | private func height(for state: State, with notification: Notification) -> CGFloat {
86 | guard
87 | let superview = superview,
88 | let window = superview.window,
89 | let font = font,
90 | let userInfo = notification.userInfo else {
91 | fatalError()
92 | }
93 | let keyboardScreenFrame: CGRect
94 | switch state {
95 | case .show:
96 | guard let frame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else {
97 | fatalError()
98 | }
99 | keyboardScreenFrame = frame
100 | case .hide:
101 | guard let frame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else {
102 | fatalError()
103 | }
104 | keyboardScreenFrame = frame
105 | }
106 | let textFrame = window.convert(frame, from: superview)
107 | let delta = UIScreen.main.bounds.maxY - textFrame.maxY
108 | let keyboardViewFrame = superview.convert(keyboardScreenFrame, from: window)
109 | let yInset = abs(delta - keyboardViewFrame.height)
110 | return yInset + font.pointSize
111 | }
112 | }
113 |
114 | private enum State {
115 | case show, hide
116 | }
117 |
118 | private extension String {
119 | func size(with font: UIFont, constrainedToWidth width: CGFloat) -> CGSize {
120 | let attString = NSAttributedString(string: self, attributes: [.font: font])
121 | let framesetter = CTFramesetterCreateWithAttributedString(attString)
122 | let range = CFRange(location: 0, length: 0)
123 | let size = CGSize(width: width, height: .greatestFiniteMagnitude)
124 | return CTFramesetterSuggestFrameSizeWithConstraints(framesetter, range, nil, size, nil)
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/Localizable.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public typealias Localize = String
4 | protocol LocalizeRepresentable: RawRepresentable {
5 | var localized: String { get }
6 | }
7 |
8 |
9 | enum Localizable {
10 | enum Global: Localize, LocalizeRepresentable {
11 | case error = "Global.Error"
12 | case tryAgain = "Global.Error.TryAgain"
13 | case success = "Global.Success"
14 | case failed = "Global.Failed"
15 | case required = "Global.Required"
16 | case optional = "Global.Optional"
17 | case dismiss = "Global.Dismiss"
18 | case cancel = "Global.Cancel"
19 | }
20 |
21 | enum QuickAction: Localize, LocalizeRepresentable {
22 | case newRadar = "QuickAction.NewRadar"
23 | case duplicateRadar = "QuickAction.DuplicateRadar"
24 | }
25 |
26 | enum Login {
27 | enum Error: Localize, LocalizeRepresentable {
28 | case invalidEmail = "LoginError.InvalidEmail"
29 | }
30 | }
31 |
32 | enum Radar: Localize, LocalizeRepresentable {
33 | case product = "Radar.Product"
34 | case area = "Radar.Area"
35 | case version = "Radar.Version"
36 | case classification = "Radar.Classification"
37 | case reproducibility = "Radar.Reproducibility"
38 | case configuration = "Radar.Configuration"
39 | case title = "Radar.Title"
40 | case description = "Radar.Description"
41 | case steps = "Radar.Steps"
42 | case expected = "Radar.Expected"
43 | case actual = "Radar.Actual"
44 | case notes = "Radar.Notes"
45 | case attachament = "Radar.Attachment"
46 | case noAttachaments = "Radar.Attachment.NoAttachments"
47 |
48 | enum Placeholder: Localize, LocalizeRepresentable {
49 | case description = "Radar.Description.Placeholder"
50 | case steps = "Radar.Steps.Placeholder"
51 | case expected = "Radar.Expected.Placeholder"
52 | case actual = "Radar.Actual.Placeholder"
53 | case notes = "Radar.Notes.Placeholder"
54 | }
55 |
56 | enum View {
57 | enum Title: Localize, LocalizeRepresentable {
58 | case duplicate = "RadarView.Title.Duplicate"
59 | case new = "RadarView.Title.New"
60 | }
61 | }
62 |
63 | enum Post: Localize, LocalizeRepresentable {
64 | case success = "Radar.Post.Success"
65 | }
66 |
67 | enum TwoFactor: Localize, LocalizeRepresentable {
68 | case title = "Radar.TwoFactorAuth.Title"
69 | case message = "Radar.TwoFactorAuth.Message"
70 | case submit = "Radar.TwoFactorAuth.Submit"
71 | }
72 | }
73 |
74 | enum Settings {
75 | enum OpenRadar: Localize, LocalizeRepresentable {
76 | case placeholder = "Settings.OpenradarPlaceholder"
77 | case confirm = "Settings.Openradar.Confirm"
78 | case message = "Settings.Openradar.Message"
79 | case clear = "Settings.Openradar.Clear"
80 | }
81 |
82 | enum AppleRadar: Localize, LocalizeRepresentable {
83 | case placeholder = "Settings.AppleRadarPlaceholder"
84 | case confirm = "Settings.AppleRadar.Confirm"
85 | case message = "Settings.AppleRadar.Message"
86 | case logout = "Settings.AppleRadar.Logout"
87 | }
88 | }
89 | }
90 |
91 | extension LocalizeRepresentable where RawValue == Localize {
92 | var localized: String {
93 | return NSLocalizedString(rawValue, comment: "")
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/OpenRadar.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | private let sectionToSetter: [String: (inout OpenRadar, String) -> Void] = [
4 | "actual results": { $0.actual = appendOrReturn($0.actual, $1) },
5 | "area": { $0.areaString = $1 },
6 | "configuration": { $0.configuration = $1 },
7 | "expected results": { $0.expected = $1 },
8 | "notes": { $0.notes = $1 },
9 | "observed results": { $0.actual = appendOrReturn($0.actual, $1) },
10 | "steps to reproduce": { $0.steps = $1 },
11 | "summary": { $0.description = $1 },
12 | "version": { $0.version = $1 }
13 | ]
14 |
15 | #if swift(>=4.0)
16 | // TODO: Change this to use keypaths instead
17 | #endif
18 | public struct OpenRadar {
19 | public var actual: String?
20 | public var areaString: String?
21 | public var configuration: String?
22 | public var description: String?
23 | public var expected: String?
24 | public var notes: String?
25 | public var steps: String?
26 | public var version: String?
27 |
28 | fileprivate init() {}
29 | }
30 |
31 | public extension String {
32 | public func openRadarFromSummary() throws -> OpenRadar {
33 | let components = self.components(separatedBy: "\r\n")
34 | var openRadar = OpenRadar()
35 | var parts = [String]()
36 | var lastSetter: ((inout OpenRadar, String) -> Void)?
37 |
38 | for component in components {
39 | guard component.last == ":", let setter = sectionToSetter[String(component.dropLast()).lowercased()] else {
40 | parts.append(component)
41 | continue
42 | }
43 |
44 | if !parts.isEmpty && lastSetter == nil {
45 | throw OpenRadarParsingError.invalidFormat
46 | }
47 |
48 | lastSetter?(&openRadar, parts.joined(separator: "\r\n").strip())
49 | parts = []
50 | lastSetter = setter
51 | }
52 |
53 | if let setter = lastSetter {
54 | if !parts.isEmpty {
55 | setter(&openRadar, parts.joined(separator: "\r\n").strip())
56 | }
57 | } else {
58 | throw OpenRadarParsingError.invalidFormat
59 | }
60 |
61 | lastSetter = nil
62 | return openRadar
63 | }
64 |
65 | public var isOpenRadar: Bool {
66 | if isEmpty { return false }
67 | var extracted = self
68 | if contains("openradar.appspot.com") || contains("rdar://") {
69 | if let radarNumber = extractRadarNumber() {
70 | extracted = radarNumber
71 | } else {
72 | return false
73 | }
74 | }
75 | let nonDigits = CharacterSet.decimalDigits.inverted
76 | return extracted.rangeOfCharacter(from: nonDigits) == nil
77 | }
78 |
79 | public func extractRadarNumber() -> String? {
80 | if contains("rdar://") {
81 | return components(separatedBy: "/").last ?? nil
82 | }
83 | if contains("openradar.appspot.com") {
84 | let components = URLComponents(string: self)
85 | if let path = components?.path {
86 | // path return /123455
87 | return path.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
88 | }
89 | return nil
90 | }
91 | return self
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/QuickAction.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public enum QuickAction {
4 | enum Radar: String {
5 | case new = "new-radar"
6 | case duplicate = "duplicate-radar"
7 | }
8 | }
9 |
10 | public extension QuickAction {
11 | struct Shortcut {
12 | static var new: UIApplicationShortcutItem {
13 | let icon = UIApplicationShortcutIcon(templateImageName: "Compose")
14 |
15 | return UIApplicationShortcutItem(type: "new-radar",
16 | localizedTitle: Localizable.QuickAction.newRadar.localized,
17 | localizedSubtitle: nil,
18 | icon: icon)
19 | }
20 |
21 | static var duplicate: UIApplicationShortcutItem {
22 | let icon = UIApplicationShortcutIcon(templateImageName: "Duplicate")
23 |
24 | return UIApplicationShortcutItem(type: "duplicate-radar",
25 | localizedTitle: Localizable.QuickAction.duplicateRadar.localized,
26 | localizedSubtitle: nil,
27 | icon: icon)
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/Radar+OpenRadar.swift:
--------------------------------------------------------------------------------
1 | import Sonar
2 |
3 | public enum OpenRadarParsingError: Error {
4 | case noResult
5 | case missingRequiredFields
6 | case invalidFormat
7 | }
8 |
9 | public extension Radar {
10 | public init(openRadar json: [String: Any]) throws {
11 | guard let dictionary = json["result"] as? [String: Any] else {
12 | throw OpenRadarParsingError.noResult
13 | }
14 |
15 | let json = dictionary.onlyStrings().filterEmpty()
16 | guard let title = json["title"], let description = json["description"] else {
17 | throw OpenRadarParsingError.missingRequiredFields
18 | }
19 |
20 | let classificationString = json["classification"]?.lowercased()
21 | let classification = Classification.All.first { $0.name.lowercased() == classificationString }
22 | ?? Classification.All.first!
23 | let productString = json["product"]?.lowercased()
24 | let product = Product.All.first { $0.name.lowercased() == productString } ?? Product.All.first!
25 |
26 | let reproducibilityString = json["reproducible"]?.lowercased()
27 | let reproducibility = Reproducibility.All.first { $0.name.lowercased() == reproducibilityString }
28 | ?? Reproducibility.All.first!
29 |
30 | // Pick the last area (if there are any for the product) instead of defaulting to the first one from
31 | // the UI. Ideally we just wouldn't pick one in this case
32 | let lastArea = Area.areas(for: product).last
33 |
34 | let productVersion = json["product_version"]
35 | let radarID = json["number"]
36 |
37 | do {
38 | let openRadar = try description.openRadarFromSummary()
39 | let version = productVersion ?? openRadar.version ?? " "
40 | let updatedDescription = summary(for: radarID, description: openRadar.description ?? description)
41 | let area = Area.areas(for: product)
42 | .first { $0.name.lowercased() == openRadar.areaString?.lowercased() }
43 |
44 | self.init(classification: classification, product: product, reproducibility: reproducibility,
45 | title: title, description: updatedDescription, steps: openRadar.steps ?? " ",
46 | expected: openRadar.expected ?? " ", actual: openRadar.actual ?? " ",
47 | configuration: openRadar.configuration ?? "", version: version,
48 | notes: openRadar.notes ?? "", attachments: [], area: area ?? lastArea)
49 |
50 | } catch is OpenRadarParsingError {
51 | let updatedDescription = summary(for: radarID, description: description)
52 | let version = productVersion ?? " "
53 |
54 | self.init(classification: classification, product: product, reproducibility: reproducibility,
55 | title: title, description: updatedDescription, steps: " ", expected: " ", actual: " ",
56 | configuration: "", version: version, notes: "", attachments: [], area: lastArea)
57 |
58 | } catch let error {
59 | assertionFailure("Got unexpected error type \(error)")
60 | throw error
61 | }
62 | }
63 | }
64 |
65 | private func summary(for radarID: String?, description: String) -> String {
66 | return radarID.map { "This is a duplicate of radar #\($0)\n\n\(description)\n" } ?? description
67 | }
68 |
--------------------------------------------------------------------------------
/Brisk iOS/Model/String+Extension.swift:
--------------------------------------------------------------------------------
1 | public extension String {
2 | public func strip() -> String {
3 | return self.trimmingCharacters(in: .whitespacesAndNewlines)
4 | }
5 | }
6 |
7 | public func appendOrReturn(_ string1: String?, _ string2: String?) -> String? {
8 | if let string1 = string1, let string2 = string2 {
9 | return string1 + "\n" + string2
10 | }
11 |
12 | return string1 ?? string2
13 | }
14 |
--------------------------------------------------------------------------------
/Brisk iOS/Radar/EnterDetailsViewController.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/Brisk iOS/Radar/EnterDetailsViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import InterfaceBacked
3 |
4 | final class TextView: UITextView, KeyboardObservable {
5 | private var observers: [Any]?
6 | required init?(coder aDecoder: NSCoder) {
7 | super.init(coder: aDecoder)
8 | observers = keyboardObservers()
9 | }
10 | deinit {
11 | removeObservers(observers ?? [])
12 | }
13 | }
14 |
15 | final class EnterDetailsViewController: UIViewController, StoryboardBacked {
16 |
17 | // MARK: - Types
18 |
19 | enum Styling {
20 | case normal, placeholder
21 | }
22 |
23 |
24 | // MARK: - Properties
25 |
26 | @IBOutlet weak var textView: TextView!
27 | @IBOutlet weak var textBottomSpaceConstraint: NSLayoutConstraint!
28 | var prefilledContent = ""
29 | var placeholder = ""
30 | var onDisappear: (String) -> Void = { _ in }
31 |
32 |
33 | // MARK: - UIViewController Methods
34 |
35 | override func viewWillAppear(_ animated: Bool) {
36 | if placeholder.isNotEmpty && prefilledContent.isEmpty {
37 | applyStyling(.placeholder)
38 | textView.text = placeholder
39 | } else {
40 | applyStyling(.normal)
41 | textView.text = prefilledContent
42 | }
43 | }
44 |
45 | override func viewDidAppear(_ animated: Bool) {
46 | textView.becomeFirstResponder()
47 | }
48 |
49 | override func viewWillDisappear(_ animated: Bool) {
50 | if placeholder.isEmpty ||
51 | textView.text != placeholder {
52 | onDisappear(textView.text)
53 | }
54 | }
55 |
56 | // MARK: - Private Methods
57 |
58 | fileprivate func moveCursorToStart() {
59 | DispatchQueue.main.async {
60 | // swiftlint:disable:next legacy_constructor
61 | self.textView.selectedRange = NSMakeRange(0, 0)
62 | }
63 | }
64 |
65 | fileprivate func applyStyling(_ styling: Styling) {
66 | switch styling {
67 | case .normal:
68 | textView.textColor = UIColor.darkText
69 | case .placeholder:
70 | textView.textColor = UIColor.lightGray
71 | }
72 | }
73 | }
74 |
75 |
76 | // MARK: - UITextViewDelegate Methods
77 |
78 | extension EnterDetailsViewController: UITextViewDelegate {
79 |
80 | func textViewDidBeginEditing(_ textView: UITextView) {
81 | if textView.text == placeholder {
82 | moveCursorToStart()
83 | }
84 | }
85 |
86 | func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
87 | let length = textView.text.count + text.count - range.length
88 | if length > 0 {
89 | if textView.text == placeholder {
90 | applyStyling(.normal)
91 | textView.text = ""
92 | }
93 | } else {
94 | applyStyling(.placeholder)
95 | moveCursorToStart()
96 | }
97 | return true
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Brisk iOS/Radar/RadarViewModel.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Sonar
3 |
4 | struct RadarViewModel {
5 | var product: Product = .iOS
6 | var area: Area? = Area.areas(for: .iOS).first
7 | var classification: Classification = .Security
8 | var reproducibility: Reproducibility = .Always
9 | var title: String = ""
10 | var description: String = ""
11 | var steps: String = ""
12 | var expected: String = ""
13 | var actual: String = ""
14 | var configuration: String = ""
15 | var version: String = ""
16 | var notes: String = ""
17 | var attachments: [Attachment] = []
18 | }
19 |
20 |
21 | extension RadarViewModel {
22 |
23 | init(_ radar: Radar) {
24 | product = radar.product
25 | area = radar.area
26 | classification = radar.classification
27 | reproducibility = radar.reproducibility
28 | title = radar.title
29 | description = radar.description
30 | steps = radar.steps
31 | expected = radar.expected
32 | actual = radar.actual
33 | configuration = radar.configuration
34 | version = radar.version
35 | notes = radar.notes
36 | attachments = radar.attachments
37 | }
38 |
39 | func createRadar() -> Radar {
40 | return Radar(
41 | classification: classification,
42 | product: product,
43 | reproducibility: reproducibility,
44 | title: title,
45 | description: description,
46 | steps: steps,
47 | expected: expected,
48 | actual: actual,
49 | configuration: configuration,
50 | version: version,
51 | notes: notes,
52 | attachments: attachments
53 | )
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Brisk iOS/Radar/SingleChoiceTableViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import InterfaceBacked
3 | import Sonar
4 |
5 | final class SingleChoiceViewController: UITableViewController {
6 |
7 |
8 | // MARK: - Properties
9 | var selected: T?
10 | var all: [T]?
11 | var radar: Radar?
12 | var onSelect: (T) -> Void = { _ in }
13 |
14 | // MARK: - UIViewController Methods
15 |
16 | override func viewDidLoad() {
17 | precondition(all != nil, "All choices must be set before the view controller is presented")
18 | precondition(all!.isNotEmpty, "All choices must not be empty")
19 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
20 | tableView.rowHeight = 44
21 | }
22 |
23 | override var preferredContentSize: CGSize {
24 | get {
25 | return CGSize(width: tableView.contentSize.width, height: tableView.rowHeight * CGFloat(tableView.numberOfRows(inSection: 0)))
26 | }
27 | set { super.preferredContentSize = newValue }
28 | }
29 |
30 |
31 | // MARK: - UITableViewController Methods
32 |
33 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
34 | guard let all = self.all else { return 0 }
35 | return all.count
36 | }
37 |
38 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
39 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else { preconditionFailure() }
40 | guard let all = self.all else { preconditionFailure() }
41 |
42 | let choice = all[indexPath.row]
43 | cell.textLabel?.text = choice.name
44 | if let selected = self.selected, choice.name == selected.name {
45 | cell.accessoryType = .checkmark
46 | } else {
47 | cell.accessoryType = .none
48 | }
49 |
50 | return cell
51 | }
52 |
53 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
54 | guard let all = self.all else { return }
55 | let choice = all[indexPath.row]
56 | onSelect(choice)
57 | }
58 |
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/Brisk iOS/Radar/SubmitRadarDelegate.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import Sonar
3 |
4 | final class SubmitRadarDelegate: APIDelegate {
5 |
6 |
7 | // MARK: - Properties
8 |
9 | let display: StatusDisplay
10 | var finishHandler: (_ succes: Bool) -> Void = { _ in }
11 |
12 |
13 | // MARK: - Init/Deinit
14 |
15 | init(_ display: StatusDisplay) {
16 | self.display = display
17 | }
18 |
19 |
20 | // APIDelegate Methods
21 |
22 | func didStartLoading() {
23 | display.showLoading()
24 | }
25 |
26 | func didFail(with error: SonarError) {
27 | display.hideLoading()
28 | display.showError(title: nil, message: error.localizedDescription, dismissButtonTitle: nil, completion: nil)
29 | finishHandler(false)
30 | }
31 |
32 | func didPostToAppleRadar() {
33 | display.hideLoading()
34 | let delay = 3.0
35 | display.showSuccess(message: Localizable.Radar.Post.success.localized, autoDismissAfter: delay)
36 | finishHandler(true)
37 | }
38 |
39 | func didPostToOpenRadar() {
40 | display.hideLoading()
41 | display.showSuccess(message: Localizable.Radar.Post.success.localized, autoDismissAfter: 3.0)
42 | finishHandler(false)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/Brisk iOS/Radar/TwoFactorAuthentication.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | final class TwoFactorAuthentication: TwoFactorAuthenticationHandler {
4 |
5 |
6 | // MARK: - Properties
7 |
8 | let viewController: UIViewController
9 |
10 |
11 | // MARK: - Init/Deinit
12 |
13 | init(viewController: UIViewController) {
14 | self.viewController = viewController
15 | }
16 |
17 | func askForCode(completion: @escaping (String) -> Void) {
18 | let alert = UIAlertController(title: Localizable.Radar.TwoFactor.title.localized, message: Localizable.Radar.TwoFactor.message.localized, preferredStyle: .alert)
19 | alert.addTextField { (field) in
20 | field.keyboardType = .numberPad
21 | let bodyDescriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
22 | field.font = UIFont(descriptor: bodyDescriptor, size: bodyDescriptor.pointSize)
23 | field.autocorrectionType = .no
24 | field.enablesReturnKeyAutomatically = true
25 | }
26 | alert.addAction(UIAlertAction(title: Localizable.Radar.TwoFactor.submit.localized, style: .default, handler: { _ in
27 | guard let field = alert.textFields?.first else { preconditionFailure() }
28 | guard let text = field.text, text.isNotEmpty else {
29 | alert.dismiss(animated: true) {
30 | self.askForCode(completion: completion)
31 | }
32 | return
33 | }
34 | completion(text)
35 | }))
36 | viewController.present(alert, animated: true)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/Brisk iOS/Resources/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 | "size" : "60x60",
35 | "idiom" : "iphone",
36 | "filename" : "icon_60pt@2x-1.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "icon_60pt@3x-1.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "idiom" : "ipad",
47 | "size" : "20x20",
48 | "scale" : "1x"
49 | },
50 | {
51 | "idiom" : "ipad",
52 | "size" : "20x20",
53 | "scale" : "2x"
54 | },
55 | {
56 | "idiom" : "ipad",
57 | "size" : "29x29",
58 | "scale" : "1x"
59 | },
60 | {
61 | "idiom" : "ipad",
62 | "size" : "29x29",
63 | "scale" : "2x"
64 | },
65 | {
66 | "idiom" : "ipad",
67 | "size" : "40x40",
68 | "scale" : "1x"
69 | },
70 | {
71 | "idiom" : "ipad",
72 | "size" : "40x40",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "76x76",
77 | "idiom" : "ipad",
78 | "filename" : "icon_76pt.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "76x76",
83 | "idiom" : "ipad",
84 | "filename" : "icon_76pt@2x.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "83.5x83.5",
89 | "idiom" : "ipad",
90 | "filename" : "icon_83.5@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "1024x1024",
95 | "idiom" : "ios-marketing",
96 | "filename" : "Icon.png",
97 | "scale" : "1x"
98 | }
99 | ],
100 | "info" : {
101 | "version" : 1,
102 | "author" : "xcode"
103 | }
104 | }
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x-1.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x-1.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Compose.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "alert.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Compose.imageset/alert.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/Compose.imageset/alert.pdf
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Duplicate.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "copy.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Duplicate.imageset/copy.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/Duplicate.imageset/copy.pdf
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Logo.imageset/256-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/Logo.imageset/256-1.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Logo.imageset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/Logo.imageset/512.png
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "256-1.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "filename" : "512.png",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Settings.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "slidersvertical.pdf"
6 | }
7 | ],
8 | "info" : {
9 | "version" : 1,
10 | "author" : "xcode"
11 | },
12 | "properties" : {
13 | "template-rendering-intent" : "template"
14 | }
15 | }
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Assets.xcassets/Settings.imageset/slidersvertical.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Brisk iOS/Resources/Assets.xcassets/Settings.imageset/slidersvertical.pdf
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Brisk iOS/Resources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Brisk
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 0.7
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleURLName
25 | com.br1sk.ios
26 | CFBundleURLSchemes
27 |
28 | brisk-rdar
29 |
30 |
31 |
32 | CFBundleVersion
33 | 132
34 | LSRequiresIPhoneOS
35 |
36 | UILaunchStoryboardName
37 | LaunchScreen
38 | UIRequiredDeviceCapabilities
39 |
40 | armv7
41 |
42 | UISupportedInterfaceOrientations
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationLandscapeLeft
46 | UIInterfaceOrientationLandscapeRight
47 | UIInterfaceOrientationPortraitUpsideDown
48 |
49 | UISupportedInterfaceOrientations~ipad
50 |
51 | UIInterfaceOrientationPortrait
52 | UIInterfaceOrientationPortraitUpsideDown
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Brisk iOS/Resources/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 |
2 |
3 | // MARK: - Global
4 |
5 | "Global.Error" = "Error";
6 | "Global.Error.TryAgain" = "Try again";
7 | "Global.Success" = "Yay. Thanks for reporting!";
8 | "Global.Failed" = "Oh noh. That did not work.";
9 | "Global.Required" = "Required";
10 | "Global.Optional" = "Optional";
11 | "Global.Dismiss" = "Dismiss";
12 | "Global.Cancel" = "Cancel";
13 |
14 |
15 | // MARK: - Quick Actions
16 | "QuickAction.NewRadar" = "New Radar";
17 | "QuickAction.DuplicateRadar" = "Duplicate Radar";
18 |
19 |
20 | // MARK: - Login
21 |
22 | "LoginError.InvalidEmail" = "The email you entered is not valid.";
23 |
24 |
25 | // MARK: - Radar
26 |
27 | "RadarView.Title.Duplicate" = "Duplicate";
28 | "RadarView.Title.New" = "New Radar";
29 |
30 | "Radar.Product" = "Product";
31 | "Radar.Area" = "Area";
32 | "Radar.Version" = "Version";
33 | "Radar.Classification" = "Classification";
34 | "Radar.Reproducibility" = "Reproducibility";
35 | "Radar.Configuration" = "Configuration";
36 | "Radar.Title" = "Title";
37 | "Radar.Description" = "Description";
38 | "Radar.Description.Placeholder" = "Provide a detailed summary of your report";
39 | "Radar.Steps" = "Steps";
40 | "Radar.Steps.Placeholder" = "Provide step-by-step instructions of how to reproduce the problem (if possible)";
41 | "Radar.Expected" = "Expected";
42 | "Radar.Expected.Placeholder" = "Describe what you expected to happen after completing the steps above";
43 | "Radar.Actual" = "Actual";
44 | "Radar.Actual.Placeholder" = "Describe what actually happened after completing the steps above";
45 | "Radar.Notes" = "Notes";
46 | "Radar.Notes.Placeholder" = "Provide any additional information, such as references to related problems or workarounds";
47 | "Radar.Attachment" = "Attachment";
48 | "Radar.Attachment.NoAttachments" = "No attachments";
49 | "Radar.Post.Success" = "Done";
50 | "Radar.TwoFactorAuth.Title" = "Apple ID Verification Code";
51 | "Radar.TwoFactorAuth.Message" = "Enter two factor auth code";
52 | "Radar.TwoFactorAuth.Submit" = "Submit";
53 |
54 |
55 | // MARK: - Settings
56 |
57 | "Settings.OpenradarPlaceholder" = "not set";
58 | "Settings.AppleRadarPlaceholder" = "not set";
59 | "Settings.AppleRadar.Confirm" = "Really?";
60 | "Settings.AppleRadar.Message" = "Are you sure you want to logout? You have to enter your AppleID again.";
61 | "Settings.AppleRadar.Logout" = "Yes, log out";
62 | "Settings.Openradar.Confirm" = "Really?";
63 | "Settings.Openradar.Message" = "Are you sure you want to clear the OpenRadar API token?";
64 | "Settings.Openradar.Clear" = "Yes, clear the token";
65 |
--------------------------------------------------------------------------------
/Brisk iOS/Settings/SettingsViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import InterfaceBacked
3 |
4 | protocol SettingsDelegate: class {
5 | func doneTapped()
6 | func clearOpenradarTapped()
7 | func logoutTapped()
8 | func frameworksTapped()
9 | func feedbackTapped()
10 | }
11 |
12 | final class SettingsViewController: UITableViewController, StoryboardBacked {
13 |
14 |
15 | // MARK: - Properties
16 |
17 | let accountSection = 0
18 | let appleRow = 0
19 | let openradarRow = 1
20 | let aboutSection = 1
21 | let thirdPartyRow = 0
22 | let feedbackRow = 1
23 | weak var delegate: SettingsDelegate?
24 | @IBOutlet weak var versionLabel: UILabel!
25 |
26 |
27 | // MARK: - UIViewController Methods
28 |
29 | override func viewDidLoad() {
30 | configureVersionLabel()
31 | }
32 |
33 | // MARK: - User Actions
34 |
35 | @IBAction func doneTapped() {
36 | delegate?.doneTapped()
37 | }
38 |
39 | // MARK: - UITableViewController Methods
40 |
41 | override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
42 | switch indexPath.section {
43 | case accountSection:
44 | switch indexPath.row {
45 | case appleRow:
46 | if let (username, _) = Keychain.get(.radar) {
47 | cell.detailTextLabel?.text = username
48 | cell.detailTextLabel?.textColor = view.tintColor
49 | } else {
50 | cell.detailTextLabel?.text = Localizable.Settings.AppleRadar.placeholder.localized
51 | cell.detailTextLabel?.textColor = UIColor.lightGray
52 | }
53 | case openradarRow:
54 | if let (_, password) = Keychain.get(.openRadar) {
55 | cell.detailTextLabel?.text = password
56 | cell.textLabel?.textColor = UIColor.darkText
57 | cell.detailTextLabel?.textColor = view.tintColor
58 | } else {
59 | cell.detailTextLabel?.text = Localizable.Settings.OpenRadar.placeholder.localized
60 | cell.detailTextLabel?.textColor = UIColor.lightGray
61 | cell.textLabel?.textColor = UIColor.lightGray
62 | }
63 | default: break
64 | }
65 | default: break
66 | }
67 | }
68 |
69 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
70 | switch indexPath.section {
71 | case accountSection:
72 | switch indexPath.row {
73 | case appleRow:
74 | showSheet(title: Localizable.Settings.AppleRadar.confirm.localized,
75 | message: Localizable.Settings.AppleRadar.message.localized,
76 | deleteTitle: Localizable.Settings.AppleRadar.logout.localized,
77 | destructiveAction: {
78 | self.delegate?.logoutTapped()
79 | })
80 | case openradarRow:
81 | showSheet(title: Localizable.Settings.OpenRadar.confirm.localized,
82 | message: Localizable.Settings.OpenRadar.message.localized,
83 | deleteTitle: Localizable.Settings.OpenRadar.clear.localized,
84 | destructiveAction: {
85 | self.delegate?.clearOpenradarTapped()
86 | })
87 | default: break
88 | }
89 | case aboutSection:
90 | switch indexPath.row {
91 | case thirdPartyRow: delegate?.frameworksTapped()
92 | case feedbackRow: delegate?.feedbackTapped()
93 | default: break
94 | }
95 | default: break
96 | }
97 | }
98 |
99 |
100 | // MARK: - Private
101 |
102 | private func showSheet(title: String, message: String, deleteTitle: String, destructiveAction: @escaping () -> Void) {
103 | let sheet = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
104 | sheet.addAction(UIAlertAction(title: deleteTitle, style: .destructive, handler: { _ in
105 | destructiveAction()
106 | if let selected = self.tableView.indexPathForSelectedRow {
107 | self.tableView.deselectRow(at: selected, animated: true)
108 | }
109 | }))
110 | sheet.addAction(UIAlertAction(title: Localizable.Global.cancel.localized, style: .cancel, handler: { _ in
111 | sheet.dismiss(animated: true)
112 | if let selected = self.tableView.indexPathForSelectedRow {
113 | self.tableView.deselectRow(at: selected, animated: true)
114 | }
115 | }))
116 | present(sheet, animated: true, completion: nil)
117 |
118 | if let popover = sheet.popoverPresentationController, let selected = tableView.indexPathForSelectedRow {
119 | popover.sourceView = tableView
120 | popover.sourceRect = tableView.rectForRow(at: selected)
121 | }
122 | }
123 |
124 | private func configureVersionLabel() {
125 | guard let info = Bundle.main.infoDictionary, let versionString = info["CFBundleShortVersionString"], let buildVersionString = info["CFBundleVersion"] else { return }
126 | versionLabel.text = "Version \(versionString) (\(buildVersionString))"
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Brisk iOS/String+Extension.swift:
--------------------------------------------------------------------------------
1 | public extension String {
2 | public func strip() -> String {
3 | return self.trimmingCharacters(in: .whitespacesAndNewlines)
4 | }
5 | }
6 |
7 | public func appendOrReturn(_ string1: String?, _ string2: String?) -> String? {
8 | if let string1 = string1, let string2 = string2 {
9 | return string1 + "\n" + string2
10 | }
11 |
12 | return string1 ?? string2
13 | }
14 |
--------------------------------------------------------------------------------
/Brisk iOSTests/EmailTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Brisk_iOS
3 |
4 | class EmailTests: XCTestCase {
5 |
6 | func testPatternMatch() {
7 | XCTAssertTrue(Email("foo@bar.design").isValid)
8 | XCTAssertFalse(Email("e@.com").isValid)
9 | XCTAssertFalse(Email("e@asdcom").isValid)
10 | XCTAssertFalse(Email("e@com.").isValid)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Brisk iOSTests/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 | BNDL
17 | CFBundleShortVersionString
18 | 0.7
19 | CFBundleVersion
20 | 132
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Brisk iOSTests/RadarNumberEtractionTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Brisk_iOS
3 |
4 | class RadarNumberEtractionTests: XCTestCase {
5 |
6 | func testValidation() {
7 | XCTAssertTrue("1234134513".isOpenRadar)
8 | XCTAssertTrue("rdar://475245".isOpenRadar)
9 | XCTAssertTrue("https://openradar.appspot.com/2348234823".isOpenRadar)
10 | XCTAssertTrue("https://openradar.appspot.com/2348234823#2345".isOpenRadar)
11 |
12 | XCTAssertFalse("rdar://asd234dasf2".isOpenRadar)
13 | XCTAssertFalse("asdf4ewe".isOpenRadar)
14 | }
15 |
16 | func testExtraction() {
17 | XCTAssertEqual("475245".extractRadarNumber()!, "475245")
18 | XCTAssertEqual("rdar://475245".extractRadarNumber(), "475245")
19 | XCTAssertEqual("https://openradar.appspot.com/2348234823#2345".extractRadarNumber()!, "2348234823")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Brisk iOSTests/SubmitRadarTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import Brisk_iOS
3 | @testable import Sonar
4 |
5 | final class MockDisplay: StatusDisplay {
6 | var didShowLoading = false
7 | var didShowError = false
8 | var didHideLoading = false
9 | var didShowSuccess = false
10 | func showLoading() {
11 | didShowLoading = true
12 | }
13 | func showSuccess(message: String, autoDismissAfter delay: TimeInterval) {
14 | didShowSuccess = true
15 | }
16 | func showError(title: String?, message: String, dismissButtonTitle: String?, completion: (() -> Void)?) {
17 | didShowError = true
18 | }
19 | func hideLoading() {
20 | didHideLoading = true
21 | }
22 | }
23 |
24 | final class SubmitRadarTests: XCTestCase {
25 |
26 | func testShowLoading() {
27 | let display = MockDisplay()
28 | let sut = SubmitRadarDelegate(display)
29 | sut.didStartLoading()
30 | XCTAssertTrue(display.didShowLoading)
31 | }
32 |
33 | func testShowError() {
34 | let expectation = XCTestExpectation(description: "Wait for finish to be called")
35 | let display = MockDisplay()
36 | let sut = SubmitRadarDelegate(display)
37 | sut.finishHandler = { _ in
38 | expectation.fulfill()
39 | }
40 | sut.didFail(with: SonarError.unknownError)
41 | XCTAssertTrue(display.didHideLoading)
42 | XCTAssertTrue(display.didShowError)
43 | wait(for: [expectation], timeout: 1.0)
44 | }
45 |
46 | func testShowSuccessWhenOpenradarFinished() {
47 | let expectation = XCTestExpectation(description: "Wait for finish to be called")
48 | let display = MockDisplay()
49 | let sut = SubmitRadarDelegate(display)
50 | sut.finishHandler = { _ in
51 | expectation.fulfill()
52 | }
53 | sut.didPostToOpenRadar()
54 | XCTAssertTrue(display.didHideLoading)
55 | XCTAssertTrue(display.didShowSuccess)
56 | wait(for: [expectation], timeout: 1.0)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Brisk iOSTests/TokenTests.swift:
--------------------------------------------------------------------------------
1 | @testable import Brisk_iOS
2 | import XCTest
3 |
4 | class TokenTests: XCTestCase {
5 |
6 | func testPatternMatch() {
7 | XCTAssertTrue(Token("800f690a-5f76-11e7-a49d-cfaf62a5e748").isValid)
8 | XCTAssertFalse(Token("8090a-5f76-11e7-a49d-cfaf625e748").isValid)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.7
2 |
3 | **New**
4 |
5 | - Build by CI
6 | - Registered in App Store connect in preparation for App Store release
7 |
8 | **Fixed**
9 |
10 | - Fixed crash when area was missing, thanks Ethan! (#34)
11 | - Fixed tests, thanks Ethan! (#33)
12 |
13 | **Known Issues**
14 |
15 | **Notes**
16 |
17 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source "https://rubygems.org"
4 |
5 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6 |
7 | gem "fastlane"
8 | gem "cocoapods"
9 | gem "xcode-install"
10 |
--------------------------------------------------------------------------------
/License:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017 Florian Bürger (https://www.florianbuerger.com)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | platform :ios, '11.0'
3 |
4 | target 'Brisk iOS' do
5 | use_frameworks!
6 | inhibit_all_warnings!
7 |
8 | pod 'InterfaceBacked'
9 | pod 'Sonar', :git => 'https://github.com/br1sk/Sonar'
10 | pod 'SVProgressHUD'
11 | pod 'AcknowList'
12 | pod 'SwiftLint', '~> 0.25.1'
13 |
14 | target 'Brisk iOSTests' do
15 | inherit! :search_paths
16 | # Pods for testing
17 | end
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AcknowList (1.6.1)
3 | - Alamofire (4.7.2)
4 | - InterfaceBacked (2.0.1)
5 | - Sonar (0.0.1):
6 | - Alamofire (~> 4.7.2)
7 | - SVProgressHUD (2.2.5)
8 | - SwiftLint (0.25.1)
9 |
10 | DEPENDENCIES:
11 | - AcknowList
12 | - InterfaceBacked
13 | - Sonar (from `https://github.com/br1sk/Sonar`)
14 | - SVProgressHUD
15 | - SwiftLint (~> 0.25.1)
16 |
17 | SPEC REPOS:
18 | https://github.com/cocoapods/specs.git:
19 | - AcknowList
20 | - Alamofire
21 | - InterfaceBacked
22 | - SVProgressHUD
23 | - SwiftLint
24 |
25 | EXTERNAL SOURCES:
26 | Sonar:
27 | :git: https://github.com/br1sk/Sonar
28 |
29 | CHECKOUT OPTIONS:
30 | Sonar:
31 | :commit: d2e88a07a96b864ada65520381e2aa58d529f84e
32 | :git: https://github.com/br1sk/Sonar
33 |
34 | SPEC CHECKSUMS:
35 | AcknowList: c9091be8dd75c95662b56f75e382487a0cbb9a27
36 | Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223
37 | InterfaceBacked: 3db1dfb52c702bf640656a22c56df5a36ea542b4
38 | Sonar: 96b87f9048b557dfbcd331c5d3598b8f92724d0d
39 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
40 | SwiftLint: ce933681be10c3266e82576dad676fa815a602e9
41 |
42 | PODFILE CHECKSUM: c386a44e7d1e39e253a2c3ce2f97999f149909a8
43 |
44 | COCOAPODS: 1.5.3
45 |
--------------------------------------------------------------------------------
/Pods/AcknowList/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015-2018 Vincent Tourraine (http://www.vtourraine.net)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/Pods/AcknowList/README.md:
--------------------------------------------------------------------------------
1 | # AcknowList
2 |
3 | _Ready to use “Acknowledgements”/“Licenses”/“Credits” view controller for [CocoaPods](http://cocoapods.org/)._
4 |
5 | _Written in Swift 4 (for Objective-C, you can use [VTAcknowledgementsViewController](https://github.com/vtourraine/VTAcknowledgementsViewController))._
6 |
7 | 
8 | 
9 | [](https://travis-ci.org/vtourraine/AcknowList)
10 | [](https://cocoapods.org/pods/AcknowList)
11 | [](https://github.com/vtourraine/AcknowList/raw/master/LICENSE)
12 |
13 |
14 |
15 |
16 | ## How to Install
17 |
18 | This project is only useful if you use CocoaPods, so let’s assume that you’re indeed using CocoaPods.
19 |
20 | 1. Add `pod 'AcknowList'` in your `Podfile`.
21 | 2. Import the `Pods-acknowledgements.plist` file from the generated `Pods/Target Support Files` folder to your main app project (so you need to run `pod install` at least once before using this pod; don’t copy the file itself, just add a reference).
22 | This file is generated at `Pods/Target Support Files/Pods-{project}/Pods-{project}-acknowledgements.plist`.
23 |
24 |
25 | ## How to Use
26 |
27 | The `AcknowListViewController` instance is usually pushed to an existing `UINavigationController`.
28 |
29 | ``` swift
30 | let viewController = AcknowListViewController()
31 | navigationController?.pushViewController(viewController, animated: true)
32 | ```
33 |
34 |
35 | ## Customization
36 |
37 | If your `.plist` file is named something other than `Pods-acknowledgements.plist` (_e.g._ if you’re using custom build targets), you can initialize the view controller with a custom path.
38 |
39 | ``` swift
40 | let viewController = AcknowListViewController(fileNamed: "Pods-AcknowExample-acknowledgements")
41 | ```
42 |
43 | ``` swift
44 | let path = Bundle.main.path(forResource: "Pods-AcknowExample-acknowledgements", ofType: "plist")
45 | let viewController = AcknowListViewController(acknowledgementsPlistPath: path)
46 | ```
47 |
48 | ## Apple TV
49 |
50 | AcknowList is also compatible with tvOS for Apple TV apps.
51 |
52 |
53 |
54 |
55 | ## Requirements
56 |
57 | AcknowList is written in Swift 4, requires iOS 8.0 or tvOS 9.0 and above, Xcode 9.0 and above.
58 |
59 |
60 | ## Credits
61 |
62 | AcknowList was created by [Vincent Tourraine](http://www.vtourraine.net), and improved by a growing [list of contributors](https://github.com/vtourraine/AcknowList/contributors).
63 |
64 |
65 | ## License
66 |
67 | AcknowList is available under the MIT license. See the `LICENSE.txt` file for more info.
68 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/da.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Tak til";
2 | "VTAckGeneratedByCocoaPods" = "Genereret af CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/de.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Danksagungen";
2 | "VTAckGeneratedByCocoaPods" = "Generiert mit CocoaPods";
3 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Acknowledgements";
2 | "VTAckGeneratedByCocoaPods" = "Generated by CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/es.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Agradecimientos";
2 | "VTAckGeneratedByCocoaPods" = "Generado por CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/fr.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Remerciements";
2 | "VTAckGeneratedByCocoaPods" = "Généré par CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/it.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Ringraziamenti";
2 | "VTAckGeneratedByCocoaPods" = "Generato tramite CocoaPods";
3 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/ja.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "謝辞";
2 | "VTAckGeneratedByCocoaPods" = "Generated by CocoaPods";
3 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/nl.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Verantwoordingen";
2 | "VTAckGeneratedByCocoaPods" = "Gegenereerd door CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/pt-BR.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Agradecimentos";
2 | "VTAckGeneratedByCocoaPods" = "Gerado por CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/pt-PT.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Agradecimentos";
2 | "VTAckGeneratedByCocoaPods" = "Gerado por CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/sv.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "Erkännanden";
2 | "VTAckGeneratedByCocoaPods" = "Genererad av CocoaPods";
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/zh-Hans.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "致谢";
2 | "VTAckGeneratedByCocoaPods" = "使用 CocoaPods 生成";
3 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Resources/AcknowList.bundle/zh-Hant.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | "VTAckAcknowledgements" = "致謝";
2 | "VTAckGeneratedByCocoaPods" = "使用 CocoaPods 管理套件";
3 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Source/Acknow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Acknow.swift
3 | //
4 | // Copyright (c) 2015-2018 Vincent Tourraine (http://www.vtourraine.net)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 |
25 | /// Represents a single acknowledgement.
26 | public struct Acknow {
27 |
28 | /// The acknowledgement title (for instance: the pod’s name).
29 | public let title: String
30 |
31 | /// The acknowledgement body text (for instance: the pod’s license).
32 | public let text: String
33 |
34 | /// The acknowledgement license (for instance the pod’s license type).
35 | public let license: String?
36 |
37 | /// Returns an object initialized from the given parameters.
38 | ///
39 | /// - Parameters:
40 | /// - title: The acknowledgement title (for instance: the pod’s name).
41 | /// - text: The acknowledgement body text (for instance: the pod’s license).
42 | /// - license: The acknowledgement license (for instance the pod’s license type).
43 | public init(title: String, text: String, license: String? = nil) {
44 | self.title = title
45 | self.text = text
46 | self.license = license
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Source/AcknowLocalization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AcknowLocalization.swift
3 | //
4 | // Copyright (c) 2015-2018 Vincent Tourraine (http://www.vtourraine.net)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | import Foundation
25 |
26 | /// Manages the localization for the main acknowledgements labels and strings.
27 | open class AcknowLocalization {
28 |
29 | /**
30 | The localized version of “Acknowledgements”.
31 | You can use this value for the button presenting the `AcknowListViewController`, for instance.
32 |
33 | - returns: The localized title.
34 | */
35 | open class func localizedTitle() -> String {
36 | return self.localizedString(forKey: "VTAckAcknowledgements", defaultString: "Acknowledgements")
37 | }
38 |
39 | /**
40 | Returns the user’s preferred language.
41 |
42 | - returns: The preferred language ID.
43 | */
44 | class func preferredLanguageCode() -> String? {
45 | return Locale.preferredLanguages.first
46 | }
47 |
48 | /**
49 | Returns a localized string.
50 |
51 | - parameter key: The key for the string to localize.
52 | - parameter defaultString: The default non-localized string.
53 |
54 | - returns: The localized string.
55 | */
56 | class func localizedString(forKey key: String, defaultString: String) -> String {
57 | var bundlePath = Bundle(for: AcknowListViewController.self).path(forResource: "AcknowList", ofType: "bundle")
58 | let languageBundle: Bundle
59 |
60 | if let currentBundlePath = bundlePath {
61 | let bundle = Bundle(path: currentBundlePath)
62 | var language = "en"
63 |
64 | if let firstLanguage = self.preferredLanguageCode() {
65 | language = firstLanguage
66 | }
67 |
68 | if let bundle = bundle {
69 | let localizations = bundle.localizations
70 | if localizations.contains(language) == false {
71 | language = language.components(separatedBy: "-").first!
72 | }
73 |
74 | if localizations.contains(language) {
75 | bundlePath = bundle.path(forResource: language, ofType: "lproj")
76 | }
77 | }
78 | }
79 |
80 | if let bundlePath = bundlePath {
81 | let bundleWithPath = Bundle(path: bundlePath)
82 | if let bundleWithPath = bundleWithPath {
83 | languageBundle = bundleWithPath
84 | }
85 | else {
86 | languageBundle = Bundle.main
87 | }
88 | }
89 | else {
90 | languageBundle = Bundle.main
91 | }
92 |
93 | let localizedDefaultString = languageBundle.localizedString(forKey: key, value:defaultString, table:nil)
94 | return Bundle.main.localizedString(forKey: key, value:localizedDefaultString, table:nil)
95 | }
96 |
97 | /**
98 | Returns the URL for the CocoaPods main website.
99 |
100 | - returns: The CocoaPods website URL.
101 | */
102 | class func CocoaPodsURLString() -> String {
103 | return "https://cocoapods.org"
104 | }
105 |
106 | /**
107 | Returns the default localized footer text.
108 |
109 | - returns: The localized footer text.
110 | */
111 | class func localizedCocoaPodsFooterText() -> String {
112 | return
113 | self.localizedString(forKey: "VTAckGeneratedByCocoaPods", defaultString: "Generated by CocoaPods")
114 | + "\n"
115 | + self.CocoaPodsURLString()
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Source/AcknowParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AcknowParser.swift
3 | //
4 | // Copyright (c) 2015-2018 Vincent Tourraine (http://www.vtourraine.net)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | import Foundation
25 |
26 | /// Responsible for parsing a CocoaPods acknowledgements plist file.
27 | open class AcknowParser {
28 |
29 | /// The root dictionary from the loaded plist file.
30 | let rootDictionary: [String: AnyObject]
31 |
32 | /**
33 | Initializes the `AcknowParser` instance with a plist path.
34 |
35 | - parameter plistPath: The path to the acknowledgements plist file.
36 |
37 | - returns: The new `AcknowParser` instance.
38 | */
39 | public init(plistPath: String) {
40 | let root = NSDictionary(contentsOfFile: plistPath)
41 | if let root = root, root is [String: AnyObject] {
42 | self.rootDictionary = root as! [String: AnyObject]
43 | }
44 | else {
45 | self.rootDictionary = Dictionary()
46 | }
47 | }
48 |
49 | /**
50 | Parses the header and footer values.
51 |
52 | - return: a tuple with the header and footer values.
53 | */
54 | open func parseHeaderAndFooter() -> (header: String?, footer: String?) {
55 | let preferenceSpecifiers: AnyObject? = self.rootDictionary["PreferenceSpecifiers"]
56 |
57 | if let preferenceSpecifiers = preferenceSpecifiers, preferenceSpecifiers is [AnyObject] {
58 | let preferenceSpecifiersArray = preferenceSpecifiers as! [AnyObject]
59 | if let headerItem = preferenceSpecifiersArray.first,
60 | let footerItem = preferenceSpecifiersArray.last,
61 | let headerText = headerItem["FooterText"], headerItem is [String: String],
62 | let footerText = footerItem["FooterText"], footerItem is [String: String] {
63 | return (headerText as! String?, footerText as! String?)
64 | }
65 | }
66 |
67 | return (nil, nil)
68 | }
69 |
70 | /**
71 | Parses the array of acknowledgements.
72 |
73 | - return: an array of `Acknow` instances.
74 | */
75 | open func parseAcknowledgements() -> [Acknow] {
76 | let preferenceSpecifiers: AnyObject? = self.rootDictionary["PreferenceSpecifiers"]
77 |
78 | if let preferenceSpecifiers = preferenceSpecifiers, preferenceSpecifiers is [AnyObject] {
79 | let preferenceSpecifiersArray = preferenceSpecifiers as! [AnyObject]
80 |
81 | // Remove the header and footer
82 | let ackPreferenceSpecifiers = preferenceSpecifiersArray.filter({ (object: AnyObject) -> Bool in
83 | if let firstObject = preferenceSpecifiersArray.first,
84 | let lastObject = preferenceSpecifiersArray.last {
85 | return (object.isEqual(firstObject) == false && object.isEqual(lastObject) == false)
86 | }
87 | return true
88 | })
89 |
90 | let acknowledgements = ackPreferenceSpecifiers.map({
91 | (preferenceSpecifier: AnyObject) -> Acknow in
92 | if let title = preferenceSpecifier["Title"] as! String?,
93 | let text = preferenceSpecifier["FooterText"] as! String? {
94 | return Acknow(title: title, text: text, license: preferenceSpecifier["License"] as? String)
95 | }
96 | else {
97 | return Acknow(title: "", text: "", license: nil)
98 | }
99 | })
100 |
101 | return acknowledgements
102 | }
103 |
104 | return []
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/Pods/AcknowList/Source/AcknowViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AcknowViewController.swift
3 | //
4 | // Copyright (c) 2015-2018 Vincent Tourraine (http://www.vtourraine.net)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | import UIKit
25 |
26 | /// Subclass of `UIViewController` that displays a single acknowledgement.
27 | open class AcknowViewController: UIViewController {
28 |
29 | /// The main text view.
30 | open var textView: UITextView?
31 |
32 | /// The represented acknowledgement.
33 | var acknowledgement: Acknow?
34 |
35 | /**
36 | Initializes the `AcknowViewController` instance with an acknowledgement.
37 |
38 | - parameter acknowledgement: The represented acknowledgement.
39 |
40 | - returns: The new `AcknowViewController` instance.
41 | */
42 | public init(acknowledgement: Acknow) {
43 | super.init(nibName: nil, bundle: nil)
44 |
45 | self.title = acknowledgement.title
46 | self.acknowledgement = acknowledgement
47 | }
48 |
49 | /**
50 | Initializes the `AcknowViewController` instance with a coder.
51 |
52 | - parameter aDecoder: The archive coder.
53 |
54 | - returns: The new `AcknowViewController` instance.
55 | */
56 | required public init?(coder aDecoder: NSCoder) {
57 | super.init(coder: aDecoder)
58 | self.acknowledgement = Acknow(title: "", text: "", license: nil)
59 | }
60 |
61 | // MARK: - View lifecycle
62 |
63 | let TopBottomDefaultMargin: CGFloat = 20
64 | let LeftRightDefaultMargin: CGFloat = 10
65 |
66 | /// Called after the controller's view is loaded into memory.
67 | open override func viewDidLoad() {
68 | super.viewDidLoad()
69 |
70 | let textView = UITextView(frame: view.bounds)
71 | textView.alwaysBounceVertical = true
72 | textView.font = UIFont.preferredFont(forTextStyle: .body)
73 | textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
74 | #if os(iOS)
75 | textView.isEditable = false
76 | textView.dataDetectorTypes = .link
77 |
78 | view.backgroundColor = UIColor.white
79 | #endif
80 | textView.textContainerInset = UIEdgeInsetsMake(TopBottomDefaultMargin, LeftRightDefaultMargin, TopBottomDefaultMargin, LeftRightDefaultMargin)
81 | view.addSubview(textView)
82 |
83 | self.textView = textView
84 | }
85 |
86 | /// Called to notify the view controller that its view has just laid out its subviews.
87 | open override func viewDidLayoutSubviews() {
88 | super.viewDidLayoutSubviews()
89 |
90 | if let textView = textView {
91 | updateTextViewInsets(textView)
92 | }
93 |
94 | // Need to set the textView text after the layout is completed, so that the content inset and offset properties can be adjusted automatically.
95 | if let acknowledgement = self.acknowledgement {
96 | textView?.text = acknowledgement.text
97 | }
98 | }
99 |
100 | @available(iOS 11.0, tvOS 11.0, *) open override func viewLayoutMarginsDidChange() {
101 | super.viewLayoutMarginsDidChange()
102 |
103 | if let textView = textView {
104 | updateTextViewInsets(textView)
105 | }
106 | }
107 |
108 | func updateTextViewInsets(_ textView: UITextView) {
109 | textView.textContainerInset = UIEdgeInsetsMake(TopBottomDefaultMargin, self.view.layoutMargins.left, TopBottomDefaultMargin, self.view.layoutMargins.right);
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Pods/Alamofire/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Pods/Alamofire/Source/DispatchQueue+Alamofire.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DispatchQueue+Alamofire.swift
3 | //
4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Dispatch
26 | import Foundation
27 |
28 | extension DispatchQueue {
29 | static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) }
30 | static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) }
31 | static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) }
32 | static var background: DispatchQueue { return DispatchQueue.global(qos: .background) }
33 |
34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) {
35 | asyncAfter(deadline: .now() + delay, execute: closure)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Pods/Alamofire/Source/Notifications.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Notifications.swift
3 | //
4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 | //
24 |
25 | import Foundation
26 |
27 | extension Notification.Name {
28 | /// Used as a namespace for all `URLSessionTask` related notifications.
29 | public struct Task {
30 | /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`.
31 | public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")
32 |
33 | /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`.
34 | public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")
35 |
36 | /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`.
37 | public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")
38 |
39 | /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`.
40 | public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
41 | }
42 | }
43 |
44 | // MARK: -
45 |
46 | extension Notification {
47 | /// Used as a namespace for all `Notification` user info dictionary keys.
48 | public struct Key {
49 | /// User info dictionary key representing the `URLSessionTask` associated with the notification.
50 | public static let Task = "org.alamofire.notification.key.task"
51 |
52 | /// User info dictionary key representing the responseData associated with the notification.
53 | public static let ResponseData = "org.alamofire.notification.key.responseData"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Pods/InterfaceBacked/InterfaceBacked/NibBacked.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public protocol NibBacked: class {
4 |
5 | static func newFromNib() -> Self
6 |
7 | static func newFromNib(withName name:String) -> Self
8 |
9 | static func newFromNib(withName name:String, bundle:Bundle) -> Self
10 |
11 | static func newFromNib(withName name:String, bundle:Bundle, owner: AnyObject?, options: [NSObject : AnyObject]?) -> Self
12 | }
13 |
14 | public extension NibBacked {
15 |
16 | static func newFromNib() -> Self {
17 | return newFromNib(withName: String(describing: Self.self), bundle: Bundle(for: Self.self))
18 | }
19 |
20 | static func newFromNib(withName name:String) -> Self {
21 | return newFromNib(withName: name, bundle: Bundle(for: Self.self))
22 | }
23 |
24 | static func newFromNib(withName name:String, bundle:Bundle) -> Self {
25 | return newFromNib(withName: name, bundle: bundle, owner: nil, options: nil)
26 | }
27 |
28 | static func newFromNib(withName name: String, bundle: Bundle, owner: AnyObject?, options: [NSObject : AnyObject]?) -> Self {
29 | let nib = UINib(nibName: name, bundle: bundle)
30 | let view = nib.instantiate(withOwner: owner, options: options).first as! Self
31 | return view
32 | }
33 | }
34 |
35 | public protocol NibBackedCell: class {
36 |
37 | static var cellNib: UINib { get }
38 |
39 | static func cellNib(inBundle bundle: Bundle) -> UINib
40 |
41 | static var identifier: String { get }
42 | }
43 |
44 | public extension NibBackedCell {
45 |
46 | static var cellNib: UINib {
47 | let nib = UINib(nibName: String(describing: self), bundle: Bundle(for: Self.self))
48 | return nib
49 | }
50 |
51 | static func cellNib(inBundle bundle: Bundle = Bundle(for: Self.self)) -> UINib {
52 | let nib = UINib(nibName: String(describing: self), bundle: bundle)
53 | return nib
54 | }
55 |
56 | static var identifier: String {
57 | return String(describing: self)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Pods/InterfaceBacked/InterfaceBacked/StoryboardBacked.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public protocol StoryboardBacked:class {
4 |
5 | static func newFromStoryboard() -> Self
6 |
7 | static func newFromStoryboard(withName name:String) -> Self
8 |
9 | static func newFromStoryboard(withName name:String, bundle:Bundle) -> Self
10 | }
11 |
12 | public extension StoryboardBacked {
13 |
14 | static func newFromStoryboard() -> Self {
15 | return newFromStoryboard(withName: String(describing: Self.self))
16 | }
17 |
18 | static func newFromStoryboard(withName name: String) -> Self {
19 | return newFromStoryboard(withName: name, bundle: Bundle(for: Self.self))
20 | }
21 |
22 | static func newFromStoryboard(withName name: String, bundle: Bundle) -> Self {
23 | let storyboard = UIStoryboard(name: name, bundle: bundle)
24 | let controller = storyboard.instantiateInitialViewController()! as! Self
25 | return controller
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Pods/InterfaceBacked/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016–2017 Florian Bürger, https://www.florianbuerger.com
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Pods/InterfaceBacked/Readme.md:
--------------------------------------------------------------------------------
1 | # Interface Backed
2 |
3 | [](https://github.com/Carthage/Carthage)
5 |
6 | Original idea and implementation by [Benjamin Sandofsky's
7 | gist](https://gist.github.com/sandofsky/0a8b5977afb16af1c6083fe97f0ac867)
8 |
9 | I just simplified the code a bit and put it in a Framework. Added a similar
10 | approach for `UIView` subclasses that rely on a `.nib` file.
11 |
12 | Released under the [MIT license](LICENSE)
13 |
14 | ## Usage
15 |
16 | Your classes must be declared `final` to adopt the protocol.
17 |
18 | ### UIViewController
19 |
20 | ```
21 | final class ViewController: UIViewController, StoryboardBacked {}
22 |
23 | let vc = ViewController.newFromStoryboard()
24 | ```
25 |
26 | You can use a custom name or a custom bundle. The function defaults to
27 | a storyboard named as the view controller and to the bundle the class is in. So
28 | this works even when your view controller class in inside a framework and not
29 | the main bundle.
30 |
31 | *Hint*: Double check that you made your custom view controller the initial view
32 | controller for the storyboard.
33 |
34 | ### UITableViewCell
35 |
36 | ```
37 | final class Cell: UITableViewCell, NibBackedCell {}
38 |
39 | tableView.registerNib(Cell.cellNib(), forCellReuseIdentifier: Cell.identifier())
40 | ```
41 |
42 | *Hint*: Double check the identifier for the cell in the `.nib` file. It must
43 | reflect the name of the class.
44 |
45 | For more information check the included Example target.
46 |
--------------------------------------------------------------------------------
/Pods/Local Podspecs/Sonar.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Sonar",
3 | "version": "0.0.1",
4 | "summary": "Apple's radar communication in Swift",
5 | "homepage": "https://github.com/br1sk/Sonar",
6 | "license": "MIT",
7 | "authors": {
8 | "Martin Conte Mac Donell": "reflejo@gmail.com"
9 | },
10 | "platforms": {
11 | "ios": "9.0",
12 | "osx": "10.11"
13 | },
14 | "source": {
15 | "git": "https://github.com/br1sk/Sonar.git"
16 | },
17 | "source_files": "Sources/Sonar/**/*.swift",
18 | "requires_arc": true,
19 | "dependencies": {
20 | "Alamofire": [
21 | "~> 4.7.2"
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Pods/Manifest.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - AcknowList (1.6.1)
3 | - Alamofire (4.7.2)
4 | - InterfaceBacked (2.0.1)
5 | - Sonar (0.0.1):
6 | - Alamofire (~> 4.7.2)
7 | - SVProgressHUD (2.2.5)
8 | - SwiftLint (0.25.1)
9 |
10 | DEPENDENCIES:
11 | - AcknowList
12 | - InterfaceBacked
13 | - Sonar (from `https://github.com/br1sk/Sonar`)
14 | - SVProgressHUD
15 | - SwiftLint (~> 0.25.1)
16 |
17 | SPEC REPOS:
18 | https://github.com/cocoapods/specs.git:
19 | - AcknowList
20 | - Alamofire
21 | - InterfaceBacked
22 | - SVProgressHUD
23 | - SwiftLint
24 |
25 | EXTERNAL SOURCES:
26 | Sonar:
27 | :git: https://github.com/br1sk/Sonar
28 |
29 | CHECKOUT OPTIONS:
30 | Sonar:
31 | :commit: d2e88a07a96b864ada65520381e2aa58d529f84e
32 | :git: https://github.com/br1sk/Sonar
33 |
34 | SPEC CHECKSUMS:
35 | AcknowList: c9091be8dd75c95662b56f75e382487a0cbb9a27
36 | Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223
37 | InterfaceBacked: 3db1dfb52c702bf640656a22c56df5a36ea542b4
38 | Sonar: 96b87f9048b557dfbcd331c5d3598b8f92724d0d
39 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
40 | SwiftLint: ce933681be10c3266e82576dad676fa815a602e9
41 |
42 | PODFILE CHECKSUM: c386a44e7d1e39e253a2c3ce2f97999f149909a8
43 |
44 | COCOAPODS: 1.5.3
45 |
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2011-2018 Sam Vermette, Tobias Tiemerding and contributors.
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 |
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVIndefiniteAnimatedView.h:
--------------------------------------------------------------------------------
1 | //
2 | // SVIndefiniteAnimatedView.h
3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD
4 | //
5 | // Copyright (c) 2014-2018 Guillaume Campagna. All rights reserved.
6 | //
7 |
8 | #import
9 |
10 | @interface SVIndefiniteAnimatedView : UIView
11 |
12 | @property (nonatomic, assign) CGFloat strokeThickness;
13 | @property (nonatomic, assign) CGFloat radius;
14 | @property (nonatomic, strong) UIColor *strokeColor;
15 |
16 | @end
17 |
18 |
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVIndefiniteAnimatedView.m:
--------------------------------------------------------------------------------
1 | //
2 | // SVIndefiniteAnimatedView.m
3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD
4 | //
5 | // Copyright (c) 2014-2018 Guillaume Campagna. All rights reserved.
6 | //
7 |
8 | #import "SVIndefiniteAnimatedView.h"
9 | #import "SVProgressHUD.h"
10 |
11 | @interface SVIndefiniteAnimatedView ()
12 |
13 | @property (nonatomic, strong) CAShapeLayer *indefiniteAnimatedLayer;
14 |
15 | @end
16 |
17 | @implementation SVIndefiniteAnimatedView
18 |
19 | - (void)willMoveToSuperview:(UIView*)newSuperview {
20 | if (newSuperview) {
21 | [self layoutAnimatedLayer];
22 | } else {
23 | [_indefiniteAnimatedLayer removeFromSuperlayer];
24 | _indefiniteAnimatedLayer = nil;
25 | }
26 | }
27 |
28 | - (void)layoutAnimatedLayer {
29 | CALayer *layer = self.indefiniteAnimatedLayer;
30 | [self.layer addSublayer:layer];
31 |
32 | CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds);
33 | CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds);
34 | layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2);
35 | }
36 |
37 | - (CAShapeLayer*)indefiniteAnimatedLayer {
38 | if(!_indefiniteAnimatedLayer) {
39 | CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5);
40 | UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat) (M_PI*3/2) endAngle:(CGFloat) (M_PI/2+M_PI*5) clockwise:YES];
41 |
42 | _indefiniteAnimatedLayer = [CAShapeLayer layer];
43 | _indefiniteAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale];
44 | _indefiniteAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2);
45 | _indefiniteAnimatedLayer.fillColor = [UIColor clearColor].CGColor;
46 | _indefiniteAnimatedLayer.strokeColor = self.strokeColor.CGColor;
47 | _indefiniteAnimatedLayer.lineWidth = self.strokeThickness;
48 | _indefiniteAnimatedLayer.lineCap = kCALineCapRound;
49 | _indefiniteAnimatedLayer.lineJoin = kCALineJoinBevel;
50 | _indefiniteAnimatedLayer.path = smoothedPath.CGPath;
51 |
52 | CALayer *maskLayer = [CALayer layer];
53 |
54 | NSBundle *bundle = [NSBundle bundleForClass:[SVProgressHUD class]];
55 | NSURL *url = [bundle URLForResource:@"SVProgressHUD" withExtension:@"bundle"];
56 | NSBundle *imageBundle = [NSBundle bundleWithURL:url];
57 |
58 | NSString *path = [imageBundle pathForResource:@"angle-mask" ofType:@"png"];
59 |
60 | maskLayer.contents = (__bridge id)[[UIImage imageWithContentsOfFile:path] CGImage];
61 | maskLayer.frame = _indefiniteAnimatedLayer.bounds;
62 | _indefiniteAnimatedLayer.mask = maskLayer;
63 |
64 | NSTimeInterval animationDuration = 1;
65 | CAMediaTimingFunction *linearCurve = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
66 |
67 | CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
68 | animation.fromValue = (id) 0;
69 | animation.toValue = @(M_PI*2);
70 | animation.duration = animationDuration;
71 | animation.timingFunction = linearCurve;
72 | animation.removedOnCompletion = NO;
73 | animation.repeatCount = INFINITY;
74 | animation.fillMode = kCAFillModeForwards;
75 | animation.autoreverses = NO;
76 | [_indefiniteAnimatedLayer.mask addAnimation:animation forKey:@"rotate"];
77 |
78 | CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
79 | animationGroup.duration = animationDuration;
80 | animationGroup.repeatCount = INFINITY;
81 | animationGroup.removedOnCompletion = NO;
82 | animationGroup.timingFunction = linearCurve;
83 |
84 | CABasicAnimation *strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
85 | strokeStartAnimation.fromValue = @0.015;
86 | strokeStartAnimation.toValue = @0.515;
87 |
88 | CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
89 | strokeEndAnimation.fromValue = @0.485;
90 | strokeEndAnimation.toValue = @0.985;
91 |
92 | animationGroup.animations = @[strokeStartAnimation, strokeEndAnimation];
93 | [_indefiniteAnimatedLayer addAnimation:animationGroup forKey:@"progress"];
94 |
95 | }
96 | return _indefiniteAnimatedLayer;
97 | }
98 |
99 | - (void)setFrame:(CGRect)frame {
100 | if(!CGRectEqualToRect(frame, super.frame)) {
101 | [super setFrame:frame];
102 |
103 | if(self.superview) {
104 | [self layoutAnimatedLayer];
105 | }
106 | }
107 |
108 | }
109 |
110 | - (void)setRadius:(CGFloat)radius {
111 | if(radius != _radius) {
112 | _radius = radius;
113 |
114 | [_indefiniteAnimatedLayer removeFromSuperlayer];
115 | _indefiniteAnimatedLayer = nil;
116 |
117 | if(self.superview) {
118 | [self layoutAnimatedLayer];
119 | }
120 | }
121 | }
122 |
123 | - (void)setStrokeColor:(UIColor*)strokeColor {
124 | _strokeColor = strokeColor;
125 | _indefiniteAnimatedLayer.strokeColor = strokeColor.CGColor;
126 | }
127 |
128 | - (void)setStrokeThickness:(CGFloat)strokeThickness {
129 | _strokeThickness = strokeThickness;
130 | _indefiniteAnimatedLayer.lineWidth = _strokeThickness;
131 | }
132 |
133 | - (CGSize)sizeThatFits:(CGSize)size {
134 | return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2);
135 | }
136 |
137 | @end
138 |
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressAnimatedView.h:
--------------------------------------------------------------------------------
1 | //
2 | // SVProgressAnimatedView.h
3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD
4 | //
5 | // Copyright (c) 2017-2018 Tobias Tiemerding. All rights reserved.
6 | //
7 |
8 | #import
9 |
10 | @interface SVProgressAnimatedView : UIView
11 |
12 | @property (nonatomic, assign) CGFloat radius;
13 | @property (nonatomic, assign) CGFloat strokeThickness;
14 | @property (nonatomic, strong) UIColor *strokeColor;
15 | @property (nonatomic, assign) CGFloat strokeEnd;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressAnimatedView.m:
--------------------------------------------------------------------------------
1 | //
2 | // SVProgressAnimatedView.m
3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD
4 | //
5 | // Copyright (c) 2017-2018 Tobias Tiemerding. All rights reserved.
6 | //
7 |
8 | #import "SVProgressAnimatedView.h"
9 |
10 | @interface SVProgressAnimatedView ()
11 |
12 | @property (nonatomic, strong) CAShapeLayer *ringAnimatedLayer;
13 |
14 | @end
15 |
16 | @implementation SVProgressAnimatedView
17 |
18 | - (void)willMoveToSuperview:(UIView*)newSuperview {
19 | if (newSuperview) {
20 | [self layoutAnimatedLayer];
21 | } else {
22 | [_ringAnimatedLayer removeFromSuperlayer];
23 | _ringAnimatedLayer = nil;
24 | }
25 | }
26 |
27 | - (void)layoutAnimatedLayer {
28 | CALayer *layer = self.ringAnimatedLayer;
29 | [self.layer addSublayer:layer];
30 |
31 | CGFloat widthDiff = CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds);
32 | CGFloat heightDiff = CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds);
33 | layer.position = CGPointMake(CGRectGetWidth(self.bounds) - CGRectGetWidth(layer.bounds) / 2 - widthDiff / 2, CGRectGetHeight(self.bounds) - CGRectGetHeight(layer.bounds) / 2 - heightDiff / 2);
34 | }
35 |
36 | - (CAShapeLayer*)ringAnimatedLayer {
37 | if(!_ringAnimatedLayer) {
38 | CGPoint arcCenter = CGPointMake(self.radius+self.strokeThickness/2+5, self.radius+self.strokeThickness/2+5);
39 | UIBezierPath* smoothedPath = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:self.radius startAngle:(CGFloat)-M_PI_2 endAngle:(CGFloat) (M_PI + M_PI_2) clockwise:YES];
40 |
41 | _ringAnimatedLayer = [CAShapeLayer layer];
42 | _ringAnimatedLayer.contentsScale = [[UIScreen mainScreen] scale];
43 | _ringAnimatedLayer.frame = CGRectMake(0.0f, 0.0f, arcCenter.x*2, arcCenter.y*2);
44 | _ringAnimatedLayer.fillColor = [UIColor clearColor].CGColor;
45 | _ringAnimatedLayer.strokeColor = self.strokeColor.CGColor;
46 | _ringAnimatedLayer.lineWidth = self.strokeThickness;
47 | _ringAnimatedLayer.lineCap = kCALineCapRound;
48 | _ringAnimatedLayer.lineJoin = kCALineJoinBevel;
49 | _ringAnimatedLayer.path = smoothedPath.CGPath;
50 | }
51 | return _ringAnimatedLayer;
52 | }
53 |
54 | - (void)setFrame:(CGRect)frame {
55 | if(!CGRectEqualToRect(frame, super.frame)) {
56 | [super setFrame:frame];
57 |
58 | if(self.superview) {
59 | [self layoutAnimatedLayer];
60 | }
61 | }
62 | }
63 |
64 | - (void)setRadius:(CGFloat)radius {
65 | if(radius != _radius) {
66 | _radius = radius;
67 |
68 | [_ringAnimatedLayer removeFromSuperlayer];
69 | _ringAnimatedLayer = nil;
70 |
71 | if(self.superview) {
72 | [self layoutAnimatedLayer];
73 | }
74 | }
75 | }
76 |
77 | - (void)setStrokeColor:(UIColor*)strokeColor {
78 | _strokeColor = strokeColor;
79 | _ringAnimatedLayer.strokeColor = strokeColor.CGColor;
80 | }
81 |
82 | - (void)setStrokeThickness:(CGFloat)strokeThickness {
83 | _strokeThickness = strokeThickness;
84 | _ringAnimatedLayer.lineWidth = _strokeThickness;
85 | }
86 |
87 | - (void)setStrokeEnd:(CGFloat)strokeEnd {
88 | _strokeEnd = strokeEnd;
89 | _ringAnimatedLayer.strokeEnd = _strokeEnd;
90 | }
91 |
92 | - (CGSize)sizeThatFits:(CGSize)size {
93 | return CGSizeMake((self.radius+self.strokeThickness/2+5)*2, (self.radius+self.strokeThickness/2+5)*2);
94 | }
95 |
96 | @end
97 |
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/angle-mask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/angle-mask.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/angle-mask@2x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/angle-mask@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/angle-mask@3x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/error.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/error@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/error@2x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/error@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/error@3x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/info.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/info@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/info@2x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/info@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/info@3x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/success.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/success@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/success@2x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/success@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SVProgressHUD/SVProgressHUD/SVProgressHUD.bundle/success@3x.png
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVRadialGradientLayer.h:
--------------------------------------------------------------------------------
1 | //
2 | // SVRadialGradientLayer.h
3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD
4 | //
5 | // Copyright (c) 2014-2018 Tobias Tiemerding. All rights reserved.
6 | //
7 |
8 | #import
9 |
10 | @interface SVRadialGradientLayer : CALayer
11 |
12 | @property (nonatomic) CGPoint gradientCenter;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/Pods/SVProgressHUD/SVProgressHUD/SVRadialGradientLayer.m:
--------------------------------------------------------------------------------
1 | //
2 | // SVRadialGradientLayer.m
3 | // SVProgressHUD, https://github.com/SVProgressHUD/SVProgressHUD
4 | //
5 | // Copyright (c) 2014-2018 Tobias Tiemerding. All rights reserved.
6 | //
7 |
8 | #import "SVRadialGradientLayer.h"
9 |
10 | @implementation SVRadialGradientLayer
11 |
12 | - (void)drawInContext:(CGContextRef)context {
13 | size_t locationsCount = 2;
14 | CGFloat locations[2] = {0.0f, 1.0f};
15 | CGFloat colors[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.75f};
16 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
17 | CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colors, locations, locationsCount);
18 | CGColorSpaceRelease(colorSpace);
19 |
20 | float radius = MIN(self.bounds.size.width , self.bounds.size.height);
21 | CGContextDrawRadialGradient (context, gradient, self.gradientCenter, 0, self.gradientCenter, radius, kCGGradientDrawsAfterEndLocation);
22 | CGGradientRelease(gradient);
23 | }
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/Pods/Sonar/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Keith Smiley (http://keith.so)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the 'Software'), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software is furnished to do so,
8 | subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 |
--------------------------------------------------------------------------------
/Pods/Sonar/README.md:
--------------------------------------------------------------------------------
1 | # Sonar
2 |
3 | An interface to create radars on [Apple's bug tracker](https://radar.apple.com)
4 | and [Open Radar](https://openradar.appspot.com/) frictionless from swift.
5 |
6 | ## Example
7 |
8 | ### Login
9 |
10 | ```swift
11 | let openRadar = Sonar(service: .openRadar(token: "abcdefg"))
12 | openRadar.login(
13 | getTwoFactorCode: { _ in fatalError("OpenRadar doesn't support 2 factor" })
14 | { result in
15 | guard case let .success = result else {
16 | return
17 | }
18 |
19 | print("Logged in!")
20 | }
21 | ```
22 |
23 | ### Create radar
24 |
25 | ```swift
26 | let radar = Radar(
27 | classification: .feature, product: .bugReporter, reproducibility: .always,
28 | title: "Add REST API to Radar", description: "Add REST API to Radar", steps: "N/A",
29 | expected: "Radar to have a REST API available", actual: "API not provided",
30 | configuration: "N/A", version: "Any", notes: "N/A", attachments: []
31 | )
32 |
33 | let openRadar = Sonar(service: .openRadar(token: "abcdefg"))
34 | openRadar.create(radar: radar) { result in
35 | // Check to see if the request succeeded
36 | }
37 | ```
38 |
39 | ### Login and Create radar on the same call
40 |
41 | ```swift
42 | let radar = Radar(
43 | classification: .feature, product: .bugReporter, reproducibility: .always,
44 | title: "Add REST API to Radar", description: "Add REST API to Radar", steps: "N/A",
45 | expected: "Radar to have a REST API available", actual: "API not provided",
46 | configuration: "N/A", version: "Any", notes: "N/A", attachments: []
47 | )
48 |
49 | let appleRadar = Sonar(service: .appleRadar(appleID: "a", password: "b"))
50 | appleRadar.loginThenCreate(
51 | radar: radar,
52 | getTwoFactorCode: { closure in
53 | let code = // Somehow get 2 factor auth code for user
54 | closure(code)
55 | })
56 | { result in
57 | switch result {
58 | case .success(let value):
59 | print(value) // This is the radar ID!
60 | case .failure(let error):
61 | print(error)
62 | }
63 | }
64 | ```
65 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/APIs/BugTracker.swift:
--------------------------------------------------------------------------------
1 | import Alamofire
2 |
3 | /// The service authentication method.
4 | ///
5 | /// - appleRadar: Apple's radar system. Authenticated by appleID / password.
6 | /// - openRadar: Open radar system. Authenticated by a non expiring token.
7 | public enum ServiceAuthentication {
8 | case appleRadar(appleID: String, password: String)
9 | case openRadar(token: String)
10 | }
11 |
12 | /// This protocol represents a bug tracker (such as Apple's radar or Open Radar).
13 | protocol BugTracker {
14 | /// Login into bug tracker. This method will use the authentication information provided by the service
15 | /// enum.
16 | ///
17 | /// - parameter getTwoFactorCode: A closure to retrieve a two factor auth code from the user.
18 | /// - parameter closure: A closure that will be called when the login is completed,
19 | /// on failure a `SonarError`.
20 | func login(getTwoFactorCode: @escaping (_ closure: @escaping (_ code: String?) -> Void) -> Void,
21 | closure: @escaping (Result) -> Void)
22 |
23 | /// Creates a new ticket into the bug tracker (needs authentication first).
24 | ///
25 | /// - parameter radar: The radar model with the information for the ticket.
26 | /// - parameter closure: A closure that will be called when the login is completed, on success it will
27 | /// contain a radar ID; on failure a `SonarError`.
28 | func create(radar: Radar, closure: @escaping (Result) -> Void)
29 | }
30 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/APIs/OpenRadar.swift:
--------------------------------------------------------------------------------
1 | import Alamofire
2 | import Foundation
3 |
4 | class OpenRadar: BugTracker {
5 | private let manager: Alamofire.SessionManager
6 |
7 | init(token: String) {
8 | let configuration = URLSessionConfiguration.default
9 | configuration.httpAdditionalHeaders = ["Authorization": token]
10 |
11 | self.manager = Alamofire.SessionManager(configuration: configuration)
12 | }
13 |
14 | /// Login into open radar. This is actually a NOP for now (token is saved into the session).
15 | ///
16 | /// - parameter getTwoFactorCode: A closure to retrieve a two factor auth code from the user
17 | /// - parameter closure: A closure that will be called when the login is completed, on success it
18 | /// will contain a list of `Product`s; on failure a `SonarError`.
19 | func login(getTwoFactorCode: @escaping (_ closure: @escaping (_ code: String?) -> Void) -> Void,
20 | closure: @escaping (Result) -> Void)
21 | {
22 | closure(.success(()))
23 | }
24 |
25 | /// Creates a new ticket into open radar (needs authentication first).
26 | ///
27 | /// - parameter radar: The radar model with the information for the ticket.
28 | /// - parameter closure: A closure that will be called when the login is completed, on success it will
29 | /// contain a radar ID; on failure a `SonarError`.
30 | func create(radar: Radar, closure: @escaping (Result) -> Void) {
31 | guard let ID = radar.ID else {
32 | closure(.failure(SonarError(message: "Invalid radar ID")))
33 | return
34 | }
35 |
36 | self.manager
37 | .request(OpenRadarRouter.create(radar: radar))
38 | .validate()
39 | .responseJSON { response in
40 | guard case .success = response.result else {
41 | closure(.failure(SonarError.from(response)))
42 | return
43 | }
44 |
45 | closure(.success(ID))
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/APIs/OpenRadarRouter.swift:
--------------------------------------------------------------------------------
1 | import Alamofire
2 | import Foundation
3 |
4 | /// Open radar request router.
5 | ///
6 | /// - create: The `Route` used to create a new radar.
7 | enum OpenRadarRouter {
8 | case create(radar: Radar)
9 |
10 | fileprivate static let baseURL = URL(string: "https://openradar.appspot.com")!
11 |
12 | /// The request components including headers and parameters.
13 | var components: (path: String, method: Alamofire.HTTPMethod, parameters: [String: String]) {
14 | switch self {
15 | case .create(let radar):
16 | let formatter = DateFormatter()
17 | formatter.dateFormat = "dd-MMM-yyyy hh:mm a"
18 | formatter.locale = Locale(identifier: "en_US_POSIX")
19 |
20 | return (path: "/api/radars/add", method: .post, parameters: [
21 | "classification": radar.classification.name,
22 | "description": radar.body,
23 | "number": radar.ID.map { String($0) } ?? "",
24 | "originated": formatter.string(from: Date()),
25 | "product": radar.product.name,
26 | "product_version": radar.version,
27 | "reproducible": radar.reproducibility.name,
28 | "status": "Open",
29 | "title": radar.title,
30 | ])
31 | }
32 | }
33 | }
34 |
35 | extension OpenRadarRouter: URLRequestConvertible {
36 | /// The URL that will be used for the request.
37 | var url: URL {
38 | return self.urlRequest!.url!
39 | }
40 |
41 | /// The request representation of the route including parameters and HTTP method.
42 | func asURLRequest() -> URLRequest {
43 | let (path, method, parameters) = self.components
44 | let url = OpenRadarRouter.baseURL.appendingPathComponent(path)
45 |
46 | var request = URLRequest(url: url)
47 | request.httpMethod = method.rawValue
48 | return try! URLEncoding().encode(request, with: parameters)
49 | }
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/APIs/Result.swift:
--------------------------------------------------------------------------------
1 | public enum Result {
2 | case success(Value)
3 | case failure(Error)
4 | }
5 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/APIs/SonarError.swift:
--------------------------------------------------------------------------------
1 | import Alamofire
2 | import Foundation
3 |
4 | /// Represents an error on the communication and/or response parsing.
5 | public struct SonarError: Error {
6 | /// The message that represents the error condition.
7 | public let message: String
8 |
9 | /// Used to catch the things we can't gracefully fail.
10 | static let unknownError = SonarError(message: "Unknown error")
11 |
12 | /// Used when the token expires.
13 | static let authenticationError = SonarError(message: "Unauthorized, perhaps you have the wrong password")
14 |
15 | init(message: String) {
16 | self.message = message
17 | }
18 |
19 | /// Factory to create a `SonarError` based on a `Response`.
20 | ///
21 | /// - parameter response: The HTTP resposne that is known to be failed.
22 | ///
23 | /// - returns: The error representing the problem.
24 | static func from(_ response: DataResponse) -> SonarError {
25 | if response.response?.statusCode == 401 {
26 | return .authenticationError
27 | }
28 |
29 | switch response.result {
30 | case .failure(let error as NSError):
31 | return SonarError(message: error.localizedDescription)
32 | default:
33 | return .unknownError
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Extensions/String+Regex.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension String {
4 | /// Returns the string matching in the given group number (if any), nil otherwise.
5 | ///
6 | /// - parameter pattern: The regular expression to match.
7 | /// - parameter group: The group number to return from the match.
8 | /// - parameter options: Options that will be use for matching using regular expressions.
9 | ///
10 | /// - returns: The string from the given group on the match (if any).
11 | func match(pattern: String, group: Int, options: NSRegularExpression.Options = []) -> String? {
12 | do {
13 | let regex = try NSRegularExpression(pattern: pattern, options: options)
14 | let range = NSRange(location: 0, length: self.count)
15 |
16 | guard let match = regex.firstMatch(in: self, options: [], range: range) else {
17 | return nil
18 | }
19 |
20 | if match.numberOfRanges < group {
21 | return nil
22 | }
23 |
24 | let matchRange = match.range(at: group)
25 | let startRange = self.index(self.startIndex, offsetBy: matchRange.location)
26 | let endRange = self.index(startRange, offsetBy: matchRange.length)
27 |
28 | return self[startRange ..< endRange].trimmingCharacters(in: .whitespacesAndNewlines)
29 | } catch {
30 | return nil
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Models/Area.swift:
--------------------------------------------------------------------------------
1 | public struct Area: Equatable {
2 | /// Internal apple's identifier
3 | public let appleIdentifier: Int
4 |
5 | /// The name of the area; useful to use as display name (e.g. 'App Switcher').
6 | public let name: String
7 |
8 | public init(appleIdentifier: Int, name: String) {
9 | self.appleIdentifier = appleIdentifier
10 | self.name = name
11 | }
12 |
13 | public static func == (lhs: Area, rhs: Area) -> Bool {
14 | return lhs.appleIdentifier == rhs.appleIdentifier && lhs.name == rhs.name
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Models/Attachment.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if os(OSX)
3 | import CoreServices
4 | #else
5 | import MobileCoreServices
6 | #endif
7 |
8 | public enum AttachmentError: Error {
9 | // Error thrown with the MIME type for the attachment is not found
10 | case invalidMimeType(fileExtension: String)
11 | }
12 |
13 | public struct Attachment: Equatable {
14 | // The filename of the attachment, used for display on the web
15 | public let filename: String
16 | // The MIME type of the attachmenth
17 | public let mimeType: String
18 | // The data from the attachment, read at the time of attachment
19 | public let data: Data
20 |
21 | // The size of the attachment (based on the data)
22 | public var size: Int {
23 | return self.data.count
24 | }
25 |
26 | public init(url: URL) throws {
27 | self.init(filename: url.lastPathComponent, mimeType: try getMimeType(for: url.pathExtension),
28 | data: try Data(contentsOf: url))
29 | }
30 |
31 | public init(filename: String, mimeType: String, data: Data) {
32 | self.filename = filename
33 | self.mimeType = mimeType
34 | self.data = data
35 | }
36 | }
37 |
38 | public func == (lhs: Attachment, rhs: Attachment) -> Bool {
39 | return lhs.filename == rhs.filename && lhs.size == rhs.size && lhs.data == rhs.data
40 | }
41 |
42 | private func getMimeType(for fileExtension: String) throws -> String {
43 | if let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
44 | fileExtension as CFString, nil),
45 | let mimeType = UTTypeCopyPreferredTagWithClass(identifier.takeRetainedValue(), kUTTagClassMIMEType)
46 | {
47 | return mimeType.takeRetainedValue() as String
48 | }
49 |
50 | throw AttachmentError.invalidMimeType(fileExtension: fileExtension)
51 | }
52 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Models/Classification.swift:
--------------------------------------------------------------------------------
1 | public struct Classification: Equatable {
2 | /// Internal apple's identifier
3 | public let appleIdentifier: Int
4 |
5 | /// The name of the classification; useful to use as display name (e.g. 'UI/Usability').
6 | public let name: String
7 |
8 | public static func == (lhs: Classification, rhs: Classification) -> Bool {
9 | return lhs.appleIdentifier == rhs.appleIdentifier && lhs.name == rhs.name
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Models/Product.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct Product: Equatable {
4 | /// Internal apple's identifier
5 | public let appleIdentifier: Int
6 | /// The category of the product (e.g. 'Hardware').
7 | public let category: String
8 | /// The name of the product; useful to use as display name (e.g. 'iOS').
9 | public let name: String
10 |
11 | public static func == (lhs: Product, rhs: Product) -> Bool {
12 | return lhs.appleIdentifier == rhs.appleIdentifier
13 | && lhs.category == rhs.category
14 | && lhs.name == rhs.name
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Models/Radar.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | private let kProductiOSID = 1
4 | private let kProductiTunesConnectID = 12
5 |
6 | public struct Radar {
7 | // The unique identifier on apple's radar platform.
8 | public var ID: Int?
9 | // The type of problem.
10 | public let classification: Classification
11 | // The product related to the report.
12 | public let product: Product
13 | // How often the problem occurs.
14 | public let reproducibility: Reproducibility
15 | // A short but descriptive sentence that summarizes the issue.
16 | public let title: String
17 | // A detailed description about the issue and include specific details to help the engineering
18 | // team understand the problem.
19 | public let description: String
20 | // The step by step process to reproduce the issue.
21 | public let steps: String
22 | // What you expected to see.
23 | public let expected: String
24 | // What you actually saw.
25 | public let actual: String
26 | // The circumstances where this does or does not occur.
27 | public let configuration: String
28 | // Product version and build number.
29 | public let version: String
30 | // Any other relevant notes not previously mentioned
31 | public let notes: String
32 |
33 | // The area in which the problem occurs (only applies for product=iOS).
34 | public let area: Area?
35 | // The identifier for your app located as part of General Information section (iTunes Connect only)
36 | public let applicationID: String?
37 | // Email address or username of the user experiencing the issue (iTunes Connect only)
38 | public let userID: String?
39 | // The attachments to send with the radar
40 | public let attachments: [Attachment]
41 |
42 | public init(classification: Classification, product: Product, reproducibility: Reproducibility,
43 | title: String, description: String, steps: String, expected: String, actual: String,
44 | configuration: String, version: String, notes: String, attachments: [Attachment],
45 | area: Area? = nil, applicationID: String? = nil, userID: String? = nil, ID: Int? = nil)
46 | {
47 | assert(area == nil || Area.areas(for: product).contains(where: { $0 == area! }),
48 | "The area passed must be be part of the product's areas")
49 |
50 | self.ID = ID
51 | self.classification = classification
52 | self.product = product
53 | self.reproducibility = reproducibility
54 | self.title = title
55 | self.description = description
56 | self.steps = steps
57 | self.expected = expected
58 | self.actual = actual
59 | self.configuration = configuration
60 | self.version = version
61 | self.notes = notes
62 | self.area = area
63 | self.applicationID = applicationID
64 | self.userID = userID
65 | self.attachments = attachments
66 | }
67 | }
68 |
69 | // MARK: - Body
70 |
71 | extension Radar {
72 |
73 | /// The composed body string using many of the components from the Radar model.
74 | var body: String {
75 | let baseTemplate = [
76 | ("Summary", self.description),
77 | ("Steps to Reproduce", self.steps),
78 | ("Expected Results", self.expected),
79 | ("Actual Results", self.actual),
80 | ("Version", self.version),
81 | ("Notes", self.notes),
82 | ]
83 |
84 | let templates = [
85 | kProductiTunesConnectID: [
86 | ("Apple ID of the App", self.applicationID ?? ""),
87 | ("Apple ID of the User", self.userID ?? "")
88 | ],
89 | kProductiOSID: [
90 | ("Area", self.area?.name ?? ""),
91 | ],
92 | ]
93 |
94 | let values = baseTemplate + (templates[self.product.appleIdentifier] ?? [])
95 | let body = values
96 | .map { "\($0):\r\n\($1)" }
97 | .joined(separator: "\r\n\r\n")
98 | return body + "\r\n\r\n"
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Models/Reproducibility.swift:
--------------------------------------------------------------------------------
1 | public struct Reproducibility: Equatable {
2 | /// Internal apple's identifier
3 | public let appleIdentifier: Int
4 |
5 | /// The name of the reproducibility; useful to use as display name (e.g. 'Sometimes').
6 | public let name: String
7 |
8 | public static func == (lhs: Reproducibility, rhs: Reproducibility) -> Bool {
9 | return lhs.appleIdentifier == rhs.appleIdentifier && lhs.name == rhs.name
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Pods/Sonar/Sources/Sonar/Sonar.swift:
--------------------------------------------------------------------------------
1 | import Alamofire
2 | import Foundation
3 |
4 | public class Sonar {
5 | private let tracker: BugTracker
6 |
7 | public init(service: ServiceAuthentication) {
8 | switch service {
9 | case .appleRadar(let appleID, let password):
10 | self.tracker = AppleRadar(appleID: appleID, password: password)
11 |
12 | case .openRadar(let token):
13 | self.tracker = OpenRadar(token: token)
14 | }
15 | }
16 |
17 | /// Login into bug tracker. This method will use the authentication information provided by the service enum.
18 | ///
19 | /// - parameter getTwoFactorCode: A closure to retrieve a two factor auth code from the user.
20 | /// - parameter closure: A closure that will be called when the login is completed,
21 | /// on failure a `SonarError`.
22 | public func login(getTwoFactorCode: @escaping (_ closure: @escaping (_ code: String?) -> Void) -> Void,
23 | closure: @escaping (Result) -> Void)
24 | {
25 | self.tracker.login(getTwoFactorCode: getTwoFactorCode) { result in
26 | closure(result)
27 | self.hold()
28 | }
29 | }
30 |
31 | /// Creates a new ticket into the bug tracker (needs authentication first).
32 | ///
33 | /// - parameter radar: The radar model with the information for the ticket.
34 | /// - parameter closure: A closure that will be called when the login is completed, on success it will
35 | /// contain a radar ID; on failure a `SonarError`.
36 | public func create(radar: Radar, closure: @escaping (Result) -> Void) {
37 | self.tracker.create(radar: radar) { result in
38 | closure(result)
39 | self.hold()
40 | }
41 | }
42 |
43 | /// Similar to `create` but logs the user in first.
44 | ///
45 | /// - parameter radar: The radar model with the information for the ticket.
46 | /// - parameter getTwoFactorCode: A closure to retrieve a two factor auth code from the user.
47 | /// - parameter closure: A closure that will be called when the login is completed, on success it
48 | /// will contain a radar ID; on failure a `SonarError`.
49 | public func loginThenCreate(
50 | radar: Radar, getTwoFactorCode: @escaping (_ closure: @escaping (_ code: String?) -> Void) -> Void,
51 | closure: @escaping (Result) -> Void)
52 | {
53 | self.tracker.login(getTwoFactorCode: getTwoFactorCode) { result in
54 | if case let .failure(error) = result {
55 | closure(.failure(error))
56 | return
57 | }
58 |
59 | self.create(radar: radar, closure: closure)
60 | }
61 | }
62 |
63 | private func hold() {}
64 | }
65 |
--------------------------------------------------------------------------------
/Pods/SwiftLint/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Realm Inc.
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 |
--------------------------------------------------------------------------------
/Pods/SwiftLint/swiftlint:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Pods/SwiftLint/swiftlint
--------------------------------------------------------------------------------
/Pods/Target Support Files/AcknowList/AcknowList-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_AcknowList : NSObject
3 | @end
4 | @implementation PodsDummy_AcknowList
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/AcknowList/AcknowList-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/AcknowList/AcknowList-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double AcknowListVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char AcknowListVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/AcknowList/AcknowList.modulemap:
--------------------------------------------------------------------------------
1 | framework module AcknowList {
2 | umbrella header "AcknowList-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/AcknowList/AcknowList.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/AcknowList
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_LDFLAGS = -framework "UIKit"
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/AcknowList
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/AcknowList/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.6.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Alamofire/Alamofire-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Alamofire : NSObject
3 | @end
4 | @implementation PodsDummy_Alamofire
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Alamofire/Alamofire-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Alamofire/Alamofire-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double AlamofireVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Alamofire/Alamofire.modulemap:
--------------------------------------------------------------------------------
1 | framework module Alamofire {
2 | umbrella header "Alamofire-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Alamofire/Alamofire.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire
8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
9 | SKIP_INSTALL = YES
10 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Alamofire/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 4.7.2
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/InterfaceBacked/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 2.0.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/InterfaceBacked/InterfaceBacked-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_InterfaceBacked : NSObject
3 | @end
4 | @implementation PodsDummy_InterfaceBacked
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/InterfaceBacked/InterfaceBacked-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/InterfaceBacked/InterfaceBacked-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double InterfaceBackedVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char InterfaceBackedVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/InterfaceBacked/InterfaceBacked.modulemap:
--------------------------------------------------------------------------------
1 | framework module InterfaceBacked {
2 | umbrella header "InterfaceBacked-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/InterfaceBacked/InterfaceBacked.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_LDFLAGS = -framework "UIKit"
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/InterfaceBacked
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOS/Pods-Brisk iOS-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_Brisk_iOS : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_Brisk_iOS
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOS/Pods-Brisk iOS-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_Brisk_iOSVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_Brisk_iOSVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOS/Pods-Brisk iOS.debug.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/Sonar"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList/AcknowList.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked/InterfaceBacked.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Sonar/Sonar.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "AcknowList" -framework "Alamofire" -framework "InterfaceBacked" -framework "SVProgressHUD" -framework "Sonar"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOS/Pods-Brisk iOS.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_Brisk_iOS {
2 | umbrella header "Pods-Brisk iOS-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOS/Pods-Brisk iOS.release.xcconfig:
--------------------------------------------------------------------------------
1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/Sonar"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList/AcknowList.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked/InterfaceBacked.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Sonar/Sonar.framework/Headers"
6 | OTHER_LDFLAGS = $(inherited) -framework "AcknowList" -framework "Alamofire" -framework "InterfaceBacked" -framework "SVProgressHUD" -framework "Sonar"
7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
8 | PODS_BUILD_DIR = ${BUILD_DIR}
9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
11 | PODS_ROOT = ${SRCROOT}/Pods
12 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Pods-Brisk iOSTests-acknowledgements.markdown:
--------------------------------------------------------------------------------
1 | # Acknowledgements
2 | This application makes use of the following third party libraries:
3 | Generated by CocoaPods - https://cocoapods.org
4 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Pods-Brisk iOSTests-acknowledgements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreferenceSpecifiers
6 |
7 |
8 | FooterText
9 | This application makes use of the following third party libraries:
10 | Title
11 | Acknowledgements
12 | Type
13 | PSGroupSpecifier
14 |
15 |
16 | FooterText
17 | Generated by CocoaPods - https://cocoapods.org
18 | Title
19 |
20 | Type
21 | PSGroupSpecifier
22 |
23 |
24 | StringsTable
25 | Acknowledgements
26 | Title
27 | Acknowledgements
28 |
29 |
30 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Pods-Brisk iOSTests-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Pods_Brisk_iOSTests : NSObject
3 | @end
4 | @implementation PodsDummy_Pods_Brisk_iOSTests
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Pods-Brisk iOSTests-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double Pods_Brisk_iOSTestsVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char Pods_Brisk_iOSTestsVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Pods-Brisk iOSTests.debug.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/Sonar"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList/AcknowList.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked/InterfaceBacked.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Sonar/Sonar.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
8 | PODS_ROOT = ${SRCROOT}/Pods
9 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Pods-Brisk iOSTests.modulemap:
--------------------------------------------------------------------------------
1 | framework module Pods_Brisk_iOSTests {
2 | umbrella header "Pods-Brisk iOSTests-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Pods-Brisk iOSTests/Pods-Brisk iOSTests.release.xcconfig:
--------------------------------------------------------------------------------
1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList" "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked" "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD" "${PODS_CONFIGURATION_BUILD_DIR}/Sonar"
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/AcknowList/AcknowList.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/InterfaceBacked/InterfaceBacked.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD/SVProgressHUD.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Sonar/Sonar.framework/Headers"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
8 | PODS_ROOT = ${SRCROOT}/Pods
9 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SVProgressHUD/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 2.2.5
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SVProgressHUD/SVProgressHUD-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_SVProgressHUD : NSObject
3 | @end
4 | @implementation PodsDummy_SVProgressHUD
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SVProgressHUD/SVProgressHUD-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SVProgressHUD/SVProgressHUD-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 | #import "SVIndefiniteAnimatedView.h"
14 | #import "SVProgressAnimatedView.h"
15 | #import "SVProgressHUD.h"
16 | #import "SVRadialGradientLayer.h"
17 |
18 | FOUNDATION_EXPORT double SVProgressHUDVersionNumber;
19 | FOUNDATION_EXPORT const unsigned char SVProgressHUDVersionString[];
20 |
21 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SVProgressHUD/SVProgressHUD.modulemap:
--------------------------------------------------------------------------------
1 | framework module SVProgressHUD {
2 | umbrella header "SVProgressHUD-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/SVProgressHUD/SVProgressHUD.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SVProgressHUD
2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
3 | OTHER_LDFLAGS = -framework "QuartzCore"
4 | PODS_BUILD_DIR = ${BUILD_DIR}
5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
6 | PODS_ROOT = ${SRCROOT}
7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SVProgressHUD
8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
9 | SKIP_INSTALL = YES
10 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Sonar/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | ${PRODUCT_BUNDLE_IDENTIFIER}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.0.1
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | ${CURRENT_PROJECT_VERSION}
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Sonar/Sonar-dummy.m:
--------------------------------------------------------------------------------
1 | #import
2 | @interface PodsDummy_Sonar : NSObject
3 | @end
4 | @implementation PodsDummy_Sonar
5 | @end
6 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Sonar/Sonar-prefix.pch:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Sonar/Sonar-umbrella.h:
--------------------------------------------------------------------------------
1 | #ifdef __OBJC__
2 | #import
3 | #else
4 | #ifndef FOUNDATION_EXPORT
5 | #if defined(__cplusplus)
6 | #define FOUNDATION_EXPORT extern "C"
7 | #else
8 | #define FOUNDATION_EXPORT extern
9 | #endif
10 | #endif
11 | #endif
12 |
13 |
14 | FOUNDATION_EXPORT double SonarVersionNumber;
15 | FOUNDATION_EXPORT const unsigned char SonarVersionString[];
16 |
17 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Sonar/Sonar.modulemap:
--------------------------------------------------------------------------------
1 | framework module Sonar {
2 | umbrella header "Sonar-umbrella.h"
3 |
4 | export *
5 | module * { export * }
6 | }
7 |
--------------------------------------------------------------------------------
/Pods/Target Support Files/Sonar/Sonar.xcconfig:
--------------------------------------------------------------------------------
1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Sonar
2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire"
3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
5 | PODS_BUILD_DIR = ${BUILD_DIR}
6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
7 | PODS_ROOT = ${SRCROOT}
8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Sonar
9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
10 | SKIP_INSTALL = YES
11 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 |
2 | # Brisk
3 |
4 | [](https://buildkite.com/florianbuerger/brisk)
5 |
6 | Brisk is an iOS universal app for filing [Radars](http://radar.apple.com/) and crossposting them to [Open Radar](http://www.openradar.me/).
7 |
8 | Brisk is written in Swift 4.0 and uses [Sonar](https://github.com/br1sk/Sonar) to communicate with Apple's Radar web “APIs”.
9 |
10 | 
11 | 
12 |
13 | ## Missing Features & known issues
14 |
15 | **Works best if you enter you Apple ID and a valid OpenRadar token on first launch**.
16 |
17 | Currently, the iOS version does not support:
18 |
19 | - [ ] Attachments
20 | - [ ] Saving radars as drafts
21 |
22 | Posting a radar will fail when you did not enter valid login credentials for Apple's Bug Reporter.
23 |
24 | Posting to OpenRadar only works when you entered your API key in settings. Re-entering your Apple ID and a new OpenRadar API token only works when you log out. Then you have to re-enter everything.
25 |
26 | ## URL Scheme
27 |
28 | Brisk supports an URL scheme `brisk-rdar` to integrate with other apps.
29 |
30 | Pass an OpenRadar number: `brisk-rdar://radar/123456`. Per default, Brisk searches for the number with user interaction. If you don't want that, use `brisk-radar://radar/123456?submit=false`.
31 |
32 | ## Installation
33 |
34 | iOS apps have to be signed in order to be installed on a device. To let Xcode handle everything for you change the `Bundle Identifier` to something unique, `com..brisk` for example and select your development team in Xcode.
35 |
36 | 1. Open `Brisk iOS.xcworkspace`
37 | 2. Go to Xcode > Preferences > Accounts and add an active iOS developer account if you haven't done that already
38 | 3. Change the `Bundle identifier` to `com..brisk`
39 | 4. Select the project in Xcode's sidebar, select the `Brisk iOS` target and select your Apple ID in `Signing > Team`
40 |
41 | Feel free to [contact me](mailto:hi@florianbuerger.com) if you need help or open a new issue.
42 |
43 | ## Development
44 |
45 | The app is built with Swift 4.0 and everything should work after you clone the project.
46 |
47 | ## Credits
48 |
49 | Thanks to [keith](https://github.com/keith) for the work on [Sonar](https://github.com/br1sk/Sonar) and the [macOS app](https://github.com/br1sk/brisk).
50 |
--------------------------------------------------------------------------------
/Resources/Assets/Brisk Icon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Resources/Assets/Brisk Icon.sketch
--------------------------------------------------------------------------------
/Resources/Assets/Brisk.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Resources/Assets/Brisk.sketch
--------------------------------------------------------------------------------
/Resources/Screenshots/01-start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Resources/Screenshots/01-start.png
--------------------------------------------------------------------------------
/Resources/Screenshots/02-dupe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Resources/Screenshots/02-dupe.png
--------------------------------------------------------------------------------
/Resources/Screenshots/03-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/Resources/Screenshots/03-file.png
--------------------------------------------------------------------------------
/fastlane/AppStore_com.florianbuerger.brisk.ios.mobileprovision:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/br1sk/brisk-ios/f7e662c6dec78ff537af37cc4e48b4effc40ca81/fastlane/AppStore_com.florianbuerger.brisk.ios.mobileprovision
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | app_identifier("com.florianbuerger.brisk.ios") # The bundle identifier of your app
2 | apple_id("f.buerger@mac.com") # Your Apple email address
3 |
4 | itc_team_id("334734") # App Store Connect Team ID
5 | team_id("HYJ4K64Z63") # Developer Portal Team ID
6 |
7 | # For more information about the Appfile, see:
8 | # https://docs.fastlane.tools/advanced/#appfile
9 |
--------------------------------------------------------------------------------
/fastlane/Deliverfile:
--------------------------------------------------------------------------------
1 | # The Deliverfile allows you to store various App Store Connect metadata
2 | # For more information, check out the docs
3 | # https://docs.fastlane.tools/actions/deliver/
4 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | default_platform(:ios)
2 |
3 | project_file = "Brisk iOS.xcodeproj"
4 | workspace_file = "Brisk iOS.xcworkspace"
5 | changelog = File.read("../CHANGELOG.md")
6 | upcoming_release_notes = changelog.split("\n# ").first
7 |
8 | before_all do
9 | if is_ci?
10 | keychain_password = ENV["KEYCHAIN_PASSWORD"]
11 | raise "You must specify KEYCHAIN_PASSWORD environment variable." if keychain_password.nil?
12 | unlock_keychain(
13 | path: "~/Library/Keychains/login.keychain-db",
14 | password: keychain_password
15 | )
16 | ensure_git_status_clean
17 | end
18 | end
19 |
20 | after_all do
21 | clean_build_artifacts
22 | end
23 |
24 | # ----------------------------------------
25 | # Code Signing
26 | # ----------------------------------------
27 |
28 | lane :provision_debug do
29 | get_certificates(
30 | development: true
31 | )
32 | ENV["PROV_PROFILE"] = get_provisioning_profile(
33 | development: true,
34 | output_path: "./fastlane/"
35 | )
36 | update_project_provisioning(
37 | xcodeproj: project_file,
38 | target_filter: "Brisk",
39 | build_configuration: "Debug"
40 | )
41 | end
42 |
43 | lane :provision_release do
44 | get_certificates(
45 | development: false
46 | )
47 | ENV["PROV_PROFILE"] = get_provisioning_profile(
48 | development: false,
49 | output_path: "./fastlane/"
50 | )
51 | update_project_provisioning(
52 | xcodeproj: project_file,
53 | target_filter: "Brisk",
54 | build_configuration: "Release"
55 | )
56 | end
57 |
58 | # ----------------------------------------
59 | # CI
60 | # ----------------------------------------
61 |
62 | # `master` -> run tests
63 | # `beta/*` -> release beta
64 | # `release/*` -> release production
65 | lane :start do
66 | prepare_build
67 | branch = git_branch # smart enough to handle BuildKite
68 | if branch.include? 'master'
69 | test
70 | elsif branch.include? 'release'
71 | release
72 | else
73 | puts "I do not know what to do on branch #{branch}"
74 | end
75 | end
76 |
77 | # ----------------------------------------
78 | # Build
79 | # ----------------------------------------
80 |
81 | lane :prepare_build do
82 | xcversion(version: '~> 9.4.0')
83 | clear_derived_data
84 | cocoapods(try_repo_update_on_error: true)
85 | end
86 |
87 | lane :release do
88 | test
89 |
90 | provision_release
91 |
92 | latest_build_number = latest_testflight_build_number(
93 | version: get_version_number(target: 'Brisk iOS'),
94 | initial_build_number: 0
95 | )
96 |
97 | increment_build_number(
98 | xcodeproj: project_file,
99 | build_number: latest_build_number + 1
100 | )
101 |
102 | build_app(
103 | workspace: workspace_file,
104 | scheme: "Brisk",
105 | configuration: "Release",
106 | export_options: {
107 | include_bitcode: false,
108 | provisioningProfiles: {
109 | "com.florianbuerger.brisk.ios" => ENV["PROV_PROFILE"]
110 | }
111 | }
112 | )
113 |
114 | upload_to_testflight(
115 | changelog: upcoming_release_notes
116 | )
117 | end
118 |
119 | # ----------------------------------------
120 | # Testing
121 | # ----------------------------------------
122 |
123 | lane :test do
124 | run_tests(scheme: "Brisk")
125 | end
126 |
--------------------------------------------------------------------------------
/fastlane/README.md:
--------------------------------------------------------------------------------
1 | fastlane documentation
2 | ================
3 | # Installation
4 |
5 | Make sure you have the latest version of the Xcode command line tools installed:
6 |
7 | ```
8 | xcode-select --install
9 | ```
10 |
11 | Install _fastlane_ using
12 | ```
13 | [sudo] gem install fastlane -NV
14 | ```
15 | or alternatively using `brew cask install fastlane`
16 |
17 | # Available Actions
18 | ### provision_debug
19 | ```
20 | fastlane provision_debug
21 | ```
22 |
23 | ### provision_release
24 | ```
25 | fastlane provision_release
26 | ```
27 |
28 | ### start
29 | ```
30 | fastlane start
31 | ```
32 |
33 | ### prepare_build
34 | ```
35 | fastlane prepare_build
36 | ```
37 |
38 | ### release
39 | ```
40 | fastlane release
41 | ```
42 |
43 | ### test
44 | ```
45 | fastlane test
46 | ```
47 |
48 |
49 | ----
50 |
51 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
52 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
53 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
54 |
--------------------------------------------------------------------------------
/fastlane/metadata/copyright.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/description.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/keywords.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/marketing_url.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/name.txt:
--------------------------------------------------------------------------------
1 | Brisk - File radars on the go
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/privacy_url.txt:
--------------------------------------------------------------------------------
1 | https://brisk.florianbuerger.com/privacy
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/promotional_text.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/release_notes.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/subtitle.txt:
--------------------------------------------------------------------------------
1 | Bug reporing made easy
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/en-US/support_url.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/primary_category.txt:
--------------------------------------------------------------------------------
1 | MZGenre.Utilities
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/primary_first_sub_category.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/primary_second_sub_category.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/review_information/demo_password.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/review_information/demo_user.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/review_information/email_address.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/review_information/first_name.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/review_information/last_name.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/review_information/notes.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/review_information/phone_number.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/secondary_category.txt:
--------------------------------------------------------------------------------
1 | MZGenre.Productivity
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/secondary_first_sub_category.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/secondary_second_sub_category.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/address_line1.txt:
--------------------------------------------------------------------------------
1 | Rotenwaldstrasse 4
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/address_line2.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/address_line3.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/city_name.txt:
--------------------------------------------------------------------------------
1 | Stuttgart
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/country.txt:
--------------------------------------------------------------------------------
1 | Germany
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/email_address.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/first_name.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/is_displayed_on_app_store.txt:
--------------------------------------------------------------------------------
1 | false
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/last_name.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/phone_number.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/postal_code.txt:
--------------------------------------------------------------------------------
1 | 70197
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/state.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/trade_representative_contact_information/trade_name.txt:
--------------------------------------------------------------------------------
1 | Florian Buerger
2 |
--------------------------------------------------------------------------------