├── .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 | [](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 | 
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 |
152 |
153 |
154 |
155 |
156 |
157 |
163 |
169 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
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 |
--------------------------------------------------------------------------------