├── .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 | ![Platform iOS](https://img.shields.io/cocoapods/p/AcknowList.svg) 8 | ![Swift 4](https://img.shields.io/badge/Swift-4-blue.svg) 9 | [![Build Status](https://travis-ci.org/vtourraine/AcknowList.svg?branch=master)](https://travis-ci.org/vtourraine/AcknowList) 10 | [![CocoaPods compatible](https://img.shields.io/cocoapods/v/AcknowList.svg)](https://cocoapods.org/pods/AcknowList) 11 | [![MIT license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/vtourraine/AcknowList/raw/master/LICENSE) 12 | 13 | iPhone screenshot 1 iPhone screenshot 2 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 | Apple TV screenshot 1 Apple TV screenshot 2 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 | [![Carthage 4 | compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](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 | [![Build status](https://badge.buildkite.com/672895d57514714220c79243148e0423061d2f04ca6ff5f6e8.svg)](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 | ![](Resources/Screenshots/01-start.png) 11 | ![](Resources/Screenshots/03-file.png) 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 | --------------------------------------------------------------------------------