├── .gitignore ├── Assets └── Screenshot.png ├── LICENSE ├── README.md ├── Serial Intents ├── AnalyzeIntentHandler.swift ├── Info.plist ├── IntentHandler.swift └── Intents.intentdefinition ├── Serial.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── Serial.xcscheme ├── Serial ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 20.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 50.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ └── Contents.json │ ├── Colours │ │ ├── Contents.json │ │ ├── background.colorset │ │ │ └── Contents.json │ │ ├── barTint.colorset │ │ │ └── Contents.json │ │ ├── contentSubtitle.colorset │ │ │ └── Contents.json │ │ ├── contentTitle.colorset │ │ │ └── Contents.json │ │ ├── tableViewCellBackground.colorset │ │ │ └── Contents.json │ │ ├── tableViewCellSelectionBackground.colorset │ │ │ └── Contents.json │ │ ├── tableViewSeparator.colorset │ │ │ └── Contents.json │ │ ├── textFieldPlaceholder.colorset │ │ │ └── Contents.json │ │ ├── textFieldText.colorset │ │ │ └── Contents.json │ │ └── tint.colorset │ │ │ └── Contents.json │ ├── Contents.json │ ├── Torch_Off.imageset │ │ ├── Contents.json │ │ ├── Torch_Off.png │ │ ├── Torch_Off@2x.png │ │ └── Torch_Off@3x.png │ └── Torch_On.imageset │ │ ├── Contents.json │ │ ├── Torch_On.png │ │ ├── Torch_On@2x.png │ │ └── Torch_On@3x.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── HistoryManager.swift ├── Info.plist ├── Serial.entitlements └── UI │ ├── Cells │ ├── ButtonCell.swift │ ├── HistoryItemCell.swift │ └── TextFieldCell.swift │ ├── ResultsHeaderView.swift │ ├── ResultsViewController.swift │ ├── Row Models │ ├── ActionRow.swift │ ├── RowRepresentable.swift │ ├── Section.swift │ ├── SubtitleRow.swift │ ├── Value.swift │ └── ValueRow.swift │ ├── ScannerViewController.swift │ ├── Utility │ ├── Extensions │ │ └── UIViewController+ShorthandAlerts.swift │ ├── GradientView.swift │ ├── PreviewView.swift │ ├── ThemedNavigationBar.swift │ └── ThemedTableViewController.swift │ └── ViewController.swift ├── SerialKit ├── Extensions │ └── String+LessPainfulSubstring.swift ├── Info.plist ├── SerialAnalyis+ManufactureLocation.swift ├── SerialAnalysis+ManufactureDate.swift ├── SerialAnalysis+OSFamily.swift ├── SerialAnalysis.swift └── SerialKit.h ├── build.sh └── deb └── DEBIAN └── control /.gitignore: -------------------------------------------------------------------------------- 1 | Archives 2 | 3 | # macOS Stuff 4 | .DS_Store 5 | 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xccheckout 28 | *.xcscmblueprint 29 | 30 | ## Obj-C/Swift specific 31 | *.hmap 32 | *.ipa 33 | *.dSYM.zip 34 | *.dSYM 35 | 36 | ## Playgrounds 37 | timeline.xctimeline 38 | playground.xcworkspace 39 | 40 | # Swift Package Manager 41 | # 42 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 43 | # Packages/ 44 | # Package.pins 45 | .build/ 46 | 47 | # CocoaPods 48 | # 49 | # We recommend against adding the Pods directory to your .gitignore. However 50 | # you should judge for yourself, the pros and cons are mentioned at: 51 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 52 | # 53 | # Pods/ 54 | 55 | # Carthage 56 | # 57 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 58 | # Carthage/Checkouts 59 | 60 | Carthage/Build 61 | 62 | # fastlane 63 | # 64 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 65 | # screenshots whenever they are needed. 66 | # For more information about the recommended setup visit: 67 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 68 | 69 | fastlane/report.xml 70 | fastlane/Preview.html 71 | fastlane/screenshots 72 | fastlane/test_output 73 | -------------------------------------------------------------------------------- /Assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Assets/Screenshot.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ayden Panhuyzen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Serial 2 | 3 | A simple iOS app for looking up an Apple device's serial number. 4 | 5 | ## Try Reincubate Lookup 6 | 7 | Serial has been succeeded by Reincubate Lookup, which you can read more about [here](https://reincubate.com/blog/serial-lookup-imei-checker-for-ios/). It features a brand-new design and tons more information. 8 | 9 | [![Download on the App Store](https://reincubate.com/res/lookup/i/badges/app_store/app_store_en.svg)](https://apps.apple.com/app/reincubate-lookup/id1483923951) 10 | 11 | --- 12 | 13 | **Warning:** Serial was coded quickly in a night as I needed to buy an iOS device with a specific version. 14 | 15 | ![Screenshots](/Assets/Screenshot.png?v2) 16 | 17 | [Download .ipa File](https://github.com/aydenp/Serial/releases) 18 | 19 | ## How does it work? 20 | 21 | Apple device serial numbers follow a known format that makes it trivial to decode their factory, manufacture week, and unique/product model identifier. 22 | 23 | This app uses an Apple support API to find the name of a device, given the 'product model identifier' suffix of a serial number. 24 | 25 | Additionally, it infers the OS family based on the device's name. If it matches a known OS type, it will get the highest probable OS version the device shipped with based on the week of manufacture. This is extremely useful if you are trying to buy a device on a certain version. 26 | 27 | You may also scan serial number barcodes from Apple product boxes. 28 | 29 | ## How can I use it? 30 | 31 | 1. Open the Xcode project (Serial.xcworkspace) in Xcode. 32 | 33 | 2. Change the app bundle identifier and teams to your own and. 34 | 35 | 3. Build the app and install it on your iOS device. 36 | 37 | ## Reporting Issues 38 | 39 | If you find a bug or code issue, report it on the [issues page](/issues). Keep in mind that this is for actual bugs, **NOT BUILD ISSUES**. 40 | 41 | ## Contributing 42 | 43 | Feel free to contribute to the source code of Serial to make it something even better! Just try to adhere to the general coding style throughout, to make it as readable as possible. 44 | 45 | ## License 46 | 47 | This project is licensed under the [MIT license](/LICENSE). Please make sure you comply with its terms while using it in any way. 48 | 49 | [Privacy Policy](https://ayden.dev/privacy/serial) 50 | 51 | -------------------------------------------------------------------------------- /Serial Intents/AnalyzeIntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnalyzeIntentHandler.swift 3 | // Serial Intents 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Intents 10 | import SerialKit 11 | 12 | class AnalyzeIntentHandler: NSObject, AnalyzeIntentHandling { 13 | 14 | func handle(intent: AnalyzeIntent, completion: @escaping (AnalyzeIntentResponse) -> Void) { 15 | guard let serialNumber = intent.serialNumber, let analysis = SerialAnalysis(serialNumber: serialNumber) else { 16 | completion(AnalyzeIntentResponse(code: .failure, userActivity: nil)) 17 | return 18 | } 19 | func checkIfComplete() { 20 | guard analysis.isComplete else { return } 21 | completion(AnalyzeIntentResponse.success(analysis: analysis.intentResults)) 22 | } 23 | analysis.register { _ in checkIfComplete() } 24 | checkIfComplete() 25 | } 26 | 27 | func resolveSerialNumber(for intent: AnalyzeIntent, with completion: @escaping (AnalyzeSerialNumberResolutionResult) -> Void) { 28 | guard let serialNumber = intent.serialNumber else { 29 | completion(.needsValue()) 30 | return 31 | } 32 | guard SerialAnalysis.isValid(serialNumber: serialNumber) else { 33 | completion(.unsupported(forReason: .invalid)) 34 | return 35 | } 36 | completion(.success(with: serialNumber)) 37 | } 38 | 39 | } 40 | 41 | extension SerialAnalysis { 42 | var intentResults: AnalysisResults { 43 | let results = AnalysisResults(identifier: nil, display: deviceName ?? "Unknown Device") 44 | results.serialNumber = serialNumber 45 | results.deviceName = deviceName 46 | results.manufactureDate = manufactureDate?.intentResults 47 | results.manufactureLocation = manufactureLocation?.locationName 48 | results.osFamily = osFamily?.friendlyName 49 | results.probableVersion = probableVersion 50 | return results 51 | } 52 | } 53 | 54 | extension SerialAnalysis.ManufactureDate { 55 | var intentResults: ManufactureDateRangeResults { 56 | let results = ManufactureDateRangeResults(identifier: nil, display: description) 57 | results.startDate = startDate.map { Calendar.current.dateComponents([.day, .month, .year], from: $0) } 58 | results.endDate = endDate.map { Calendar.current.dateComponents([.day, .month, .year], from: $0) } 59 | return results 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Serial Intents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Serial Intents 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | IntentsRestrictedWhileLocked 28 | 29 | IntentsRestrictedWhileProtectedDataUnavailable 30 | 31 | IntentsSupported 32 | 33 | AnalyzeIntent 34 | 35 | 36 | NSExtensionPointIdentifier 37 | com.apple.intents-service 38 | NSExtensionPrincipalClass 39 | $(PRODUCT_MODULE_NAME).IntentHandler 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Serial Intents/IntentHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntentHandler.swift 3 | // Serial Intents 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Intents 10 | 11 | class IntentHandler: INExtension { 12 | 13 | override func handler(for intent: INIntent) -> Any { 14 | guard intent is AnalyzeIntent else { fatalError("Unknown intent: \(intent)") } 15 | return AnalyzeIntentHandler() 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Serial Intents/Intents.intentdefinition: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | INEnums 6 | 7 | INIntentDefinitionModelVersion 8 | 1.1 9 | INIntentDefinitionNamespace 10 | efJyNc 11 | INIntentDefinitionSystemVersion 12 | 19A558d 13 | INIntentDefinitionToolsBuildVersion 14 | 11A420a 15 | INIntentDefinitionToolsVersion 16 | 11.0 17 | INIntents 18 | 19 | 20 | INIntentCategory 21 | information 22 | INIntentConfigurable 23 | 24 | INIntentDescription 25 | Look up a device's serial number to retrieve more information on it. 26 | INIntentDescriptionID 27 | xl9QCM 28 | INIntentIneligibleForSuggestions 29 | 30 | INIntentInput 31 | serialNumber 32 | INIntentKeyParameter 33 | serialNumber 34 | INIntentLastParameterTag 35 | 3 36 | INIntentManagedParameterCombinations 37 | 38 | serialNumber 39 | 40 | INIntentParameterCombinationSupportsBackgroundExecution 41 | 42 | INIntentParameterCombinationTitle 43 | Analyze ${serialNumber} 44 | INIntentParameterCombinationTitleID 45 | 4h3aWu 46 | INIntentParameterCombinationUpdatesLinked 47 | 48 | 49 | 50 | INIntentName 51 | Analyze 52 | INIntentParameters 53 | 54 | 55 | INIntentParameterDisplayName 56 | Serial Number 57 | INIntentParameterDisplayNameID 58 | n37vLU 59 | INIntentParameterDisplayPriority 60 | 1 61 | INIntentParameterMetadata 62 | 63 | INIntentParameterMetadataCapitalization 64 | AllCharacters 65 | INIntentParameterMetadataDisableAutocorrect 66 | 67 | INIntentParameterMetadataDisableSmartDashes 68 | 69 | INIntentParameterMetadataDisableSmartQuotes 70 | 71 | 72 | INIntentParameterName 73 | serialNumber 74 | INIntentParameterPromptDialogs 75 | 76 | 77 | INIntentParameterPromptDialogCustom 78 | 79 | INIntentParameterPromptDialogFormatString 80 | What is the device's serial number? 81 | INIntentParameterPromptDialogFormatStringID 82 | v5Yees 83 | INIntentParameterPromptDialogType 84 | Primary 85 | 86 | 87 | INIntentParameterSupportsResolution 88 | 89 | INIntentParameterTag 90 | 1 91 | INIntentParameterType 92 | String 93 | INIntentParameterUnsupportedReasons 94 | 95 | 96 | INIntentParameterUnsupportedReasonCode 97 | invalid 98 | INIntentParameterUnsupportedReasonCustom 99 | 100 | INIntentParameterUnsupportedReasonFormatString 101 | That is not a valid serial number. Please provide a 12-digit serial number. 102 | INIntentParameterUnsupportedReasonFormatStringID 103 | K3GtCl 104 | 105 | 106 | 107 | 108 | INIntentResponse 109 | 110 | INIntentResponseCodes 111 | 112 | 113 | INIntentResponseCodeConciseFormatString 114 | This serial number is for an ${analysis.deviceName} manufactured in ${analysis.manufactureDate}. View more information inside Serial. 115 | INIntentResponseCodeConciseFormatStringID 116 | xOcb2N 117 | INIntentResponseCodeFormatString 118 | Done! This serial number is for an ${analysis.deviceName} manufactured in ${analysis.manufactureDate}. 119 | INIntentResponseCodeFormatStringID 120 | zWztBA 121 | INIntentResponseCodeName 122 | success 123 | INIntentResponseCodeSuccess 124 | 125 | 126 | 127 | INIntentResponseCodeConciseFormatString 128 | I couldn't find any information on that serial number. 129 | INIntentResponseCodeConciseFormatStringID 130 | saXDyC 131 | INIntentResponseCodeFormatString 132 | Sorry, I couldn't find any information on that serial number. 133 | INIntentResponseCodeFormatStringID 134 | f7JEuP 135 | INIntentResponseCodeName 136 | failure 137 | 138 | 139 | INIntentResponseLastParameterTag 140 | 3 141 | INIntentResponseOutput 142 | analysis 143 | INIntentResponseParameters 144 | 145 | 146 | INIntentResponseParameterDisplayName 147 | Analysis Results 148 | INIntentResponseParameterDisplayNameID 149 | WpXLxx 150 | INIntentResponseParameterDisplayPriority 151 | 1 152 | INIntentResponseParameterName 153 | analysis 154 | INIntentResponseParameterObjectType 155 | AnalysisResults 156 | INIntentResponseParameterObjectTypeNamespace 157 | efJyNc 158 | INIntentResponseParameterTag 159 | 3 160 | INIntentResponseParameterType 161 | Object 162 | INIntentResponsePropertyDisplayName 163 | Analysis Results 164 | INIntentResponsePropertyDisplayNameID 165 | CCMFOO 166 | INIntentResponsePropertyDisplayPriority 167 | 1 168 | INIntentResponsePropertyName 169 | analysis 170 | INIntentResponsePropertyObjectType 171 | SerialAnalysis 172 | INIntentResponsePropertyObjectTypeNamespace 173 | efJyNc 174 | INIntentResponsePropertyTag 175 | 2 176 | INIntentResponsePropertyType 177 | Object 178 | 179 | 180 | 181 | INIntentTitle 182 | Analyze Serial Number 183 | INIntentTitleID 184 | LHedNm 185 | INIntentType 186 | Custom 187 | INIntentVerb 188 | View 189 | 190 | 191 | INTypes 192 | 193 | 194 | INTypeDisplayName 195 | Analysis Results 196 | INTypeDisplayNameID 197 | IV1M10 198 | INTypeLastPropertyTag 199 | 107 200 | INTypeName 201 | AnalysisResults 202 | INTypeProperties 203 | 204 | 205 | INTypePropertyDefault 206 | 207 | INTypePropertyDisplayPriority 208 | 1 209 | INTypePropertyName 210 | identifier 211 | INTypePropertyTag 212 | 1 213 | INTypePropertyType 214 | String 215 | 216 | 217 | INTypePropertyDefault 218 | 219 | INTypePropertyDisplayPriority 220 | 2 221 | INTypePropertyName 222 | displayString 223 | INTypePropertyTag 224 | 2 225 | INTypePropertyType 226 | String 227 | 228 | 229 | INTypePropertyDefault 230 | 231 | INTypePropertyDisplayPriority 232 | 3 233 | INTypePropertyName 234 | pronunciationHint 235 | INTypePropertyTag 236 | 3 237 | INTypePropertyType 238 | String 239 | 240 | 241 | INTypePropertyDefault 242 | 243 | INTypePropertyDisplayPriority 244 | 4 245 | INTypePropertyName 246 | alternativeSpeakableMatches 247 | INTypePropertySupportsMultipleValues 248 | 249 | INTypePropertyTag 250 | 4 251 | INTypePropertyType 252 | SpeakableString 253 | 254 | 255 | INTypePropertyDisplayName 256 | Serial Number 257 | INTypePropertyDisplayNameID 258 | 371oGs 259 | INTypePropertyDisplayPriority 260 | 5 261 | INTypePropertyName 262 | serialNumber 263 | INTypePropertyTag 264 | 100 265 | INTypePropertyType 266 | String 267 | 268 | 269 | INTypePropertyDisplayName 270 | Manufacture Location 271 | INTypePropertyDisplayNameID 272 | Bx13UK 273 | INTypePropertyDisplayPriority 274 | 6 275 | INTypePropertyName 276 | manufactureLocation 277 | INTypePropertyTag 278 | 102 279 | INTypePropertyType 280 | String 281 | 282 | 283 | INTypePropertyDisplayName 284 | Manufacture Date 285 | INTypePropertyDisplayNameID 286 | GF9YyI 287 | INTypePropertyDisplayPriority 288 | 7 289 | INTypePropertyName 290 | manufactureDate 291 | INTypePropertyObjectType 292 | ManufactureDateRangeResults 293 | INTypePropertyObjectTypeNamespace 294 | efJyNc 295 | INTypePropertyTag 296 | 107 297 | INTypePropertyType 298 | Object 299 | 300 | 301 | INTypePropertyDisplayName 302 | Device Name 303 | INTypePropertyDisplayNameID 304 | H3IQ0Q 305 | INTypePropertyDisplayPriority 306 | 8 307 | INTypePropertyName 308 | deviceName 309 | INTypePropertyTag 310 | 104 311 | INTypePropertyType 312 | String 313 | 314 | 315 | INTypePropertyDisplayName 316 | Operating System Family 317 | INTypePropertyDisplayNameID 318 | 9p8n5N 319 | INTypePropertyDisplayPriority 320 | 9 321 | INTypePropertyName 322 | osFamily 323 | INTypePropertyTag 324 | 105 325 | INTypePropertyType 326 | String 327 | 328 | 329 | INTypePropertyDisplayName 330 | Probable Version 331 | INTypePropertyDisplayNameID 332 | 0qjKld 333 | INTypePropertyDisplayPriority 334 | 10 335 | INTypePropertyName 336 | probableVersion 337 | INTypePropertyTag 338 | 106 339 | INTypePropertyType 340 | String 341 | 342 | 343 | 344 | 345 | INTypeDisplayName 346 | Manufacturing Date Range 347 | INTypeDisplayNameID 348 | zBBalK 349 | INTypeLastPropertyTag 350 | 103 351 | INTypeName 352 | ManufactureDateRangeResults 353 | INTypeProperties 354 | 355 | 356 | INTypePropertyDefault 357 | 358 | INTypePropertyDisplayPriority 359 | 1 360 | INTypePropertyName 361 | identifier 362 | INTypePropertyTag 363 | 1 364 | INTypePropertyType 365 | String 366 | 367 | 368 | INTypePropertyDefault 369 | 370 | INTypePropertyDisplayPriority 371 | 2 372 | INTypePropertyName 373 | displayString 374 | INTypePropertyTag 375 | 2 376 | INTypePropertyType 377 | String 378 | 379 | 380 | INTypePropertyDefault 381 | 382 | INTypePropertyDisplayPriority 383 | 3 384 | INTypePropertyName 385 | pronunciationHint 386 | INTypePropertyTag 387 | 3 388 | INTypePropertyType 389 | String 390 | 391 | 392 | INTypePropertyDefault 393 | 394 | INTypePropertyDisplayPriority 395 | 4 396 | INTypePropertyName 397 | alternativeSpeakableMatches 398 | INTypePropertySupportsMultipleValues 399 | 400 | INTypePropertyTag 401 | 4 402 | INTypePropertyType 403 | SpeakableString 404 | 405 | 406 | INTypePropertyDisplayName 407 | Start Date 408 | INTypePropertyDisplayNameID 409 | AUO8gV 410 | INTypePropertyDisplayPriority 411 | 5 412 | INTypePropertyMetadata 413 | 414 | INTypePropertyMetadataDateStyle 415 | Medium 416 | INTypePropertyMetadataFormat 417 | Style 418 | INTypePropertyMetadataTimeStyle 419 | Medium 420 | INTypePropertyMetadataType 421 | Date 422 | 423 | INTypePropertyName 424 | startDate 425 | INTypePropertyTag 426 | 102 427 | INTypePropertyType 428 | DateComponents 429 | 430 | 431 | INTypePropertyDisplayName 432 | End Date 433 | INTypePropertyDisplayNameID 434 | 2OAXrA 435 | INTypePropertyDisplayPriority 436 | 6 437 | INTypePropertyMetadata 438 | 439 | INTypePropertyMetadataDateStyle 440 | Medium 441 | INTypePropertyMetadataFormat 442 | Style 443 | INTypePropertyMetadataTimeStyle 444 | Medium 445 | INTypePropertyMetadataType 446 | Date 447 | 448 | INTypePropertyName 449 | endDate 450 | INTypePropertyTag 451 | 103 452 | INTypePropertyType 453 | DateComponents 454 | 455 | 456 | 457 | 458 | 459 | 460 | -------------------------------------------------------------------------------- /Serial.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C3134B8F2328B8050005952C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C36F1A412256BA170007DC21 /* Assets.xcassets */; }; 11 | C325FFD2225702400060DFCF /* ScannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C325FFD1225702400060DFCF /* ScannerViewController.swift */; }; 12 | C36D8DEE233416B6000B6F2D /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4E1E2333F47D0011BBDF /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; }; 13 | C36D8DF02334184C000B6F2D /* AnalyzeIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36D8DEF2334184C000B6F2D /* AnalyzeIntentHandler.swift */; }; 14 | C36F1A3B2256BA150007DC21 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36F1A3A2256BA150007DC21 /* AppDelegate.swift */; }; 15 | C36F1A3D2256BA150007DC21 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36F1A3C2256BA150007DC21 /* ViewController.swift */; }; 16 | C36F1A402256BA150007DC21 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C36F1A3E2256BA150007DC21 /* Main.storyboard */; }; 17 | C36F1A452256BA170007DC21 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C36F1A432256BA170007DC21 /* LaunchScreen.storyboard */; }; 18 | C36F1A4F2256BE350007DC21 /* UIViewController+ShorthandAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36F1A4E2256BE350007DC21 /* UIViewController+ShorthandAlerts.swift */; }; 19 | C36F1A512256D05F0007DC21 /* ResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36F1A502256D05F0007DC21 /* ResultsViewController.swift */; }; 20 | C36F1A532256DB310007DC21 /* HistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36F1A522256DB310007DC21 /* HistoryManager.swift */; }; 21 | C3AB0BF42328963900D9BBA8 /* TextFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AB0BF32328963900D9BBA8 /* TextFieldCell.swift */; }; 22 | C3AB0BF62328964000D9BBA8 /* ButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AB0BF52328964000D9BBA8 /* ButtonCell.swift */; }; 23 | C3AB0BF82328964900D9BBA8 /* HistoryItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AB0BF72328964900D9BBA8 /* HistoryItemCell.swift */; }; 24 | C3AB0BFA23289B1100D9BBA8 /* GradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AB0BF923289B1100D9BBA8 /* GradientView.swift */; }; 25 | C3AE4DD1233218AC0011BBDF /* ThemedNavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DD0233218AC0011BBDF /* ThemedNavigationBar.swift */; }; 26 | C3AE4DDD2333E7470011BBDF /* SerialKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C3AE4DDB2333E7470011BBDF /* SerialKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 27 | C3AE4DE02333E7470011BBDF /* SerialKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3AE4DD92333E7470011BBDF /* SerialKit.framework */; }; 28 | C3AE4DE12333E7470011BBDF /* SerialKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3AE4DD92333E7470011BBDF /* SerialKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 29 | C3AE4DE62333E74F0011BBDF /* SerialAnalysis+OSFamily.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C9959E227E9D2F00EE6D63 /* SerialAnalysis+OSFamily.swift */; }; 30 | C3AE4DE72333E74F0011BBDF /* SerialAnalyis+ManufactureLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C9959A227E9CDC00EE6D63 /* SerialAnalyis+ManufactureLocation.swift */; }; 31 | C3AE4DE82333E74F0011BBDF /* SerialAnalysis.swift in Sources */ = {isa = PBXBuildFile; fileRef = C36F1A4C2256BDBD0007DC21 /* SerialAnalysis.swift */; }; 32 | C3AE4DE92333E74F0011BBDF /* SerialAnalysis+ManufactureDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C9959C227E9CFE00EE6D63 /* SerialAnalysis+ManufactureDate.swift */; }; 33 | C3AE4DEA2333E7900011BBDF /* String+LessPainfulSubstring.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4ED84225717A9009EEF45 /* String+LessPainfulSubstring.swift */; }; 34 | C3AE4DED2333EB4E0011BBDF /* RowRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DEC2333EB4E0011BBDF /* RowRepresentable.swift */; }; 35 | C3AE4DEF2333EB620011BBDF /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DEE2333EB620011BBDF /* Section.swift */; }; 36 | C3AE4DF12333EB790011BBDF /* ValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DF02333EB790011BBDF /* ValueRow.swift */; }; 37 | C3AE4DF32333EB7E0011BBDF /* SubtitleRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DF22333EB7E0011BBDF /* SubtitleRow.swift */; }; 38 | C3AE4DF52333EB830011BBDF /* ActionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DF42333EB830011BBDF /* ActionRow.swift */; }; 39 | C3AE4DF72333EC350011BBDF /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DF62333EC350011BBDF /* Value.swift */; }; 40 | C3AE4E002333F45B0011BBDF /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4DFF2333F45B0011BBDF /* IntentHandler.swift */; }; 41 | C3AE4E162333F45B0011BBDF /* Serial Intents.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C3AE4DFD2333F45B0011BBDF /* Serial Intents.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 42 | C3AE4E1F2333F47D0011BBDF /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = C3AE4E1E2333F47D0011BBDF /* Intents.intentdefinition */; }; 43 | C3C995A1227E9D7E00EE6D63 /* ThemedTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C995A0227E9D7E00EE6D63 /* ThemedTableViewController.swift */; }; 44 | C3D4ED8722571849009EEF45 /* ResultsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4ED8622571849009EEF45 /* ResultsHeaderView.swift */; }; 45 | C3D4ED8B22571BD0009EEF45 /* PreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D4ED8A22571BD0009EEF45 /* PreviewView.swift */; }; 46 | /* End PBXBuildFile section */ 47 | 48 | /* Begin PBXContainerItemProxy section */ 49 | C36D8DF1233439AD000B6F2D /* PBXContainerItemProxy */ = { 50 | isa = PBXContainerItemProxy; 51 | containerPortal = C36F1A2F2256BA150007DC21 /* Project object */; 52 | proxyType = 1; 53 | remoteGlobalIDString = C3AE4DD82333E7470011BBDF; 54 | remoteInfo = SerialKit; 55 | }; 56 | C3AE4DDE2333E7470011BBDF /* PBXContainerItemProxy */ = { 57 | isa = PBXContainerItemProxy; 58 | containerPortal = C36F1A2F2256BA150007DC21 /* Project object */; 59 | proxyType = 1; 60 | remoteGlobalIDString = C3AE4DD82333E7470011BBDF; 61 | remoteInfo = SerialKit; 62 | }; 63 | C3AE4E142333F45B0011BBDF /* PBXContainerItemProxy */ = { 64 | isa = PBXContainerItemProxy; 65 | containerPortal = C36F1A2F2256BA150007DC21 /* Project object */; 66 | proxyType = 1; 67 | remoteGlobalIDString = C3AE4DFC2333F45B0011BBDF; 68 | remoteInfo = "Serial Intents"; 69 | }; 70 | /* End PBXContainerItemProxy section */ 71 | 72 | /* Begin PBXCopyFilesBuildPhase section */ 73 | C3AE4DE52333E7470011BBDF /* Embed Frameworks */ = { 74 | isa = PBXCopyFilesBuildPhase; 75 | buildActionMask = 2147483647; 76 | dstPath = ""; 77 | dstSubfolderSpec = 10; 78 | files = ( 79 | C3AE4DE12333E7470011BBDF /* SerialKit.framework in Embed Frameworks */, 80 | ); 81 | name = "Embed Frameworks"; 82 | runOnlyForDeploymentPostprocessing = 0; 83 | }; 84 | C3AE4E1D2333F45B0011BBDF /* Embed App Extensions */ = { 85 | isa = PBXCopyFilesBuildPhase; 86 | buildActionMask = 2147483647; 87 | dstPath = ""; 88 | dstSubfolderSpec = 13; 89 | files = ( 90 | C3AE4E162333F45B0011BBDF /* Serial Intents.appex in Embed App Extensions */, 91 | ); 92 | name = "Embed App Extensions"; 93 | runOnlyForDeploymentPostprocessing = 0; 94 | }; 95 | /* End PBXCopyFilesBuildPhase section */ 96 | 97 | /* Begin PBXFileReference section */ 98 | C325FFD1225702400060DFCF /* ScannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScannerViewController.swift; sourceTree = ""; }; 99 | C36D8DEC2334144A000B6F2D /* Serial.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Serial.entitlements; sourceTree = ""; }; 100 | C36D8DEF2334184C000B6F2D /* AnalyzeIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyzeIntentHandler.swift; sourceTree = ""; }; 101 | C36F1A372256BA150007DC21 /* Serial.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Serial.app; sourceTree = BUILT_PRODUCTS_DIR; }; 102 | C36F1A3A2256BA150007DC21 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 103 | C36F1A3C2256BA150007DC21 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 104 | C36F1A3F2256BA150007DC21 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 105 | C36F1A412256BA170007DC21 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 106 | C36F1A442256BA170007DC21 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 107 | C36F1A462256BA170007DC21 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 108 | C36F1A4C2256BDBD0007DC21 /* SerialAnalysis.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialAnalysis.swift; sourceTree = ""; }; 109 | C36F1A4E2256BE350007DC21 /* UIViewController+ShorthandAlerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+ShorthandAlerts.swift"; sourceTree = ""; }; 110 | C36F1A502256D05F0007DC21 /* ResultsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsViewController.swift; sourceTree = ""; }; 111 | C36F1A522256DB310007DC21 /* HistoryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryManager.swift; sourceTree = ""; }; 112 | C3AB0BF32328963900D9BBA8 /* TextFieldCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldCell.swift; sourceTree = ""; }; 113 | C3AB0BF52328964000D9BBA8 /* ButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonCell.swift; sourceTree = ""; }; 114 | C3AB0BF72328964900D9BBA8 /* HistoryItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryItemCell.swift; sourceTree = ""; }; 115 | C3AB0BF923289B1100D9BBA8 /* GradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientView.swift; sourceTree = ""; }; 116 | C3AE4DD0233218AC0011BBDF /* ThemedNavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedNavigationBar.swift; sourceTree = ""; }; 117 | C3AE4DD92333E7470011BBDF /* SerialKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SerialKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 118 | C3AE4DDB2333E7470011BBDF /* SerialKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SerialKit.h; sourceTree = ""; }; 119 | C3AE4DDC2333E7470011BBDF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 120 | C3AE4DEC2333EB4E0011BBDF /* RowRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowRepresentable.swift; sourceTree = ""; }; 121 | C3AE4DEE2333EB620011BBDF /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = ""; }; 122 | C3AE4DF02333EB790011BBDF /* ValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValueRow.swift; sourceTree = ""; }; 123 | C3AE4DF22333EB7E0011BBDF /* SubtitleRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubtitleRow.swift; sourceTree = ""; }; 124 | C3AE4DF42333EB830011BBDF /* ActionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRow.swift; sourceTree = ""; }; 125 | C3AE4DF62333EC350011BBDF /* Value.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; 126 | C3AE4DFD2333F45B0011BBDF /* Serial Intents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Serial Intents.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 127 | C3AE4DFF2333F45B0011BBDF /* IntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = ""; }; 128 | C3AE4E012333F45B0011BBDF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 129 | C3AE4E082333F45B0011BBDF /* IntentsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IntentsUI.framework; path = System/Library/Frameworks/IntentsUI.framework; sourceTree = SDKROOT; }; 130 | C3AE4E1E2333F47D0011BBDF /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = ""; }; 131 | C3C9959A227E9CDC00EE6D63 /* SerialAnalyis+ManufactureLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SerialAnalyis+ManufactureLocation.swift"; sourceTree = ""; }; 132 | C3C9959C227E9CFE00EE6D63 /* SerialAnalysis+ManufactureDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SerialAnalysis+ManufactureDate.swift"; sourceTree = ""; }; 133 | C3C9959E227E9D2F00EE6D63 /* SerialAnalysis+OSFamily.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SerialAnalysis+OSFamily.swift"; sourceTree = ""; }; 134 | C3C995A0227E9D7E00EE6D63 /* ThemedTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemedTableViewController.swift; sourceTree = ""; }; 135 | C3D4ED84225717A9009EEF45 /* String+LessPainfulSubstring.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+LessPainfulSubstring.swift"; sourceTree = ""; }; 136 | C3D4ED8622571849009EEF45 /* ResultsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultsHeaderView.swift; sourceTree = ""; }; 137 | C3D4ED8A22571BD0009EEF45 /* PreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewView.swift; sourceTree = ""; }; 138 | /* End PBXFileReference section */ 139 | 140 | /* Begin PBXFrameworksBuildPhase section */ 141 | C36F1A342256BA150007DC21 /* Frameworks */ = { 142 | isa = PBXFrameworksBuildPhase; 143 | buildActionMask = 2147483647; 144 | files = ( 145 | C3AE4DE02333E7470011BBDF /* SerialKit.framework in Frameworks */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | C3AE4DD62333E7470011BBDF /* Frameworks */ = { 150 | isa = PBXFrameworksBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | C3AE4DFA2333F45B0011BBDF /* Frameworks */ = { 157 | isa = PBXFrameworksBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | ); 161 | runOnlyForDeploymentPostprocessing = 0; 162 | }; 163 | /* End PBXFrameworksBuildPhase section */ 164 | 165 | /* Begin PBXGroup section */ 166 | C36D8DED2334168A000B6F2D /* Extensions */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | C3D4ED84225717A9009EEF45 /* String+LessPainfulSubstring.swift */, 170 | ); 171 | path = Extensions; 172 | sourceTree = ""; 173 | }; 174 | C36F1A2E2256BA150007DC21 = { 175 | isa = PBXGroup; 176 | children = ( 177 | C36F1A392256BA150007DC21 /* Serial */, 178 | C3AE4DDA2333E7470011BBDF /* SerialKit */, 179 | C3AE4DFE2333F45B0011BBDF /* Serial Intents */, 180 | C3AE4E072333F45B0011BBDF /* Frameworks */, 181 | C36F1A382256BA150007DC21 /* Products */, 182 | ); 183 | indentWidth = 4; 184 | sourceTree = ""; 185 | tabWidth = 4; 186 | usesTabs = 0; 187 | }; 188 | C36F1A382256BA150007DC21 /* Products */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | C36F1A372256BA150007DC21 /* Serial.app */, 192 | C3AE4DD92333E7470011BBDF /* SerialKit.framework */, 193 | C3AE4DFD2333F45B0011BBDF /* Serial Intents.appex */, 194 | ); 195 | name = Products; 196 | sourceTree = ""; 197 | }; 198 | C36F1A392256BA150007DC21 /* Serial */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | C36D8DEC2334144A000B6F2D /* Serial.entitlements */, 202 | C36F1A3A2256BA150007DC21 /* AppDelegate.swift */, 203 | C3C995A4227E9D9900EE6D63 /* UI */, 204 | C36F1A3E2256BA150007DC21 /* Main.storyboard */, 205 | C36F1A412256BA170007DC21 /* Assets.xcassets */, 206 | C36F1A432256BA170007DC21 /* LaunchScreen.storyboard */, 207 | C36F1A462256BA170007DC21 /* Info.plist */, 208 | C36F1A522256DB310007DC21 /* HistoryManager.swift */, 209 | ); 210 | path = Serial; 211 | sourceTree = ""; 212 | }; 213 | C3AB0BF22328963000D9BBA8 /* Cells */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | C3AB0BF32328963900D9BBA8 /* TextFieldCell.swift */, 217 | C3AB0BF52328964000D9BBA8 /* ButtonCell.swift */, 218 | C3AB0BF72328964900D9BBA8 /* HistoryItemCell.swift */, 219 | ); 220 | path = Cells; 221 | sourceTree = ""; 222 | }; 223 | C3AE4DDA2333E7470011BBDF /* SerialKit */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | C3AE4DDB2333E7470011BBDF /* SerialKit.h */, 227 | C36F1A4C2256BDBD0007DC21 /* SerialAnalysis.swift */, 228 | C3C9959E227E9D2F00EE6D63 /* SerialAnalysis+OSFamily.swift */, 229 | C3C9959A227E9CDC00EE6D63 /* SerialAnalyis+ManufactureLocation.swift */, 230 | C3C9959C227E9CFE00EE6D63 /* SerialAnalysis+ManufactureDate.swift */, 231 | C36D8DED2334168A000B6F2D /* Extensions */, 232 | C3AE4DDC2333E7470011BBDF /* Info.plist */, 233 | ); 234 | path = SerialKit; 235 | sourceTree = ""; 236 | }; 237 | C3AE4DF82333EC5A0011BBDF /* Row Models */ = { 238 | isa = PBXGroup; 239 | children = ( 240 | C3AE4DEE2333EB620011BBDF /* Section.swift */, 241 | C3AE4DEC2333EB4E0011BBDF /* RowRepresentable.swift */, 242 | C3AE4DF62333EC350011BBDF /* Value.swift */, 243 | C3AE4DF02333EB790011BBDF /* ValueRow.swift */, 244 | C3AE4DF22333EB7E0011BBDF /* SubtitleRow.swift */, 245 | C3AE4DF42333EB830011BBDF /* ActionRow.swift */, 246 | ); 247 | path = "Row Models"; 248 | sourceTree = ""; 249 | }; 250 | C3AE4DFE2333F45B0011BBDF /* Serial Intents */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | C3AE4DFF2333F45B0011BBDF /* IntentHandler.swift */, 254 | C36D8DEF2334184C000B6F2D /* AnalyzeIntentHandler.swift */, 255 | C3AE4E1E2333F47D0011BBDF /* Intents.intentdefinition */, 256 | C3AE4E012333F45B0011BBDF /* Info.plist */, 257 | ); 258 | path = "Serial Intents"; 259 | sourceTree = ""; 260 | }; 261 | C3AE4E072333F45B0011BBDF /* Frameworks */ = { 262 | isa = PBXGroup; 263 | children = ( 264 | C3AE4E082333F45B0011BBDF /* IntentsUI.framework */, 265 | ); 266 | name = Frameworks; 267 | sourceTree = ""; 268 | }; 269 | C3C99599227E9C6300EE6D63 /* Extensions */ = { 270 | isa = PBXGroup; 271 | children = ( 272 | C36F1A4E2256BE350007DC21 /* UIViewController+ShorthandAlerts.swift */, 273 | ); 274 | path = Extensions; 275 | sourceTree = ""; 276 | }; 277 | C3C995A2227E9D8600EE6D63 /* Utility */ = { 278 | isa = PBXGroup; 279 | children = ( 280 | C3AB0BF923289B1100D9BBA8 /* GradientView.swift */, 281 | C3D4ED8A22571BD0009EEF45 /* PreviewView.swift */, 282 | C3C995A0227E9D7E00EE6D63 /* ThemedTableViewController.swift */, 283 | C3AE4DD0233218AC0011BBDF /* ThemedNavigationBar.swift */, 284 | C3C99599227E9C6300EE6D63 /* Extensions */, 285 | ); 286 | path = Utility; 287 | sourceTree = ""; 288 | }; 289 | C3C995A4227E9D9900EE6D63 /* UI */ = { 290 | isa = PBXGroup; 291 | children = ( 292 | C36F1A3C2256BA150007DC21 /* ViewController.swift */, 293 | C325FFD1225702400060DFCF /* ScannerViewController.swift */, 294 | C36F1A502256D05F0007DC21 /* ResultsViewController.swift */, 295 | C3D4ED8622571849009EEF45 /* ResultsHeaderView.swift */, 296 | C3AE4DF82333EC5A0011BBDF /* Row Models */, 297 | C3AB0BF22328963000D9BBA8 /* Cells */, 298 | C3C995A2227E9D8600EE6D63 /* Utility */, 299 | ); 300 | path = UI; 301 | sourceTree = ""; 302 | }; 303 | /* End PBXGroup section */ 304 | 305 | /* Begin PBXHeadersBuildPhase section */ 306 | C3AE4DD42333E7470011BBDF /* Headers */ = { 307 | isa = PBXHeadersBuildPhase; 308 | buildActionMask = 2147483647; 309 | files = ( 310 | C3AE4DDD2333E7470011BBDF /* SerialKit.h in Headers */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | /* End PBXHeadersBuildPhase section */ 315 | 316 | /* Begin PBXNativeTarget section */ 317 | C36F1A362256BA150007DC21 /* Serial */ = { 318 | isa = PBXNativeTarget; 319 | buildConfigurationList = C36F1A492256BA170007DC21 /* Build configuration list for PBXNativeTarget "Serial" */; 320 | buildPhases = ( 321 | C36F1A332256BA150007DC21 /* Sources */, 322 | C36F1A342256BA150007DC21 /* Frameworks */, 323 | C36F1A352256BA150007DC21 /* Resources */, 324 | C3AE4DE52333E7470011BBDF /* Embed Frameworks */, 325 | C3AE4E1D2333F45B0011BBDF /* Embed App Extensions */, 326 | ); 327 | buildRules = ( 328 | ); 329 | dependencies = ( 330 | C3AE4DDF2333E7470011BBDF /* PBXTargetDependency */, 331 | C3AE4E152333F45B0011BBDF /* PBXTargetDependency */, 332 | ); 333 | name = Serial; 334 | productName = Serial; 335 | productReference = C36F1A372256BA150007DC21 /* Serial.app */; 336 | productType = "com.apple.product-type.application"; 337 | }; 338 | C3AE4DD82333E7470011BBDF /* SerialKit */ = { 339 | isa = PBXNativeTarget; 340 | buildConfigurationList = C3AE4DE22333E7470011BBDF /* Build configuration list for PBXNativeTarget "SerialKit" */; 341 | buildPhases = ( 342 | C3AE4DD42333E7470011BBDF /* Headers */, 343 | C3AE4DD52333E7470011BBDF /* Sources */, 344 | C3AE4DD62333E7470011BBDF /* Frameworks */, 345 | C3AE4DD72333E7470011BBDF /* Resources */, 346 | ); 347 | buildRules = ( 348 | ); 349 | dependencies = ( 350 | ); 351 | name = SerialKit; 352 | productName = SerialKit; 353 | productReference = C3AE4DD92333E7470011BBDF /* SerialKit.framework */; 354 | productType = "com.apple.product-type.framework"; 355 | }; 356 | C3AE4DFC2333F45B0011BBDF /* Serial Intents */ = { 357 | isa = PBXNativeTarget; 358 | buildConfigurationList = C3AE4E1A2333F45B0011BBDF /* Build configuration list for PBXNativeTarget "Serial Intents" */; 359 | buildPhases = ( 360 | C3AE4DF92333F45B0011BBDF /* Sources */, 361 | C3AE4DFA2333F45B0011BBDF /* Frameworks */, 362 | C3AE4DFB2333F45B0011BBDF /* Resources */, 363 | ); 364 | buildRules = ( 365 | ); 366 | dependencies = ( 367 | C36D8DF2233439AD000B6F2D /* PBXTargetDependency */, 368 | ); 369 | name = "Serial Intents"; 370 | productName = "Serial Intents"; 371 | productReference = C3AE4DFD2333F45B0011BBDF /* Serial Intents.appex */; 372 | productType = "com.apple.product-type.app-extension"; 373 | }; 374 | /* End PBXNativeTarget section */ 375 | 376 | /* Begin PBXProject section */ 377 | C36F1A2F2256BA150007DC21 /* Project object */ = { 378 | isa = PBXProject; 379 | attributes = { 380 | LastSwiftUpdateCheck = 1100; 381 | LastUpgradeCheck = 1010; 382 | ORGANIZATIONNAME = "Ayden Panhuyzen"; 383 | TargetAttributes = { 384 | C36F1A362256BA150007DC21 = { 385 | CreatedOnToolsVersion = 10.1; 386 | LastSwiftMigration = 1100; 387 | }; 388 | C3AE4DD82333E7470011BBDF = { 389 | CreatedOnToolsVersion = 11.0; 390 | }; 391 | C3AE4DFC2333F45B0011BBDF = { 392 | CreatedOnToolsVersion = 11.0; 393 | }; 394 | }; 395 | }; 396 | buildConfigurationList = C36F1A322256BA150007DC21 /* Build configuration list for PBXProject "Serial" */; 397 | compatibilityVersion = "Xcode 9.3"; 398 | developmentRegion = en; 399 | hasScannedForEncodings = 0; 400 | knownRegions = ( 401 | en, 402 | Base, 403 | ); 404 | mainGroup = C36F1A2E2256BA150007DC21; 405 | productRefGroup = C36F1A382256BA150007DC21 /* Products */; 406 | projectDirPath = ""; 407 | projectRoot = ""; 408 | targets = ( 409 | C36F1A362256BA150007DC21 /* Serial */, 410 | C3AE4DD82333E7470011BBDF /* SerialKit */, 411 | C3AE4DFC2333F45B0011BBDF /* Serial Intents */, 412 | ); 413 | }; 414 | /* End PBXProject section */ 415 | 416 | /* Begin PBXResourcesBuildPhase section */ 417 | C36F1A352256BA150007DC21 /* Resources */ = { 418 | isa = PBXResourcesBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | C36F1A452256BA170007DC21 /* LaunchScreen.storyboard in Resources */, 422 | C3134B8F2328B8050005952C /* Assets.xcassets in Resources */, 423 | C36F1A402256BA150007DC21 /* Main.storyboard in Resources */, 424 | ); 425 | runOnlyForDeploymentPostprocessing = 0; 426 | }; 427 | C3AE4DD72333E7470011BBDF /* Resources */ = { 428 | isa = PBXResourcesBuildPhase; 429 | buildActionMask = 2147483647; 430 | files = ( 431 | ); 432 | runOnlyForDeploymentPostprocessing = 0; 433 | }; 434 | C3AE4DFB2333F45B0011BBDF /* Resources */ = { 435 | isa = PBXResourcesBuildPhase; 436 | buildActionMask = 2147483647; 437 | files = ( 438 | ); 439 | runOnlyForDeploymentPostprocessing = 0; 440 | }; 441 | /* End PBXResourcesBuildPhase section */ 442 | 443 | /* Begin PBXSourcesBuildPhase section */ 444 | C36F1A332256BA150007DC21 /* Sources */ = { 445 | isa = PBXSourcesBuildPhase; 446 | buildActionMask = 2147483647; 447 | files = ( 448 | C36D8DEE233416B6000B6F2D /* Intents.intentdefinition in Sources */, 449 | C3C995A1227E9D7E00EE6D63 /* ThemedTableViewController.swift in Sources */, 450 | C325FFD2225702400060DFCF /* ScannerViewController.swift in Sources */, 451 | C36F1A3D2256BA150007DC21 /* ViewController.swift in Sources */, 452 | C36F1A3B2256BA150007DC21 /* AppDelegate.swift in Sources */, 453 | C3AB0BF82328964900D9BBA8 /* HistoryItemCell.swift in Sources */, 454 | C36F1A532256DB310007DC21 /* HistoryManager.swift in Sources */, 455 | C3AE4DED2333EB4E0011BBDF /* RowRepresentable.swift in Sources */, 456 | C3AE4DF72333EC350011BBDF /* Value.swift in Sources */, 457 | C3AE4DF52333EB830011BBDF /* ActionRow.swift in Sources */, 458 | C3AE4DF12333EB790011BBDF /* ValueRow.swift in Sources */, 459 | C3AE4DF32333EB7E0011BBDF /* SubtitleRow.swift in Sources */, 460 | C3AB0BFA23289B1100D9BBA8 /* GradientView.swift in Sources */, 461 | C3AB0BF62328964000D9BBA8 /* ButtonCell.swift in Sources */, 462 | C3D4ED8B22571BD0009EEF45 /* PreviewView.swift in Sources */, 463 | C36F1A512256D05F0007DC21 /* ResultsViewController.swift in Sources */, 464 | C36F1A4F2256BE350007DC21 /* UIViewController+ShorthandAlerts.swift in Sources */, 465 | C3D4ED8722571849009EEF45 /* ResultsHeaderView.swift in Sources */, 466 | C3AE4DEF2333EB620011BBDF /* Section.swift in Sources */, 467 | C3AB0BF42328963900D9BBA8 /* TextFieldCell.swift in Sources */, 468 | C3AE4DD1233218AC0011BBDF /* ThemedNavigationBar.swift in Sources */, 469 | ); 470 | runOnlyForDeploymentPostprocessing = 0; 471 | }; 472 | C3AE4DD52333E7470011BBDF /* Sources */ = { 473 | isa = PBXSourcesBuildPhase; 474 | buildActionMask = 2147483647; 475 | files = ( 476 | C3AE4DE72333E74F0011BBDF /* SerialAnalyis+ManufactureLocation.swift in Sources */, 477 | C3AE4DE62333E74F0011BBDF /* SerialAnalysis+OSFamily.swift in Sources */, 478 | C3AE4DEA2333E7900011BBDF /* String+LessPainfulSubstring.swift in Sources */, 479 | C3AE4DE82333E74F0011BBDF /* SerialAnalysis.swift in Sources */, 480 | C3AE4DE92333E74F0011BBDF /* SerialAnalysis+ManufactureDate.swift in Sources */, 481 | ); 482 | runOnlyForDeploymentPostprocessing = 0; 483 | }; 484 | C3AE4DF92333F45B0011BBDF /* Sources */ = { 485 | isa = PBXSourcesBuildPhase; 486 | buildActionMask = 2147483647; 487 | files = ( 488 | C3AE4E1F2333F47D0011BBDF /* Intents.intentdefinition in Sources */, 489 | C3AE4E002333F45B0011BBDF /* IntentHandler.swift in Sources */, 490 | C36D8DF02334184C000B6F2D /* AnalyzeIntentHandler.swift in Sources */, 491 | ); 492 | runOnlyForDeploymentPostprocessing = 0; 493 | }; 494 | /* End PBXSourcesBuildPhase section */ 495 | 496 | /* Begin PBXTargetDependency section */ 497 | C36D8DF2233439AD000B6F2D /* PBXTargetDependency */ = { 498 | isa = PBXTargetDependency; 499 | target = C3AE4DD82333E7470011BBDF /* SerialKit */; 500 | targetProxy = C36D8DF1233439AD000B6F2D /* PBXContainerItemProxy */; 501 | }; 502 | C3AE4DDF2333E7470011BBDF /* PBXTargetDependency */ = { 503 | isa = PBXTargetDependency; 504 | target = C3AE4DD82333E7470011BBDF /* SerialKit */; 505 | targetProxy = C3AE4DDE2333E7470011BBDF /* PBXContainerItemProxy */; 506 | }; 507 | C3AE4E152333F45B0011BBDF /* PBXTargetDependency */ = { 508 | isa = PBXTargetDependency; 509 | target = C3AE4DFC2333F45B0011BBDF /* Serial Intents */; 510 | targetProxy = C3AE4E142333F45B0011BBDF /* PBXContainerItemProxy */; 511 | }; 512 | /* End PBXTargetDependency section */ 513 | 514 | /* Begin PBXVariantGroup section */ 515 | C36F1A3E2256BA150007DC21 /* Main.storyboard */ = { 516 | isa = PBXVariantGroup; 517 | children = ( 518 | C36F1A3F2256BA150007DC21 /* Base */, 519 | ); 520 | name = Main.storyboard; 521 | sourceTree = ""; 522 | }; 523 | C36F1A432256BA170007DC21 /* LaunchScreen.storyboard */ = { 524 | isa = PBXVariantGroup; 525 | children = ( 526 | C36F1A442256BA170007DC21 /* Base */, 527 | ); 528 | name = LaunchScreen.storyboard; 529 | sourceTree = ""; 530 | }; 531 | /* End PBXVariantGroup section */ 532 | 533 | /* Begin XCBuildConfiguration section */ 534 | C36F1A472256BA170007DC21 /* Debug */ = { 535 | isa = XCBuildConfiguration; 536 | buildSettings = { 537 | ALWAYS_SEARCH_USER_PATHS = NO; 538 | CLANG_ANALYZER_NONNULL = YES; 539 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 540 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 541 | CLANG_CXX_LIBRARY = "libc++"; 542 | CLANG_ENABLE_MODULES = YES; 543 | CLANG_ENABLE_OBJC_ARC = YES; 544 | CLANG_ENABLE_OBJC_WEAK = YES; 545 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 546 | CLANG_WARN_BOOL_CONVERSION = YES; 547 | CLANG_WARN_COMMA = YES; 548 | CLANG_WARN_CONSTANT_CONVERSION = YES; 549 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 550 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 551 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 552 | CLANG_WARN_EMPTY_BODY = YES; 553 | CLANG_WARN_ENUM_CONVERSION = YES; 554 | CLANG_WARN_INFINITE_RECURSION = YES; 555 | CLANG_WARN_INT_CONVERSION = YES; 556 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 557 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 558 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 559 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 560 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 561 | CLANG_WARN_STRICT_PROTOTYPES = YES; 562 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 563 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 564 | CLANG_WARN_UNREACHABLE_CODE = YES; 565 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 566 | CODE_SIGN_IDENTITY = "iPhone Developer"; 567 | COPY_PHASE_STRIP = NO; 568 | DEBUG_INFORMATION_FORMAT = dwarf; 569 | ENABLE_STRICT_OBJC_MSGSEND = YES; 570 | ENABLE_TESTABILITY = YES; 571 | GCC_C_LANGUAGE_STANDARD = gnu11; 572 | GCC_DYNAMIC_NO_PIC = NO; 573 | GCC_NO_COMMON_BLOCKS = YES; 574 | GCC_OPTIMIZATION_LEVEL = 0; 575 | GCC_PREPROCESSOR_DEFINITIONS = ( 576 | "DEBUG=1", 577 | "$(inherited)", 578 | ); 579 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 580 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 581 | GCC_WARN_UNDECLARED_SELECTOR = YES; 582 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 583 | GCC_WARN_UNUSED_FUNCTION = YES; 584 | GCC_WARN_UNUSED_VARIABLE = YES; 585 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 586 | LD_RUNPATH_SEARCH_PATHS = /usr/lib/libswift/stable; 587 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 588 | MTL_FAST_MATH = YES; 589 | ONLY_ACTIVE_ARCH = YES; 590 | SDKROOT = iphoneos; 591 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 592 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 593 | }; 594 | name = Debug; 595 | }; 596 | C36F1A482256BA170007DC21 /* Release */ = { 597 | isa = XCBuildConfiguration; 598 | buildSettings = { 599 | ALWAYS_SEARCH_USER_PATHS = NO; 600 | CLANG_ANALYZER_NONNULL = YES; 601 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 602 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 603 | CLANG_CXX_LIBRARY = "libc++"; 604 | CLANG_ENABLE_MODULES = YES; 605 | CLANG_ENABLE_OBJC_ARC = YES; 606 | CLANG_ENABLE_OBJC_WEAK = YES; 607 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 608 | CLANG_WARN_BOOL_CONVERSION = YES; 609 | CLANG_WARN_COMMA = YES; 610 | CLANG_WARN_CONSTANT_CONVERSION = YES; 611 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 612 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 613 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 614 | CLANG_WARN_EMPTY_BODY = YES; 615 | CLANG_WARN_ENUM_CONVERSION = YES; 616 | CLANG_WARN_INFINITE_RECURSION = YES; 617 | CLANG_WARN_INT_CONVERSION = YES; 618 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 619 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 620 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 621 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 622 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 623 | CLANG_WARN_STRICT_PROTOTYPES = YES; 624 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 625 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 626 | CLANG_WARN_UNREACHABLE_CODE = YES; 627 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 628 | CODE_SIGN_IDENTITY = "iPhone Developer"; 629 | COPY_PHASE_STRIP = NO; 630 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 631 | ENABLE_NS_ASSERTIONS = NO; 632 | ENABLE_STRICT_OBJC_MSGSEND = YES; 633 | GCC_C_LANGUAGE_STANDARD = gnu11; 634 | GCC_NO_COMMON_BLOCKS = YES; 635 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 636 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 637 | GCC_WARN_UNDECLARED_SELECTOR = YES; 638 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 639 | GCC_WARN_UNUSED_FUNCTION = YES; 640 | GCC_WARN_UNUSED_VARIABLE = YES; 641 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 642 | LD_RUNPATH_SEARCH_PATHS = /usr/lib/libswift/stable; 643 | MTL_ENABLE_DEBUG_INFO = NO; 644 | MTL_FAST_MATH = YES; 645 | SDKROOT = iphoneos; 646 | SWIFT_COMPILATION_MODE = wholemodule; 647 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 648 | VALIDATE_PRODUCT = YES; 649 | }; 650 | name = Release; 651 | }; 652 | C36F1A4A2256BA170007DC21 /* Debug */ = { 653 | isa = XCBuildConfiguration; 654 | buildSettings = { 655 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 656 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 657 | CODE_SIGN_ENTITLEMENTS = Serial/Serial.entitlements; 658 | CODE_SIGN_STYLE = Automatic; 659 | DEVELOPMENT_TEAM = 6N2S3YHMFB; 660 | INFOPLIST_FILE = Serial/Info.plist; 661 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 662 | LD_RUNPATH_SEARCH_PATHS = ( 663 | "$(inherited)", 664 | "@executable_path/Frameworks", 665 | ); 666 | MARKETING_VERSION = 1.0.3; 667 | PRODUCT_BUNDLE_IDENTIFIER = dev.ayden.ios.serial; 668 | PRODUCT_NAME = "$(TARGET_NAME)"; 669 | SWIFT_VERSION = 5.0; 670 | TARGETED_DEVICE_FAMILY = "1,2"; 671 | }; 672 | name = Debug; 673 | }; 674 | C36F1A4B2256BA170007DC21 /* Release */ = { 675 | isa = XCBuildConfiguration; 676 | buildSettings = { 677 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 678 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 679 | CODE_SIGN_ENTITLEMENTS = Serial/Serial.entitlements; 680 | CODE_SIGN_STYLE = Automatic; 681 | DEVELOPMENT_TEAM = 6N2S3YHMFB; 682 | INFOPLIST_FILE = Serial/Info.plist; 683 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 684 | LD_RUNPATH_SEARCH_PATHS = ( 685 | "$(inherited)", 686 | "@executable_path/Frameworks", 687 | ); 688 | MARKETING_VERSION = 1.0.3; 689 | PRODUCT_BUNDLE_IDENTIFIER = dev.ayden.ios.serial; 690 | PRODUCT_NAME = "$(TARGET_NAME)"; 691 | SWIFT_VERSION = 5.0; 692 | TARGETED_DEVICE_FAMILY = "1,2"; 693 | }; 694 | name = Release; 695 | }; 696 | C3AE4DE32333E7470011BBDF /* Debug */ = { 697 | isa = XCBuildConfiguration; 698 | buildSettings = { 699 | APPLICATION_EXTENSION_API_ONLY = YES; 700 | CODE_SIGN_STYLE = Automatic; 701 | CURRENT_PROJECT_VERSION = 1; 702 | DEFINES_MODULE = YES; 703 | DEVELOPMENT_TEAM = 6N2S3YHMFB; 704 | DYLIB_COMPATIBILITY_VERSION = 1; 705 | DYLIB_CURRENT_VERSION = 1; 706 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 707 | INFOPLIST_FILE = SerialKit/Info.plist; 708 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 709 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 710 | LD_RUNPATH_SEARCH_PATHS = ( 711 | "$(inherited)", 712 | "@executable_path/Frameworks", 713 | "@loader_path/Frameworks", 714 | ); 715 | PRODUCT_BUNDLE_IDENTIFIER = dev.ayden.ios.serial.serialkit; 716 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 717 | SKIP_INSTALL = YES; 718 | SWIFT_VERSION = 5.0; 719 | TARGETED_DEVICE_FAMILY = "1,2"; 720 | VERSIONING_SYSTEM = "apple-generic"; 721 | VERSION_INFO_PREFIX = ""; 722 | }; 723 | name = Debug; 724 | }; 725 | C3AE4DE42333E7470011BBDF /* Release */ = { 726 | isa = XCBuildConfiguration; 727 | buildSettings = { 728 | APPLICATION_EXTENSION_API_ONLY = YES; 729 | CODE_SIGN_STYLE = Automatic; 730 | CURRENT_PROJECT_VERSION = 1; 731 | DEFINES_MODULE = YES; 732 | DEVELOPMENT_TEAM = 6N2S3YHMFB; 733 | DYLIB_COMPATIBILITY_VERSION = 1; 734 | DYLIB_CURRENT_VERSION = 1; 735 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 736 | INFOPLIST_FILE = SerialKit/Info.plist; 737 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 738 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 739 | LD_RUNPATH_SEARCH_PATHS = ( 740 | "$(inherited)", 741 | "@executable_path/Frameworks", 742 | "@loader_path/Frameworks", 743 | ); 744 | PRODUCT_BUNDLE_IDENTIFIER = dev.ayden.ios.serial.serialkit; 745 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 746 | SKIP_INSTALL = YES; 747 | SWIFT_VERSION = 5.0; 748 | TARGETED_DEVICE_FAMILY = "1,2"; 749 | VERSIONING_SYSTEM = "apple-generic"; 750 | VERSION_INFO_PREFIX = ""; 751 | }; 752 | name = Release; 753 | }; 754 | C3AE4E1B2333F45B0011BBDF /* Debug */ = { 755 | isa = XCBuildConfiguration; 756 | buildSettings = { 757 | CODE_SIGN_STYLE = Automatic; 758 | DEVELOPMENT_TEAM = 6N2S3YHMFB; 759 | INFOPLIST_FILE = "Serial Intents/Info.plist"; 760 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 761 | LD_RUNPATH_SEARCH_PATHS = ( 762 | "$(inherited)", 763 | "@executable_path/Frameworks", 764 | "@executable_path/../../Frameworks", 765 | ); 766 | PRODUCT_BUNDLE_IDENTIFIER = dev.ayden.ios.serial.intents; 767 | PRODUCT_NAME = "$(TARGET_NAME)"; 768 | SKIP_INSTALL = YES; 769 | SWIFT_VERSION = 5.0; 770 | TARGETED_DEVICE_FAMILY = "1,2"; 771 | }; 772 | name = Debug; 773 | }; 774 | C3AE4E1C2333F45B0011BBDF /* Release */ = { 775 | isa = XCBuildConfiguration; 776 | buildSettings = { 777 | CODE_SIGN_STYLE = Automatic; 778 | DEVELOPMENT_TEAM = 6N2S3YHMFB; 779 | INFOPLIST_FILE = "Serial Intents/Info.plist"; 780 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 781 | LD_RUNPATH_SEARCH_PATHS = ( 782 | "$(inherited)", 783 | "@executable_path/Frameworks", 784 | "@executable_path/../../Frameworks", 785 | ); 786 | PRODUCT_BUNDLE_IDENTIFIER = dev.ayden.ios.serial.intents; 787 | PRODUCT_NAME = "$(TARGET_NAME)"; 788 | SKIP_INSTALL = YES; 789 | SWIFT_VERSION = 5.0; 790 | TARGETED_DEVICE_FAMILY = "1,2"; 791 | }; 792 | name = Release; 793 | }; 794 | /* End XCBuildConfiguration section */ 795 | 796 | /* Begin XCConfigurationList section */ 797 | C36F1A322256BA150007DC21 /* Build configuration list for PBXProject "Serial" */ = { 798 | isa = XCConfigurationList; 799 | buildConfigurations = ( 800 | C36F1A472256BA170007DC21 /* Debug */, 801 | C36F1A482256BA170007DC21 /* Release */, 802 | ); 803 | defaultConfigurationIsVisible = 0; 804 | defaultConfigurationName = Release; 805 | }; 806 | C36F1A492256BA170007DC21 /* Build configuration list for PBXNativeTarget "Serial" */ = { 807 | isa = XCConfigurationList; 808 | buildConfigurations = ( 809 | C36F1A4A2256BA170007DC21 /* Debug */, 810 | C36F1A4B2256BA170007DC21 /* Release */, 811 | ); 812 | defaultConfigurationIsVisible = 0; 813 | defaultConfigurationName = Release; 814 | }; 815 | C3AE4DE22333E7470011BBDF /* Build configuration list for PBXNativeTarget "SerialKit" */ = { 816 | isa = XCConfigurationList; 817 | buildConfigurations = ( 818 | C3AE4DE32333E7470011BBDF /* Debug */, 819 | C3AE4DE42333E7470011BBDF /* Release */, 820 | ); 821 | defaultConfigurationIsVisible = 0; 822 | defaultConfigurationName = Release; 823 | }; 824 | C3AE4E1A2333F45B0011BBDF /* Build configuration list for PBXNativeTarget "Serial Intents" */ = { 825 | isa = XCConfigurationList; 826 | buildConfigurations = ( 827 | C3AE4E1B2333F45B0011BBDF /* Debug */, 828 | C3AE4E1C2333F45B0011BBDF /* Release */, 829 | ); 830 | defaultConfigurationIsVisible = 0; 831 | defaultConfigurationName = Release; 832 | }; 833 | /* End XCConfigurationList section */ 834 | }; 835 | rootObject = C36F1A2F2256BA150007DC21 /* Project object */; 836 | } 837 | -------------------------------------------------------------------------------- /Serial.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Serial.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Serial.xcodeproj/xcshareddata/xcschemes/Serial.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Serial/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-04. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | applyAppearance() 18 | return true 19 | } 20 | 21 | private func applyAppearance() { 22 | window?.tintColor = UIColor(named: "tint") 23 | UITextField.appearance().keyboardAppearance = .dark 24 | UIScrollView.appearance().indicatorStyle = .white 25 | UITableViewCell.appearance().backgroundColor = UIColor(named: "tableViewCellBackground") 26 | UITableViewCell.appearance().selectedBackgroundView = { 27 | let view = UIView() 28 | view.backgroundColor = UIColor(named: "tableViewCellSelectionBackground") 29 | return view 30 | }() 31 | UITextField.appearance(whenContainedInInstancesOf: [UITableViewCell.self]).textColor = UIColor(named: "textFieldText") 32 | UILabel.appearance(whenContainedInInstancesOf: [UITextField.self]).textColor = UIColor(named: "textFieldPlaceholder") 33 | 34 | if #available(iOS 13.0, *) {} else { 35 | // Fix toolbar being white on iOS 12 and lower (in iOS 13 it takes its keyboard's style, which is okay, because it doesn't force a dark trait collection. Setting this to black on iOS 13 will force a dark trait collection and use a different tint colour than we necessarily want and I really wish this was easier) 36 | UIToolbar.appearance().barStyle = .black 37 | } 38 | } 39 | 40 | func applicationWillResignActive(_ application: UIApplication) { 41 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 42 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 43 | } 44 | 45 | func applicationDidEnterBackground(_ application: UIApplication) { 46 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 47 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 48 | } 49 | 50 | func applicationWillEnterForeground(_ application: UIApplication) { 51 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 52 | } 53 | 54 | func applicationDidBecomeActive(_ application: UIApplication) { 55 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 56 | } 57 | 58 | func applicationWillTerminate(_ application: UIApplication) { 59 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 60 | } 61 | 62 | private func returnToHome() -> ViewController? { 63 | guard let root = window?.rootViewController as? UINavigationController else { return nil } 64 | root.popToRootViewController(animated: false) 65 | root.presentedViewController?.dismiss(animated: false, completion: nil) 66 | return root.viewControllers.first as? ViewController 67 | } 68 | } 69 | 70 | extension AppDelegate { 71 | private enum LaunchShortcut: String { 72 | case scan 73 | 74 | init?(shortcutItem: UIApplicationShortcutItem) { 75 | guard let identifier = shortcutItem.type.split(separator: ".").last else { return nil } 76 | self.init(rawValue: String(identifier)) 77 | } 78 | } 79 | 80 | func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { 81 | guard let shortcut = LaunchShortcut(shortcutItem: shortcutItem) else { completionHandler(false); return } 82 | switch shortcut { 83 | case .scan: 84 | returnToHome()?.showCamera() 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "29.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "58.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "87.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "80.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "120.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "57.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "57x57", 53 | "idiom" : "iphone", 54 | "filename" : "114.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "120.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "60x60", 65 | "idiom" : "iphone", 66 | "filename" : "180.png", 67 | "scale" : "3x" 68 | }, 69 | { 70 | "size" : "20x20", 71 | "idiom" : "ipad", 72 | "filename" : "20.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "20x20", 77 | "idiom" : "ipad", 78 | "filename" : "40.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "29.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "29x29", 89 | "idiom" : "ipad", 90 | "filename" : "58.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "40.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "ipad", 102 | "filename" : "80.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "50.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "50x50", 113 | "idiom" : "ipad", 114 | "filename" : "100.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "72.png", 121 | "scale" : "1x" 122 | }, 123 | { 124 | "size" : "72x72", 125 | "idiom" : "ipad", 126 | "filename" : "144.png", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "76.png", 133 | "scale" : "1x" 134 | }, 135 | { 136 | "size" : "76x76", 137 | "idiom" : "ipad", 138 | "filename" : "152.png", 139 | "scale" : "2x" 140 | }, 141 | { 142 | "size" : "83.5x83.5", 143 | "idiom" : "ipad", 144 | "filename" : "167.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "1024x1024", 149 | "idiom" : "ios-marketing", 150 | "filename" : "1024.png", 151 | "scale" : "1x" 152 | } 153 | ], 154 | "info" : { 155 | "version" : 1, 156 | "author" : "xcode" 157 | } 158 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/background.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.145", 13 | "alpha" : "1.000", 14 | "blue" : "0.200", 15 | "green" : "0.153" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "platform" : "ios", 29 | "reference" : "systemGroupedBackgroundColor" 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/barTint.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.094", 13 | "alpha" : "1.000", 14 | "blue" : "0.157", 15 | "green" : "0.094" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "color-space" : "srgb", 29 | "components" : { 30 | "red" : "0.000", 31 | "alpha" : "0.000", 32 | "blue" : "0.000", 33 | "green" : "0.000" 34 | } 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/contentSubtitle.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.525", 13 | "alpha" : "1.000", 14 | "blue" : "0.612", 15 | "green" : "0.529" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "platform" : "ios", 29 | "reference" : "secondaryLabelColor" 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/contentTitle.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "gray-gamma-22", 11 | "components" : { 12 | "white" : "1.000", 13 | "alpha" : "1.000" 14 | } 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/tableViewCellBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "extended-srgb", 11 | "components" : { 12 | "red" : "0.173", 13 | "alpha" : "1.000", 14 | "blue" : "0.231", 15 | "green" : "0.180" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "platform" : "ios", 29 | "reference" : "systemFillColor" 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/tableViewCellSelectionBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "extended-srgb", 11 | "components" : { 12 | "red" : "0.267", 13 | "alpha" : "1.000", 14 | "blue" : "0.357", 15 | "green" : "0.267" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "platform" : "ios", 29 | "reference" : "systemFillColor" 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/tableViewSeparator.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "extended-srgb", 11 | "components" : { 12 | "red" : "0.263", 13 | "alpha" : "1.000", 14 | "blue" : "0.353", 15 | "green" : "0.263" 16 | } 17 | } 18 | }, 19 | { 20 | "idiom" : "universal", 21 | "appearances" : [ 22 | { 23 | "appearance" : "luminosity", 24 | "value" : "dark" 25 | } 26 | ], 27 | "color" : { 28 | "platform" : "ios", 29 | "reference" : "separatorColor" 30 | } 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/textFieldPlaceholder.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "gray-gamma-22", 11 | "components" : { 12 | "white" : "1.000", 13 | "alpha" : "0.244" 14 | } 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/textFieldText.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "gray-gamma-22", 11 | "components" : { 12 | "white" : "1.000", 13 | "alpha" : "1.000" 14 | } 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Colours/tint.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "colors" : [ 7 | { 8 | "idiom" : "universal", 9 | "color" : { 10 | "color-space" : "srgb", 11 | "components" : { 12 | "red" : "0.518", 13 | "alpha" : "1.000", 14 | "blue" : "0.984", 15 | "green" : "0.639" 16 | } 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_Off.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Torch_Off.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Torch_Off@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Torch_Off@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_Off.imageset/Torch_Off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/Torch_Off.imageset/Torch_Off.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_Off.imageset/Torch_Off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/Torch_Off.imageset/Torch_Off@2x.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_Off.imageset/Torch_Off@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/Torch_Off.imageset/Torch_Off@3x.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_On.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Torch_On.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Torch_On@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Torch_On@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_On.imageset/Torch_On.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/Torch_On.imageset/Torch_On.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_On.imageset/Torch_On@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/Torch_On.imageset/Torch_On@2x.png -------------------------------------------------------------------------------- /Serial/Assets.xcassets/Torch_On.imageset/Torch_On@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aydenp/Serial/5fb6abb938f82b8ae017f9e86d2f9ec5b5ffe442/Serial/Assets.xcassets/Torch_On.imageset/Torch_On@3x.png -------------------------------------------------------------------------------- /Serial/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Serial/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 107 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 198 | 199 | 200 | 201 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /Serial/HistoryManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecentAnalysesManager.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-04. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class HistoryManager { 12 | private static let defaultsKey = "history" 13 | static let shared = HistoryManager() 14 | static let notification = Notification.Name(rawValue: "SerialHistoryManagerItemsChangedNotificationName") 15 | 16 | private init() {} 17 | 18 | func record(serialNumber: String) { 19 | deleteAll(serialNumber: serialNumber) 20 | items.append(Item(serialNumber: serialNumber, date: Date())) 21 | } 22 | 23 | func deleteAll(serialNumber: String) { 24 | items.removeAll { $0.serialNumber == serialNumber } 25 | } 26 | 27 | private var _items: [Item]? 28 | var items: [Item] { 29 | get { 30 | // Allow providing fake recents with environment variable 31 | if let serialNumbers = ProcessInfo.processInfo.environment["DEMO_FAKE_RECENTS"]?.split(separator: ",") { 32 | let date = Calendar.current.date(bySettingHour: 9, minute: 41, second: 0, of: Date()) ?? Date() 33 | _items = serialNumbers.map { Item(serialNumber: String($0), date: date) } 34 | } 35 | 36 | // Load from user defaults if not populated 37 | if _items == nil { 38 | _items = UserDefaults.standard.array(forKey: HistoryManager.defaultsKey)?.filter { $0 is Data }.compactMap { try? JSONDecoder().decode(Item.self, from: $0 as! Data) } 39 | } 40 | 41 | // Return (possibly newly) stored value or an empty array 42 | return _items ?? [] 43 | } 44 | set { 45 | _items = Array(newValue.suffix(10)) 46 | NotificationCenter.default.post(name: HistoryManager.notification, object: nil) 47 | UserDefaults.standard.set(_items!.compactMap { try? JSONEncoder().encode($0) }, forKey: HistoryManager.defaultsKey) 48 | } 49 | } 50 | 51 | struct Item: Codable { 52 | let serialNumber: String, date: Date 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Serial/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 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | NSCameraUsageDescription 29 | Serial needs camera access in order to conveniently scan serial numbers. 30 | NSUserActivityTypes 31 | 32 | AnalyzeIntent 33 | 34 | UIApplicationShortcutItems 35 | 36 | 37 | UIApplicationShortcutItemIconType 38 | UIApplicationShortcutIconTypeCapturePhoto 39 | UIApplicationShortcutItemTitle 40 | Scan Barcode 41 | UIApplicationShortcutItemType 42 | $(PRODUCT_BUNDLE_IDENTIFIER).scan 43 | 44 | 45 | UILaunchStoryboardName 46 | LaunchScreen 47 | UIMainStoryboardFile 48 | Main 49 | UIRequiredDeviceCapabilities 50 | 51 | armv7 52 | 53 | UIStatusBarStyle 54 | UIStatusBarStyleLightContent 55 | UISupportedInterfaceOrientations 56 | 57 | UIInterfaceOrientationPortrait 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | UISupportedInterfaceOrientations~ipad 62 | 63 | UIInterfaceOrientationPortrait 64 | UIInterfaceOrientationPortraitUpsideDown 65 | UIInterfaceOrientationLandscapeLeft 66 | UIInterfaceOrientationLandscapeRight 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /Serial/Serial.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.siri 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Serial/UI/Cells/ButtonCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonCell.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-10. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ButtonCell: UITableViewCell { 12 | @IBOutlet weak var button: UIButton! 13 | 14 | override func didMoveToWindow() { 15 | super.didMoveToWindow() 16 | updateSelectionStyle() 17 | } 18 | 19 | var isEnabled: Bool { 20 | get { 21 | return button.isEnabled 22 | } 23 | set { 24 | button.isEnabled = newValue 25 | updateSelectionStyle() 26 | } 27 | } 28 | 29 | private func updateSelectionStyle() { 30 | selectionStyle = button.isEnabled ? .default : .none 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Serial/UI/Cells/HistoryItemCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HistoryItemCell.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-10. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HistoryItemCell: UITableViewCell { 12 | @IBOutlet weak var serialNumberLabel: UILabel! 13 | @IBOutlet weak var dateLabel: UILabel! 14 | private var hasAwaken = false 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | populateData() 19 | hasAwaken = true 20 | } 21 | 22 | var item: HistoryManager.Item? { 23 | didSet { 24 | guard hasAwaken else { return } 25 | populateData() 26 | } 27 | } 28 | 29 | private func populateData() { 30 | serialNumberLabel.text = item?.serialNumber 31 | dateLabel.text = item != nil ? DateFormatter.localizedString(from: item!.date, dateStyle: .short, timeStyle: .short) : nil 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Serial/UI/Cells/TextFieldCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextFieldCell.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-10. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TextFieldCell: UITableViewCell { 12 | @IBOutlet weak var textField: UITextField! 13 | @IBOutlet weak var _textLabel: UILabel! 14 | 15 | override var textLabel: UILabel? { 16 | return _textLabel 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Serial/UI/ResultsHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultsHeaderView.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-05. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SerialKit 11 | 12 | class ResultsHeaderView: UIView { 13 | var analysis: SerialAnalysis! 14 | private var titleLabel = UILabel(), numberLabel = UILabel() 15 | 16 | init(analysis: SerialAnalysis) { 17 | super.init(frame: .zero) 18 | self.analysis = analysis 19 | 20 | titleLabel.font = .systemFont(ofSize: 26, weight: .medium) 21 | titleLabel.adjustsFontSizeToFitWidth = true 22 | titleLabel.minimumScaleFactor = 0.6 23 | titleLabel.textColor = .white 24 | 25 | numberLabel.font = .systemFont(ofSize: 15) 26 | numberLabel.textColor = .lightGray 27 | 28 | self.analysis.register { self.update(with: $0) } 29 | self.update(with: analysis) 30 | 31 | let stackView = UIStackView(arrangedSubviews: [titleLabel, numberLabel]) 32 | stackView.translatesAutoresizingMaskIntoConstraints = false 33 | stackView.axis = .vertical 34 | stackView.alignment = .center 35 | stackView.spacing = 4 36 | addSubview(stackView) 37 | stackView.topAnchor.constraint(equalTo: topAnchor, constant: 40).isActive = true 38 | stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12).isActive = true 39 | stackView.leftAnchor.constraint(equalTo: leftAnchor, constant: 20).isActive = true 40 | stackView.rightAnchor.constraint(equalTo: rightAnchor, constant: -20).isActive = true 41 | } 42 | 43 | required init?(coder aDecoder: NSCoder) { 44 | fatalError("init(coder:) has not been implemented") 45 | } 46 | 47 | func update(with analysis: SerialAnalysis) { 48 | titleLabel.text = analysis.deviceName ?? "Unknown" 49 | numberLabel.text = analysis.serialNumber 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Serial/UI/ResultsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultsViewController.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-04. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SafariServices 11 | import SerialKit 12 | 13 | class ResultsViewController: ThemedTableViewController { 14 | var analysis: SerialAnalysis! 15 | 16 | init(analysis: SerialAnalysis) { 17 | if #available(iOS 13.0, *) { 18 | super.init(style: .insetGrouped) 19 | } else { 20 | super.init(style: .grouped) 21 | } 22 | self.analysis = analysis 23 | } 24 | 25 | required init?(coder aDecoder: NSCoder) { 26 | fatalError("init(coder:) has not been implemented") 27 | } 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | title = "Analysis" 33 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissResults)) 34 | HistoryManager.shared.record(serialNumber: analysis.serialNumber) 35 | tableView.tableHeaderView = ResultsHeaderView(analysis: analysis) 36 | 37 | var sections = [Section]() 38 | 39 | // Location info 40 | var locationRows = [ValueRow(title: "Location", value: analysis.manufactureLocation?.locationName ?? "Unknown")] 41 | if let owner = analysis.manufactureLocation?.factoryOwner { locationRows.append(ValueRow(title: "Operator", value: owner)) } 42 | sections.append(Section(rows: locationRows, header: "Factory")) 43 | 44 | // Date info: 45 | var dateRows: [RowRepresentable] = [SubtitleRow(title: analysis.manufactureDate?.description ?? "Unknown", subtitle: analysis.manufactureDate?.dateRangeDescription)] 46 | if let age = analysis.manufactureDate?.ageDescription { dateRows.append(ValueRow(title: "Age", value: age)) } 47 | sections.append(Section(rows: dateRows, header: "Manufacture Date")) 48 | 49 | sections.append(Section(rows: [ValueRow(title: "Family", value: .async { (update) in 50 | analysis.register { update($0.osFamily?.friendlyName) } 51 | }), ValueRow(title: "Probable Version", value: .async { (update) in 52 | analysis.register { update($0.probableVersion) } 53 | })], header: "Operating System", footer: "The current software version during the device's week of manufacture.")) 54 | 55 | sections.append(Section(rows: [ActionRow(title: "Open Tech Specs", url: analysis.techSpecsURL, viewController: self), ActionRow(title: "Check Coverage", url: analysis.checkCoverageURL, viewController: self), ActionRow(title: "EveryMac Lookup", url: analysis.everyMacURL, viewController: self)], header: "More Information", footer: nil)) 56 | 57 | self.sections = sections 58 | } 59 | 60 | override func viewDidLayoutSubviews() { 61 | super.viewDidLayoutSubviews() 62 | 63 | if let headerView = tableView.tableHeaderView { 64 | let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height 65 | var headerFrame = headerView.frame 66 | 67 | if height != headerFrame.size.height { 68 | headerFrame.size.height = height 69 | headerView.frame = headerFrame 70 | tableView.tableHeaderView = headerView 71 | } 72 | } 73 | } 74 | 75 | var sections: [Section] = [] { 76 | didSet { tableView.reloadData() } 77 | } 78 | 79 | @objc func dismissResults() { 80 | dismiss(animated: true, completion: nil) 81 | } 82 | 83 | // MARK: - Table view data source 84 | 85 | override func numberOfSections(in tableView: UITableView) -> Int { 86 | return sections.count 87 | } 88 | 89 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 90 | return sections[section].rows.count 91 | } 92 | 93 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 94 | return sections[indexPath.section].rows[indexPath.row].createCell() 95 | } 96 | 97 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 98 | return sections[section].header 99 | } 100 | 101 | override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 102 | return sections[section].footer 103 | } 104 | 105 | // MARK: - Table view delegate 106 | 107 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 108 | guard let rowItem = sections[indexPath.section].rows[indexPath.row] as? SelectableRowRepresentable else { return } 109 | rowItem.selectedCell() 110 | } 111 | 112 | // MARK: - Convenience 113 | 114 | static func getPresentableController(analysis: SerialAnalysis) -> UINavigationController { 115 | let vc = ResultsViewController(analysis: analysis) 116 | let nav = UINavigationController(navigationBarClass: ThemedNavigationBar.self, toolbarClass: nil) 117 | nav.setViewControllers([vc], animated: false) 118 | return nav 119 | } 120 | 121 | static func present(analysis: SerialAnalysis, onViewController viewController: UIViewController) { 122 | viewController.present(getPresentableController(analysis: analysis), animated: true, completion: nil) 123 | } 124 | 125 | static func presentAnalysis(for serialNumber: String, onViewController viewController: UIViewController) { 126 | guard let analysis = SerialAnalysis(serialNumber: serialNumber) else { return } 127 | present(analysis: analysis, onViewController: viewController) 128 | } 129 | } 130 | 131 | extension SerialAnalysis.ManufactureDate { 132 | public var dateRangeDescription: String? { 133 | guard let start = startDate, let end = endDate else { return nil } 134 | return "\(SerialAnalysis.ManufactureDate.startDateFormatter.string(from: start)) to \(SerialAnalysis.ManufactureDate.endDateFormatter.string(from: end))" 135 | } 136 | 137 | public var ageDescription: String? { 138 | guard let start = startDate, let age = Calendar.current.dateComponents([.day], from: start, to: Date()).day else { return nil } 139 | return "\(NumberFormatter.localizedString(from: max(0, age - 7) as NSNumber, number: .decimal))–\(NumberFormatter.localizedString(from: age as NSNumber, number: .decimal)) days" 140 | } 141 | 142 | // MARK: - Formatting 143 | 144 | static private let startDateFormatter = { () -> DateFormatter in 145 | let formatter = DateFormatter() 146 | formatter.dateFormat = "MMMM d" 147 | return formatter 148 | }() 149 | static private let endDateFormatter = { () -> DateFormatter in 150 | let formatter = DateFormatter() 151 | formatter.dateFormat = "MMMM d, yyyy" 152 | return formatter 153 | }() 154 | } 155 | -------------------------------------------------------------------------------- /Serial/UI/Row Models/ActionRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionRow.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SafariServices 11 | 12 | struct ActionRow: SelectableRowRepresentable { 13 | let title: String, action: () -> () 14 | 15 | init(title: String, action: @escaping () -> ()) { 16 | self.title = title 17 | self.action = action 18 | } 19 | 20 | init(title: String, url: URL?, viewController: UIViewController) { 21 | self.title = title 22 | self.action = { 23 | guard let url = url else { return } 24 | let svc = SFSafariViewController(url: url) 25 | viewController.present(svc, animated: true, completion: nil) 26 | } 27 | } 28 | 29 | func createCell() -> UITableViewCell { 30 | let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil) 31 | cell.textLabel?.text = title 32 | cell.selectionStyle = .default 33 | cell.accessoryType = .disclosureIndicator 34 | return cell 35 | } 36 | 37 | func selectedCell() { 38 | action() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Serial/UI/Row Models/RowRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RowRepresentable.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol RowRepresentable { 12 | func createCell() -> UITableViewCell 13 | } 14 | 15 | protocol SelectableRowRepresentable: RowRepresentable { 16 | func selectedCell() 17 | } 18 | -------------------------------------------------------------------------------- /Serial/UI/Row Models/Section.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Section.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Section { 12 | let header: String?, footer: String? 13 | let rows: [RowRepresentable] 14 | 15 | init(rows: [RowRepresentable], header: String? = nil, footer: String? = nil) { 16 | self.rows = rows 17 | self.header = header 18 | self.footer = footer 19 | } 20 | 21 | init(row: RowRepresentable, header: String? = nil, footer: String? = nil) { 22 | self.init(rows: [row], header: header, footer: footer) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Serial/UI/Row Models/SubtitleRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SubtitleRow.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct SubtitleRow: RowRepresentable { 12 | let title: String, subtitle: String? 13 | 14 | func createCell() -> UITableViewCell { 15 | let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil) 16 | cell.textLabel?.text = title 17 | cell.detailTextLabel?.text = subtitle 18 | cell.selectionStyle = .none 19 | return cell 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Serial/UI/Row Models/Value.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Value.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Value { 12 | init(immediate value: String?) { 13 | self.value = value 14 | } 15 | 16 | init(block: AsyncValueProviderBlock) { 17 | block({ [weak self] in 18 | self?.value = $0 19 | }) 20 | } 21 | 22 | var onUpdate: UpdateBlock? { 23 | didSet { fireUpdateHandler() } 24 | } 25 | 26 | private var value: String? { 27 | didSet { fireUpdateHandler() } 28 | } 29 | 30 | private func fireUpdateHandler() { 31 | DispatchQueue.main.async { 32 | self.onUpdate?(self.value) 33 | } 34 | } 35 | 36 | static func async(_ block: AsyncValueProviderBlock) -> Value { 37 | return Value(block: block) 38 | } 39 | 40 | typealias UpdateBlock = (String?) -> () 41 | typealias AsyncValueProviderBlock = (_ update: @escaping UpdateBlock) -> () 42 | } 43 | -------------------------------------------------------------------------------- /Serial/UI/Row Models/ValueRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ValueRow.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct ValueRow: RowRepresentable { 12 | let title: String, value: Value 13 | 14 | init(title: String, value: Value) { 15 | self.title = title 16 | self.value = value 17 | } 18 | 19 | init(title: String, value: String?) { 20 | self.title = title 21 | self.value = Value(immediate: value) 22 | } 23 | 24 | func createCell() -> UITableViewCell { 25 | let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) 26 | cell.textLabel?.text = title 27 | value.onUpdate = { value in 28 | cell.detailTextLabel?.text = value ?? "—" 29 | } 30 | cell.selectionStyle = .none 31 | return cell 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Serial/UI/ScannerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScannerViewController.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-04. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | import Vision 12 | import SerialKit 13 | 14 | class ScannerViewController: UIViewController { 15 | @IBOutlet weak var previewView: PreviewView! 16 | @IBOutlet weak var gradientView: GradientView! 17 | @IBOutlet weak var torchItem: UIBarButtonItem! 18 | @IBOutlet weak var permissionView: UIView! 19 | @IBOutlet weak var hintLabel: UILabel! 20 | @IBOutlet weak var loadingIcon: UIActivityIndicatorView! 21 | 22 | private var isFinished = false 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | // Setup legibility gradient view 28 | gradientView.colours = [.clear, UIColor(white: 0, alpha: 0.8)] 29 | gradientView.gradientLayer.startPoint = .zero 30 | gradientView.gradientLayer.endPoint = CGPoint(x: 0, y: 1) 31 | 32 | setup() 33 | } 34 | 35 | override func viewWillDisappear(_ animated: Bool) { 36 | super.viewWillDisappear(animated) 37 | isTorchEnabled = false 38 | } 39 | 40 | func scan(serialNumber: String) { 41 | if isFinished { return } 42 | isFinished = true 43 | let presenting = self.navigationController!.presentingViewController! 44 | DispatchQueue.main.async { 45 | self.destroyCam() 46 | self.dismiss(animated: false) { ResultsViewController.presentAnalysis(for: serialNumber, onViewController: presenting) } 47 | } 48 | } 49 | 50 | override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { 51 | super.viewWillTransition(to: size, with: coordinator) 52 | adjustOrientation() 53 | } 54 | 55 | func adjustOrientation() { 56 | guard let connection = self.previewView?.previewLayer?.connection, connection.isVideoOrientationSupported else { return } 57 | switch UIDevice.current.orientation { 58 | case .landscapeRight: connection.videoOrientation = .landscapeLeft 59 | case .landscapeLeft: connection.videoOrientation = .landscapeRight 60 | case .portraitUpsideDown: connection.videoOrientation = .portraitUpsideDown 61 | default: connection.videoOrientation = .portrait 62 | } 63 | } 64 | 65 | enum Mode { 66 | case needsPermission, loading, scanning 67 | } 68 | 69 | var device: AVCaptureDevice? { 70 | didSet { updateTorchItem() } 71 | } 72 | 73 | var isTorchEnabled: Bool { 74 | get { 75 | return device?.torchMode ?? .off == .on 76 | } 77 | set { 78 | guard let device = device, isTorchEnabled != newValue else { return } 79 | do { 80 | try device.lockForConfiguration() 81 | device.torchMode = newValue ? .on : .off 82 | device.unlockForConfiguration() 83 | updateTorchItem() 84 | } catch let error { 85 | self.showErrorAlert(title: "Couldn't Toggle Light", error: error, unknownMessage: "An error occurred while attempting to toggle the flashlight.") 86 | } 87 | } 88 | } 89 | 90 | @IBAction func torchTapped(_ sender: Any) { 91 | isTorchEnabled.toggle() 92 | } 93 | 94 | private func updateTorchItem() { 95 | let isTorchAvailable = device?.isTorchAvailable ?? false 96 | let isTorchEnabled = self.isTorchEnabled 97 | DispatchQueue.main.async { 98 | self.torchItem.isEnabled = isTorchAvailable 99 | self.torchItem.image = UIImage(named: isTorchEnabled ? "Torch_On" : "Torch_Off") 100 | self.torchItem.accessibilityLabel = "Toggle Flashlight" 101 | self.torchItem.accessibilityValue = isTorchEnabled ? "Turn flashlight off" : "Turn flashlight on" 102 | } 103 | } 104 | 105 | var mode = Mode.loading { 106 | didSet { 107 | let mode = self.mode 108 | OperationQueue.main.addOperation { 109 | self.loadingIcon.isHidden = mode != .loading 110 | self.permissionView.isHidden = mode != .needsPermission 111 | self.hintLabel.isHidden = mode != .scanning 112 | self.gradientView.isHidden = mode != .scanning 113 | } 114 | } 115 | } 116 | 117 | func setup() { 118 | mode = .loading 119 | AVCaptureDevice.requestAccess(for: .video) { granted in 120 | if granted { 121 | self.mode = .scanning 122 | self.setupCam() 123 | } else { 124 | self.mode = .needsPermission 125 | self.destroyCam() 126 | } 127 | } 128 | } 129 | 130 | var session: AVCaptureSession? { 131 | didSet { oldValue?.stopRunning() } 132 | } 133 | 134 | func destroyCam() { 135 | OperationQueue.main.addOperation { self.previewView?.previewLayer = nil } 136 | isTorchEnabled = false 137 | session = nil 138 | device = nil 139 | } 140 | 141 | func setupCam() { 142 | // Allow providing fake demo camera input (for use in simulator) 143 | if let demoImage = UIImage(named: "DemoFakeCamera") { 144 | OperationQueue.main.addOperation { 145 | let imageView = UIImageView(image: demoImage) 146 | imageView.frame = self.previewView.bounds 147 | imageView.contentMode = .scaleAspectFill 148 | imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 149 | self.previewView.addSubview(imageView) 150 | } 151 | 152 | self.mode = .scanning 153 | return 154 | } 155 | 156 | func fail(with error: Error? = nil) { 157 | self.destroyCam() 158 | self.showErrorAlert(title: "Camera Error", error: error, unknownMessage: "There was an error trying to access your device's camera.", action: .ok { _ in self.dismiss(animated: true, completion: nil)}, completion: nil) 159 | } 160 | 161 | self.destroyCam() 162 | do { 163 | let session = AVCaptureSession() 164 | guard let device = AVCaptureDevice.default(for: .video) else { fail(); return } 165 | 166 | let input = try AVCaptureDeviceInput(device: device) 167 | session.addInput(input) 168 | 169 | let output = AVCaptureMetadataOutput() 170 | output.setMetadataObjectsDelegate(self, queue: DispatchQueue.global()) 171 | session.addOutput(output) 172 | guard output.availableMetadataObjectTypes.contains(.code128) else { fail(); return } 173 | output.metadataObjectTypes = [.code128] 174 | 175 | let layer = AVCaptureVideoPreviewLayer(session: session) 176 | layer.videoGravity = .resizeAspectFill 177 | OperationQueue.main.addOperation { 178 | self.adjustOrientation() 179 | self.previewView.previewLayer = layer 180 | } 181 | 182 | session.startRunning() 183 | self.mode = .scanning 184 | 185 | self.device = device 186 | self.session = session 187 | } catch let error { 188 | fail(with: error) 189 | } 190 | } 191 | 192 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 193 | super.touchesBegan(touches, with: event) 194 | guard let device = self.device else { return } 195 | 196 | for touch in touches { 197 | let originalPoint = touch.location(in: touch.view) 198 | let screenRect = UIScreen.main.bounds 199 | let point = CGPoint(x: originalPoint.x / screenRect.size.width, y: originalPoint.y / screenRect.size.height) 200 | do { 201 | if device.isFocusPointOfInterestSupported { 202 | try device.lockForConfiguration() 203 | device.focusPointOfInterest = point 204 | device.exposurePointOfInterest = point 205 | device.focusMode = device.isFocusModeSupported(.continuousAutoFocus) ? .continuousAutoFocus : .autoFocus 206 | device.exposureMode = device.isExposureModeSupported(.continuousAutoExposure) ? .continuousAutoExposure : .autoExpose 207 | device.unlockForConfiguration() 208 | } 209 | } catch _ {} 210 | } 211 | } 212 | 213 | @IBAction func openSettingsPressed(_ sender: Any) { 214 | UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil) 215 | } 216 | 217 | @IBAction func dismissTapped(_ sender: Any) { 218 | dismiss(animated: true, completion: nil) 219 | } 220 | } 221 | 222 | extension ScannerViewController: AVCaptureMetadataOutputObjectsDelegate { 223 | func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { 224 | guard let text = ((metadataObjects.first { 225 | guard let barcode = $0 as? AVMetadataMachineReadableCodeObject, let text = barcode.stringValue else { return false } 226 | return text.count == 13 && text.hasPrefix("S") 227 | }) as? AVMetadataMachineReadableCodeObject)?.stringValue?.dropFirst(), SerialAnalysis.isValid(serialNumber: String(text)) else { return } 228 | scan(serialNumber: String(text)) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /Serial/UI/Utility/Extensions/UIViewController+ShorthandAlerts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+ShorthandAlerts.swift 3 | // 4 | // Created by Ayden Panhuyzen on 2017-07-31. 5 | // Copyright © 2017-2018 Ayden Panhuyzen. All rights reserved. 6 | // https://gist.github.com/aydenp 7 | // 8 | 9 | import UIKit 10 | 11 | fileprivate let unknownErrorMessage = NSLocalizedString("An unknown error has occurred", comment: "A message showing that an error has occurred, but the specific error causing it is unknown.") 12 | 13 | extension UIViewController { 14 | 15 | /// Show an alert on the view controller. 16 | func showAlert(title: String? = nil, message: String? = nil, preferredStyle: UIAlertController.Style = .alert, actions: [UIAlertAction] = [.ok], completion: (() -> Void)? = nil) { 17 | guard Thread.isMainThread else { 18 | DispatchQueue.main.async { 19 | self.showAlert(title: title, message: message, preferredStyle: preferredStyle, actions: actions, completion: completion) 20 | } 21 | return 22 | } 23 | let alert = UIAlertController.createAlert(title: title, message: message, preferredStyle: preferredStyle, actions: actions, completion: completion) 24 | present(alert, animated: true, completion: completion) 25 | } 26 | 27 | /// Show an alert on the view controller. 28 | func showAlert(title: String? = nil, message: String? = nil, preferredStyle: UIAlertController.Style = .alert, action: UIAlertAction, completion: (() -> Void)? = nil) { 29 | showAlert(title: title, message: message, preferredStyle: preferredStyle, actions: [action], completion: completion) 30 | } 31 | 32 | /// Show an alert on the view controller for the specified error. 33 | func showErrorAlert(title: String? = nil, error: Error?, unknownMessage: String = unknownErrorMessage, actions: [UIAlertAction] = [.ok], completion: (() -> Void)? = nil) { 34 | showAlert(title: title, message: error?.localizedDescription ?? unknownMessage, actions: actions, completion: completion) 35 | } 36 | 37 | /// Show an alert on the view controller for the specified error. 38 | func showErrorAlert(title: String? = nil, error: Error?, unknownMessage: String = unknownErrorMessage, action: UIAlertAction, completion: (() -> Void)? = nil) { 39 | showErrorAlert(title: title, error: error, actions: [action], completion: completion) 40 | } 41 | 42 | } 43 | 44 | extension UIAlertController { 45 | 46 | static func createAlert(title: String? = nil, message: String? = nil, preferredStyle: UIAlertController.Style = .alert, actions: [UIAlertAction] = [.ok], completion: (() -> Void)? = nil) -> UIAlertController { 47 | let alert = self.init(title: title, message: message, preferredStyle: preferredStyle) 48 | actions.forEach({ alert.addAction($0) }) 49 | return alert 50 | } 51 | 52 | } 53 | 54 | // Extend UIAlertAction to have quick and Swift-like initializers 55 | extension UIAlertAction { 56 | 57 | static func cancel(_ title: String = NSLocalizedString("Cancel", comment: ""), handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { 58 | return UIAlertAction(title: title, style: .cancel, handler: handler) 59 | } 60 | 61 | static func ok(handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { 62 | return self.cancel(NSLocalizedString("OK", comment: ""), handler: handler) 63 | } 64 | 65 | static func dismiss(handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { 66 | return self.cancel(NSLocalizedString("Dismiss", comment: ""), handler: handler) 67 | } 68 | 69 | static func normal(_ title: String, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { 70 | return UIAlertAction(title: title, style: .default, handler: handler) 71 | } 72 | 73 | static func destructive(_ title: String, handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { 74 | return UIAlertAction(title: title, style: .destructive, handler: handler) 75 | } 76 | 77 | static func delete(handler: ((UIAlertAction) -> Void)? = nil) -> UIAlertAction { 78 | return UIAlertAction(title: NSLocalizedString("Delete", comment: ""), style: .destructive, handler: handler) 79 | } 80 | 81 | // Allow being accessed as properties: 82 | 83 | static var cancel: UIAlertAction { 84 | return .cancel() 85 | } 86 | 87 | static var ok: UIAlertAction { 88 | return .ok() 89 | } 90 | 91 | static var dismiss: UIAlertAction { 92 | return .dismiss() 93 | } 94 | 95 | static var delete: UIAlertAction { 96 | return .delete() 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /Serial/UI/Utility/GradientView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GradientView.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-10. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class GradientView: UIView { 12 | var gradientLayer: CAGradientLayer { 13 | return layer as! CAGradientLayer 14 | } 15 | 16 | override class var layerClass: AnyClass { 17 | return CAGradientLayer.self 18 | } 19 | 20 | var colours: [UIColor]? { 21 | get { return gradientLayer.colors?.map { UIColor(cgColor: $0 as! CGColor) } } 22 | set { gradientLayer.colors = newValue?.map { $0.cgColor } } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Serial/UI/Utility/PreviewView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewView.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-05. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class PreviewView: UIView { 13 | var previewLayer: AVCaptureVideoPreviewLayer? { 14 | didSet { 15 | oldValue?.removeFromSuperlayer() 16 | guard let newValue = previewLayer else { return } 17 | newValue.frame = self.bounds 18 | layer.addSublayer(newValue) 19 | } 20 | } 21 | 22 | override func layoutSubviews() { 23 | super.layoutSubviews() 24 | previewLayer?.frame = bounds 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Serial/UI/Utility/ThemedNavigationBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemedNavigationBar.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-18. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ThemedNavigationBar: UINavigationBar { 12 | 13 | override func didMoveToWindow() { 14 | super.didMoveToWindow() 15 | 16 | barStyle = .black 17 | updateStyle() 18 | } 19 | 20 | override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { 21 | super.traitCollectionDidChange(previousTraitCollection) 22 | updateStyle() 23 | } 24 | 25 | private func updateStyle() { 26 | // Using barStyle = black means UIKit always gives us the dark tint colour dynamically, so we have to do that manually 27 | if #available(iOS 13.0, *) { 28 | barTintColor = UIColor(named: "barTint")?.resolvedColor(with: traitCollection) 29 | } else { 30 | barTintColor = UIColor(named: "barTint") 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /Serial/UI/Utility/ThemedTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThemedTableViewController.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-07. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ThemedTableViewController: UITableViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | view.backgroundColor = UIColor(named: "background") 16 | tableView.separatorColor = UIColor(named: "tableViewSeparator") 17 | } 18 | 19 | override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 20 | cell.textLabel?.textColor = UIColor(named: "contentTitle") 21 | cell.detailTextLabel?.textColor = UIColor(named: "contentSubtitle") 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Serial/UI/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-04. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SerialKit 11 | 12 | class ViewController: ThemedTableViewController { 13 | var manualEntryToolbar: UIToolbar! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | // Listen for history change notifications 19 | NotificationCenter.default.addObserver(self, selector: #selector(reloadHistory), name: HistoryManager.notification, object: nil) 20 | 21 | // Create toolbar to show in manual entry keyboard 22 | manualEntryToolbar = UIToolbar() 23 | manualEntryToolbar.items = [ 24 | UIBarButtonItem(title: "Scan Barcode", style: .plain, target: self, action: #selector(showCamera)), 25 | UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), 26 | UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissTextField)) 27 | ] 28 | 29 | // Only show large title on iOS 13+ (new blended navigation bar mode looks great, the large + tint does not.) 30 | if #available(iOS 13.0, *) { 31 | navigationItem.largeTitleDisplayMode = .always 32 | } 33 | } 34 | 35 | override func viewWillAppear(_ animated: Bool) { 36 | super.viewWillAppear(animated) 37 | textField?.text = nil 38 | } 39 | 40 | override func viewDidAppear(_ animated: Bool) { 41 | super.viewDidAppear(animated) 42 | textField?.becomeFirstResponder() 43 | } 44 | 45 | @objc func analyzeManualEntry() { 46 | guard let text = textField?.text, SerialAnalysis.isValid(serialNumber: text) else { 47 | showAlert(title: "Enter a Serial Number", message: "Please enter a valid 12-digit serial number in order to start analysis.") 48 | return 49 | } 50 | ResultsViewController.presentAnalysis(for: text, onViewController: self) 51 | } 52 | 53 | private weak var textField: UITextField? { 54 | didSet { 55 | textField?.addTarget(self, action: #selector(analyzeManualEntry), for: .editingDidEndOnExit) 56 | textField?.addTarget(self, action: #selector(valueChanged), for: .editingChanged) 57 | manualEntryToolbar.sizeToFit() 58 | textField?.inputAccessoryView = manualEntryToolbar 59 | } 60 | } 61 | 62 | // MARK: - History Loading 63 | 64 | private var history = Array(HistoryManager.shared.items.reversed()) { 65 | didSet { tableView.reloadSections(IndexSet(integer: 1), with: .fade) } 66 | } 67 | 68 | @objc func reloadHistory() { 69 | history = HistoryManager.shared.items.reversed() 70 | } 71 | 72 | // MARK: - Table view data source 73 | 74 | override func numberOfSections(in tableView: UITableView) -> Int { 75 | return 2 76 | } 77 | 78 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 79 | switch section { 80 | case 0: return 2 81 | case 1: return history.count 82 | default: return 0 83 | } 84 | } 85 | 86 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 87 | switch indexPath.section { 88 | case 0: 89 | switch indexPath.row { 90 | case 0: 91 | let cell = tableView.dequeueReusableCell(withIdentifier: "SerialEntry") as! TextFieldCell 92 | textField = cell.textField 93 | return cell 94 | case 1: 95 | let cell = tableView.dequeueReusableCell(withIdentifier: "Button") as! ButtonCell 96 | cell.button.setTitle("Analyze", for: .normal) 97 | return cell 98 | default: fatalError("Unknown row.") 99 | } 100 | case 1: 101 | let cell = tableView.dequeueReusableCell(withIdentifier: "HistoryItem") as! HistoryItemCell 102 | cell.item = history[indexPath.row] 103 | return cell 104 | default: fatalError("Unknown section.") 105 | } 106 | } 107 | 108 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 109 | switch section { 110 | case 0: return "Manual Entry" 111 | case 1: return "History" 112 | default: return nil 113 | } 114 | } 115 | 116 | override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { 117 | guard section == 1 else { return nil } 118 | return history.isEmpty ? "No previous analyses." : nil 119 | } 120 | 121 | // MARK: - UITableView Delegate 122 | 123 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 124 | tableView.deselectRow(at: indexPath, animated: true) 125 | switch indexPath.section { 126 | case 0: 127 | if indexPath.row == 1 { analyzeManualEntry(); return } 128 | textField?.becomeFirstResponder() 129 | case 1: ResultsViewController.presentAnalysis(for: history[indexPath.row].serialNumber, onViewController: self) 130 | default: break 131 | } 132 | } 133 | 134 | override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { 135 | return indexPath.section == 1 136 | } 137 | 138 | override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 139 | guard indexPath.section == 1, editingStyle == .delete else { return } 140 | HistoryManager.shared.deleteAll(serialNumber: history[indexPath.row].serialNumber) 141 | } 142 | 143 | // MARK: History Previews 144 | 145 | private var previewingAnalysis: SerialAnalysis? 146 | @available(iOS 13.0, *) 147 | override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { 148 | guard let analysis = SerialAnalysis(serialNumber: history[indexPath.row].serialNumber) else { return nil } 149 | 150 | // Store in a variable to use for the tap action 151 | previewingAnalysis = analysis 152 | 153 | return UIContextMenuConfiguration(identifier: nil, previewProvider: { ResultsViewController(analysis: analysis) }, actionProvider: nil) 154 | } 155 | 156 | @available(iOS 13.0, *) 157 | override func tableView(_ tableView: UITableView, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) { 158 | guard let analysis = previewingAnalysis else { return } 159 | animator.addCompletion { 160 | ResultsViewController.present(analysis: analysis, onViewController: self) 161 | } 162 | previewingAnalysis = nil 163 | } 164 | 165 | // MARK: - UITextField actions 166 | 167 | @objc func valueChanged() { 168 | tableView.reloadRows(at: [IndexPath(row: 1, section: 0)], with: .none) 169 | } 170 | 171 | @objc func showCamera() { 172 | performSegue(withIdentifier: "ShowCamera", sender: nil) 173 | } 174 | 175 | @objc func dismissTextField() { 176 | textField?.resignFirstResponder() 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /SerialKit/Extensions/String+LessPainfulSubstring.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+LessPainfulSubstring.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-05. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | func substring(from start: Int, to end: Int) -> String { 13 | let startIndex = index(self.startIndex, offsetBy: start), endIndex = index(self.startIndex, offsetBy: end) 14 | return String(self[startIndex...endIndex]) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SerialKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /SerialKit/SerialAnalyis+ManufactureLocation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerialAnalyis+ManufactureLocation.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-05-05. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension SerialAnalysis { 12 | enum ManufactureLocation: String { 13 | case FC = "FC", F = "F", XA = "XA", XB = "XB", QP = "QP", G8 = "G8", RN = "RN", CK = "CK", VM = "VM", SG = "SG", E = "E", MB = "MB", PT = "PT", CY = "CY", EE = "EE", QT = "QT", UV = "UV", FK = "FK", F1 = "F1", F2 = "F2", W8 = "W8", DL = "DL", DM = "DM", DN = "DN", YM = "YM", SevenJ = "7J", OneC = "1C", FourH = "4H", WQ = "WQ", F7 = "F7", C0 = "C0", C3 = "C3", C7 = "C7", C1 = "C1", C2 = "C2", RM = "RM", GH = "GH" 14 | 15 | public var locationName: String { 16 | switch self { 17 | case .FC: return "Fountain, Colorado, USA" 18 | case .F: return "Fremont, California, USA" 19 | case .XA, .XB: return "California, USA" 20 | case .QP, .G8: return "USA" 21 | case .RN: return "Mexico" 22 | case .CK: return "Cork, Ireland" 23 | case .VM: return "Pardubice, Czech Republic" 24 | case .SG, .E: return "Singapore" 25 | case .MB: return "Malaysia" 26 | case .PT, .CY: return "South Korea" 27 | case .EE, .QT, .UV: return "Taiwan" 28 | case .FK, .F1, .F2: return "Zhengzhou, China" 29 | case .W8, .C7: return "Shanghai, China" 30 | case .DN: return "Chengdu, China" 31 | case .C3: return "Shenzhen, China" 32 | case .DL, .DM, .YM, .SevenJ, .OneC, .FourH, .WQ, .F7, .C0, .C1, .C2: return "China" 33 | case .RM, .GH: return "Unknown (Refurbished)" 34 | } 35 | } 36 | 37 | public var factoryOwner: String? { 38 | switch self { 39 | case .FC, .F, .CK, .XA, .XB: return "Apple" 40 | case .VM, .FK, .F1, .F2, .DL, .DM, .DN, .C3: return "Foxconn" 41 | case .C0, .QT: return "Quanta Computer" 42 | case .YM, .SevenJ: return "Foxconn (Hon Hai)" 43 | case .C7: return "Pegatron" 44 | default: return nil 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /SerialKit/SerialAnalysis+ManufactureDate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerialAnalysis+ManufactureDate.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-05-05. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension SerialAnalysis { 12 | struct ManufactureDate { 13 | static private let weekNumbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "C", "D", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "T", "V", "W", "X", "Y"] 14 | static private let yearNumbers = ["C", "D", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z"] 15 | 16 | public let year: Int, week: Int 17 | 18 | public init?(serialNumberPart: String) { 19 | let yearPart = serialNumberPart.prefix(1), weekPart = serialNumberPart.suffix(1) 20 | guard let yearValueIndex = SerialAnalysis.ManufactureDate.yearNumbers.firstIndex(where: { $0 == yearPart }), let weekValueIndex = SerialAnalysis.ManufactureDate.weekNumbers.firstIndex(where: { $0 == weekPart }) else { return nil } 21 | year = 2019 - (SerialAnalysis.ManufactureDate.yearNumbers.count - yearValueIndex - 1) / 2 22 | let isHalfYear = yearValueIndex % 2 > 0 23 | week = weekValueIndex + (isHalfYear ? 26 : 0) + 1 24 | } 25 | 26 | public var description: String { 27 | return "Week \(NumberFormatter.localizedString(from: week as NSNumber, number: .decimal)), \(year)" 28 | } 29 | 30 | public var startDate: Date? { 31 | let components = DateComponents(calendar: Calendar(identifier: .gregorian), timeZone: TimeZone(secondsFromGMT: 0), era: nil, year: nil, month: nil, day: nil, hour: nil, minute: nil, second: nil, nanosecond: nil, weekday: nil, weekdayOrdinal: nil, quarter: nil, weekOfMonth: nil, weekOfYear: week, yearForWeekOfYear: year) 32 | return components.date 33 | } 34 | 35 | public var endDate: Date? { 36 | return startDate?.addingTimeInterval(60 * 60 * 24 * 7) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SerialKit/SerialAnalysis+OSFamily.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerialAnalysis+OSFamily.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-05-05. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension SerialAnalysis { 12 | enum OSFamily: String, CaseIterable { 13 | case iOS = "ios", macOS = "macos", tvOS = "tvos", watchOS = "watchos", audioOS = "audioos" 14 | 15 | private var deviceNameMatches: [String] { 16 | switch self { 17 | case .iOS: return ["iphone", "ipad", "ipod touch"] 18 | case .macOS: return ["mac", "xserve"] 19 | case .tvOS: return ["apple tv"] 20 | case .watchOS: return ["apple watch"] 21 | case .audioOS: return ["homepod"] 22 | } 23 | } 24 | 25 | public var friendlyName: String { 26 | switch self { 27 | case .iOS: return "iOS" 28 | case .macOS: return "macOS" 29 | case .tvOS: return "tvOS" 30 | case .watchOS: return "watchOS" 31 | case .audioOS: return "audioOS" 32 | } 33 | } 34 | 35 | public func matches(deviceName: String) -> Bool { 36 | let name = deviceName.lowercased() 37 | return deviceNameMatches.contains { name.contains($0) } 38 | } 39 | 40 | public static func from(deviceName: String) -> OSFamily? { 41 | return OSFamily.allCases.first { $0.matches(deviceName: deviceName) } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SerialKit/SerialAnalysis.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SerialAnalysis.swift 3 | // Serial 4 | // 5 | // Created by Ayden Panhuyzen on 2019-04-04. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class SerialAnalysis { 12 | private static let deviceLookupAPI = "https://deviceinfo.madebyayden.co" 13 | 14 | /// The full serial number being analyzed. 15 | public let serialNumber: String 16 | /// The last digits of the serial number which identify this device's model (Space Grey iPhone X, etc) 17 | public let modelPart: String 18 | public let manufactureLocation: ManufactureLocation?, manufactureDate: ManufactureDate?, techSpecsURL: URL?, checkCoverageURL: URL?, everyMacURL: URL? 19 | 20 | public init?(serialNumber: String) { 21 | self.serialNumber = serialNumber.uppercased() 22 | // Ensure serial number is valid 23 | guard SerialAnalysis.isValid(serialNumber: self.serialNumber) else { return nil } 24 | // Parse available information from number and leave other info for later 25 | manufactureLocation = ManufactureLocation(rawValue: String(self.serialNumber.prefix(2))) 26 | manufactureDate = ManufactureDate(serialNumberPart: self.serialNumber.substring(from: 3, to: 4)) 27 | modelPart = self.serialNumber.substring(from: 8, to: 11) 28 | // Set Apple Support URLs 29 | techSpecsURL = URL(string: "https://support-sp.apple.com/sp/index?page=cpuspec&cc=\(modelPart)") 30 | checkCoverageURL = URL(string: "https://checkcoverage.apple.com/?sn=\(self.serialNumber)") 31 | everyMacURL = URL(string: "https://everymac.com/ultimate-mac-lookup/?search_keywords=\(self.serialNumber)") 32 | fetchDeviceName() 33 | } 34 | 35 | /// The device's friendly name, such as 'iPhone X' or 'Apple Watch Series 4 Stainless Steel 44mm Silver' 36 | public private(set) var deviceName: String? { 37 | didSet { 38 | // Set the probable OS name based on the device's friendly name 39 | osFamily = deviceName != nil ? OSFamily.from(deviceName: deviceName!) : nil 40 | postUpdateNotification() 41 | } 42 | } 43 | 44 | /// The most likely OS family this device belongs to, based on the device's friendly name 45 | public private(set) var osFamily: OSFamily? { 46 | didSet { fetchProbableVersion() } 47 | } 48 | 49 | /// The highest version this device can ship on, if available. 50 | public private(set) var probableVersion: String? { 51 | didSet { postUpdateNotification() } 52 | } 53 | 54 | // MARK: - Pre-validation 55 | 56 | public static func isValid(serialNumber: String) -> Bool { 57 | // TODO: There's a lot more to check to make sure it's valid. 58 | return serialNumber.count == 12 59 | } 60 | 61 | // MARK: - Requests 62 | 63 | /// Whether or not this information is complete (all requests have finished). 64 | public var isComplete: Bool { 65 | return jobsInProgress.isEmpty 66 | } 67 | 68 | private var jobsInProgress = Set() 69 | 70 | /// Jobs that may take additional time to complete. 71 | private enum Job { 72 | case deviceName, probableVersion 73 | } 74 | 75 | /// The current task checkng the device name, if any 76 | private var deviceNameTask: URLSessionDataTask? 77 | /// Fetches the device name from Apple's Support API given the last few digits of the serial number 78 | private func fetchDeviceName() { 79 | jobsInProgress.insert(.deviceName) 80 | deviceName = nil 81 | guard let url = URL(string: "https://support-sp.apple.com/sp/product?cc=\(modelPart)") else { return } 82 | var request = URLRequest(url: url) 83 | request.addValue("application/xhtml+xml,application/xml", forHTTPHeaderField: "Accept") 84 | request.cachePolicy = .returnCacheDataElseLoad 85 | // Make the request 86 | deviceNameTask = URLSession.shared.dataTask(with: request) { (data, _, error) in 87 | guard error == nil, let data = data, let xmlString = String(data: data, encoding: .utf8) else { print("Fetch device name error:", error ?? "none"); self.jobsInProgress.remove(.deviceName); return } 88 | // Who needs an XML parser, right?? 89 | // im so disappointed in myself :( 90 | guard let startIndex = xmlString.range(of: "")?.upperBound, let endIndex = xmlString.range(of: "", options: .init(rawValue: 0), range: startIndex.. () 130 | /// Contains blocks to notify when new data is received 131 | private var _observers = [UpdateBlock]() 132 | 133 | /** 134 | Register an observer to receive future data update notifications about this analysis. 135 | It will be called immediately to allow populating the data more conveniently. 136 | */ 137 | public func register(observer: @escaping UpdateBlock) { 138 | _observers.append(observer) 139 | // Call the observer for convenience 140 | DispatchQueue.main.async { 141 | observer(self) 142 | } 143 | } 144 | 145 | /// Notifies our observers that a data change has occurred. 146 | private func postUpdateNotification() { 147 | DispatchQueue.main.async { 148 | self._observers.forEach { $0(self) } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /SerialKit/SerialKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // SerialKit.h 3 | // SerialKit 4 | // 5 | // Created by Ayden Panhuyzen on 2019-09-19. 6 | // Copyright © 2019 Ayden Panhuyzen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SerialKit. 12 | FOUNDATION_EXPORT double SerialKitVersionNumber; 13 | 14 | //! Project version string for SerialKit. 15 | FOUNDATION_EXPORT const unsigned char SerialKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export LC_CTYPE=C 3 | export LANG=C 4 | 5 | appName=Serial 6 | 7 | project=${appName}.xcodeproj 8 | schemeName=${appName} 9 | 10 | 11 | rm -rf Archives/ 12 | rm -rf deb/Applications/* 13 | mkdir Archives 14 | 15 | # Create archive 16 | xcodebuild -project ${project} -scheme ${schemeName} -sdk iphoneos \ 17 | -configuration Release build CODE_SIGN_IDENTITY= CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO build \ 18 | -archivePath 'Archives/Build.xcarchive' \ 19 | archive 20 | 21 | # Isolate application folder 22 | mkdir Archives/Payload 23 | cp -R Archives/Build.xcarchive/Products/Applications/${appName}.app Archives/Payload/ 24 | rm -rf Archives/Build.xcarchive 25 | 26 | # Clean out files 27 | cd Archives/Payload 28 | find . -name '*.DS_Store' -type f -delete 29 | find . -name '*embedded.mobileprovision' -type f -delete 30 | find . -name '*_CodeSignature' -type f -delete 31 | cd ../.. 32 | 33 | # Create IPA 34 | cd Archives 35 | zip -r -X Sideload.ipa Payload 36 | cd .. 37 | 38 | # Move application payload to deb source 39 | mv Archives/Payload/* deb/Applications/ 40 | 41 | # Remove Swift libraries from deb source (since we have libswift for that) 42 | rm -rf deb/Applications/*.app/Frameworks/libswift* 43 | 44 | # Clean up .DS_Store files in deb folder 45 | cd deb 46 | find . -name '*.DS_Store' -type f -delete 47 | cd .. 48 | 49 | # Fake-sign app 50 | ldid -S deb/Applications/*.app/${appName} 51 | ldid -S deb/Applications/*.app/Frameworks/*.framework/* 52 | ldid -S deb/Applications/*.app/${appName}/PlugIns/*.appex/* 53 | ldid -S deb/Applications/*.app/${appName}/PlugIns/*.appex/Frameworks/*.framework/* 54 | 55 | # Create deb 56 | dpkg-deb -Zgzip -b deb 57 | mv deb.deb Archives/Jailbreak.deb 58 | 59 | # Clean up 60 | rm -rf Archives/Payload 61 | rm -rf deb/Applications/* 62 | 63 | # Show in Finder 64 | open Archives/ 65 | 66 | echo Done! The files are ready for distribution. 67 | -------------------------------------------------------------------------------- /deb/DEBIAN/control: -------------------------------------------------------------------------------- 1 | Package: dev.ayden.ios.serial 2 | Name: Serial 3 | Depends: firmware (>= 11.0), org.swift.libswift 4 | Version: 1.0.3 5 | Architecture: iphoneos-arm 6 | Description: Quickly find information such as the manufacture date and model of an Apple device from its serial number. 7 | Maintainer: Ayden Panhuyzen 8 | Author: Ayden Panhuyzen 9 | Section: Utilities 10 | 11 | --------------------------------------------------------------------------------