├── .gitignore
├── .gitmodules
├── LICENSE.md
├── README.md
├── TacBoard.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ ├── WorkspaceSettings.xcsettings
│ └── swiftpm
│ └── Package.resolved
└── TacBoard
├── Resources
├── 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
│ ├── Colors
│ │ ├── BackgroundColor.colorset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── HighlightedCellBackgroundColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadBackgroundColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadBlackWhiteColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadBlueColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadForegroundColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadGreenColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadIndigoColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadOrangeColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadRedColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadVioletColor.colorset
│ │ │ └── Contents.json
│ │ ├── NotepadYellowColor.colorset
│ │ │ └── Contents.json
│ │ ├── PlaceholderTextColor.colorset
│ │ │ └── Contents.json
│ │ ├── PlaceholderTextInvertedColor.colorset
│ │ │ └── Contents.json
│ │ ├── PopoverBorderColor.colorset
│ │ │ └── Contents.json
│ │ ├── PrimaryTextColor.colorset
│ │ │ └── Contents.json
│ │ ├── PrimaryTextInvertedColor.colorset
│ │ │ └── Contents.json
│ │ ├── SecondaryBackgroundColor.colorset
│ │ │ └── Contents.json
│ │ ├── SecondaryTextColor.colorset
│ │ │ └── Contents.json
│ │ ├── SecondaryTextInvertedColor.colorset
│ │ │ └── Contents.json
│ │ ├── SelectedCellBackgroundColor.colorset
│ │ │ └── Contents.json
│ │ ├── SeparatorColor.colorset
│ │ │ └── Contents.json
│ │ ├── TintColor.colorset
│ │ │ └── Contents.json
│ │ └── TintInvertedColor.colorset
│ │ │ └── Contents.json
│ ├── Contents.json
│ └── Module Icons
│ │ ├── Aircraft
│ │ ├── Contents.json
│ │ ├── Icon-AircraftModule-A-10A.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-A-10C.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-AJS37.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-AV8BNA.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-Bf-109K-4.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-C-101.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-ChristenEagleII.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-F-14.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-F-15C.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-F-16C.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-F-5E.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-F-86.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-FA-18C.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-FW-190A8.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-FW-190D9.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-FlamingCliffs.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-Generic.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-I-16.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-JF-17.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-Ka-50.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-L-39C.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-M-2000C.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-Mi-8MTV2.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-MiG-15bis.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-MiG-19P.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-MiG-21bis.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-MiG-29.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-P-47D-30.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-P-51D.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-SA342.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-SpitfireLFMkIX.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-Su-25A.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-Su-27.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Icon-AircraftModule-Su-33.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon-38x38.png
│ │ ├── Icon-AircraftModule-UH-1H.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ └── Icon-AircraftModule-Yak-52.imageset
│ │ │ ├── Contents.json
│ │ │ └── icon.png
│ │ ├── Contents.json
│ │ └── Terrain
│ │ ├── Contents.json
│ │ ├── Icon-TerrainModule-Generic.imageset
│ │ ├── Contents.json
│ │ └── icon.png
│ │ ├── Icon-TerrainModule-Nevada.imageset
│ │ ├── Contents.json
│ │ └── icon.png
│ │ ├── Icon-TerrainModule-Normandy.imageset
│ │ ├── Contents.json
│ │ └── icon.png
│ │ ├── Icon-TerrainModule-PersianGulf.imageset
│ │ ├── Contents.json
│ │ └── icon.png
│ │ └── Icon-TerrainModule-TheChannel.imageset
│ │ ├── Contents.json
│ │ └── icon.png
├── Base.lproj
│ └── Localizable.strings
├── Data
│ ├── Fallback
│ │ ├── AirportDataIndex.json
│ │ ├── ChecklistDataIndex.json
│ │ └── ReferenceDataIndex.json
│ ├── Help
│ │ ├── A10C.png
│ │ ├── ChecklistITunesExample.jpg
│ │ ├── ChecklistShareExample.jpg
│ │ ├── Example.dcschecklist
│ │ ├── Help.css
│ │ └── Help.html
│ └── Modules
│ │ └── Base.lproj
│ │ ├── AircraftModules.json
│ │ └── TerrainModules.json
├── Info.plist
└── Storyboards
│ └── Base.lproj
│ ├── Airports.storyboard
│ ├── Checklists.storyboard
│ ├── Common.storyboard
│ ├── Home.storyboard
│ ├── LaunchScreen.storyboard
│ ├── Main.storyboard
│ ├── Notepad.storyboard
│ └── Reference.storyboard
├── Source
├── Airports
│ ├── Model
│ │ ├── Airport.swift
│ │ ├── AirportCollection.swift
│ │ ├── AirportCommFrequency.swift
│ │ ├── AirportFrequencyBand.swift
│ │ ├── AirportImage.swift
│ │ ├── AirportNavaid.swift
│ │ ├── AirportRunway.swift
│ │ ├── AirportType.swift
│ │ └── AirportViewModel.swift
│ └── UI
│ │ ├── AirportDetailTableViewCell.swift
│ │ ├── AirportDetailViewController.swift
│ │ ├── AirportImageViewController.swift
│ │ ├── AirportListTableViewController.swift
│ │ ├── AirportListViewController.swift
│ │ ├── AirportTableViewCell.swift
│ │ └── AirportViewController.swift
├── Checklists
│ ├── Model
│ │ ├── ChecklistItem.swift
│ │ ├── ChecklistProcedure.swift
│ │ ├── ChecklistSection.swift
│ │ ├── ChecklistViewModel.swift
│ │ └── Items
│ │ │ ├── ChecklistCommentItem.swift
│ │ │ └── ChecklistDefaultItem.swift
│ └── UI
│ │ ├── ChecklistBinderViewController.swift
│ │ ├── ChecklistFolderViewController.swift
│ │ ├── ChecklistItemTableViewCell.swift
│ │ ├── ChecklistProcedureTableViewCell.swift
│ │ ├── ChecklistProcedureViewController.swift
│ │ └── ChecklistViewController.swift
├── Common
│ ├── Extensions
│ │ ├── Array.swift
│ │ ├── CodingUserInfoKey.swift
│ │ ├── Data.swift
│ │ ├── Decoder.swift
│ │ ├── Double.swift
│ │ ├── HTTPURLResponse.swift
│ │ ├── Int.swift
│ │ ├── KeyedDecodingContainer.swift
│ │ ├── NSAttributedString.swift
│ │ ├── Set.swift
│ │ ├── String.swift
│ │ ├── UIApplication.swift
│ │ ├── UIColor.swift
│ │ ├── UIDevice.swift
│ │ ├── UIFont.swift
│ │ ├── UIImage.swift
│ │ ├── UISplitViewController.swift
│ │ ├── UIStoryboard.swift
│ │ ├── UIView.swift
│ │ ├── URL.swift
│ │ └── WKWebView.swift
│ ├── Model
│ │ ├── Binder.swift
│ │ ├── ContentManager.swift
│ │ ├── ContentSource.swift
│ │ ├── DataIndex.swift
│ │ ├── DataIndexKey.swift
│ │ ├── Defaultable.swift
│ │ ├── Folder.swift
│ │ ├── ISO3166.swift
│ │ ├── LatLon.swift
│ │ ├── NavaidType.swift
│ │ ├── RadioFrequencyBand.swift
│ │ ├── RadioModulationType.swift
│ │ ├── ReferenceEquatable.swift
│ │ └── ResourceLocation.swift
│ └── UI
│ │ ├── BinderViewController.swift
│ │ ├── BlockButton.swift
│ │ ├── DisplayFormatter.swift
│ │ ├── FolderTableViewCell.swift
│ │ ├── FolderViewController.swift
│ │ ├── MediaFrameViewController.swift
│ │ ├── PlaceholderViewController.swift
│ │ ├── PopoverContainerViewController.swift
│ │ ├── SegmentedControl.swift
│ │ ├── SelectableHighlightedTableViewCell.swift
│ │ ├── SplitDisplayModeTogglingViewController.swift
│ │ ├── SplitViewControllerDelegate.swift
│ │ └── TableViewController.swift
├── Home
│ ├── HomeAboutViewController.swift
│ ├── HomeHelpViewController.swift
│ ├── HomeTableViewCell.swift
│ ├── HomeTitleTableViewCell.swift
│ └── HomeViewController.swift
├── Main
│ └── MainViewController.swift
├── Modules
│ ├── Model
│ │ ├── AircraftModule.swift
│ │ ├── Module.swift
│ │ └── TerrainModule.swift
│ └── UI
│ │ └── ModuleListViewController.swift
├── Notepad
│ ├── Model
│ │ ├── NotepadPage.swift
│ │ ├── NotepadPath.swift
│ │ └── NotepadViewModel.swift
│ └── UI
│ │ ├── Backgrounds
│ │ └── NotepadNineLineCASBackgroundView.swift
│ │ ├── NotepadDrawingGestureRecognizer.swift
│ │ ├── NotepadDrawingView.swift
│ │ ├── NotepadDrawingViewController.swift
│ │ ├── NotepadSettingsViewController.swift
│ │ └── NotepadViewController.swift
├── Reference
│ ├── Model
│ │ ├── ReferenceDocument.swift
│ │ ├── ReferenceDocumentType.swift
│ │ └── ReferenceViewModel.swift
│ └── UI
│ │ ├── ReferenceBinderViewController.swift
│ │ ├── ReferenceDocumentHTMLViewController.swift
│ │ ├── ReferenceDocumentTableViewCell.swift
│ │ ├── ReferenceFolderViewController.swift
│ │ └── ReferenceViewController.swift
├── Settings
│ ├── Model
│ │ ├── SettingsManager.swift
│ │ ├── SplitDisplayMode.swift
│ │ └── UnitFormat.swift
│ └── UI
│ │ └── SettingsViewController.swift
├── User Content
│ ├── UserContentListViewController.swift
│ └── UserContentManager.swift
└── Utility
│ ├── AppDelegate.swift
│ ├── AppInfo.h
│ ├── AppInfo.m
│ ├── Assets.swift
│ ├── Constants.swift
│ ├── ErrorHandling.swift
│ ├── LocalizableString.swift
│ ├── SceneDelegate.swift
│ ├── SwiftBridgingHeader.h
│ └── VersionManager.swift
└── TacBoard.xcodeproj
├── project.pbxproj
└── xcshareddata
└── xcschemes
├── TacBoard - Debug.xcscheme
└── TacBoard - Release.xcscheme
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Gcc Patch
26 | /*.gcno
27 |
28 | ## MacOS files
29 | .DS_Store
30 |
31 | ## TacBoard autogenerated files
32 | TacBoard/Source/Utility/Version.h
33 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "TacBoard/Resources/Data/Remote"]
2 | path = TacBoard/Resources/Data/Remote
3 | url = git@github.com:xchrishawk/TacBoardData.git
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DCS TacBoard
2 |
3 |  [](https://www.gnu.org/licenses/gpl-3.0)
4 |
5 | **DCS TacBoard** is an open-source kneeboard application for the combat flight simulator [Digital Combat Simulator](https://www.digitalcombatsimulator.com/en/), by Eagle Dynamics. It supports all iOS platforms, including iPad and iPhone.
6 |
7 | ## Features
8 |
9 | The application supports all DCS aircraft and terrain modules. Additional content is being added continuously.
10 |
11 | - Comprehensive checklists for numerous aircraft supported by DCS.
12 | - Airport information, including charts, communications frequencies, and navigation information.
13 | - Reference section including a huge amount of useful information, including performance charts and aircraft systems information.
14 | - A notepad allowing the user to take notes and draw diagrams while in flight.
15 |
16 | ## License
17 |
18 | DCS TacBoard is licensed under the [GPLv3 license](https://www.gnu.org/licenses/gpl-3.0).
--------------------------------------------------------------------------------
/TacBoard.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TacBoard.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TacBoard.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TacBoard.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Nimble",
6 | "repositoryURL": "https://github.com/Quick/Nimble.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "2b1809051b4a65c1d7f5233331daa24572cd7fca",
10 | "version": "8.1.1"
11 | }
12 | },
13 | {
14 | "package": "Quick",
15 | "repositoryURL": "https://github.com/Quick/Quick.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "09b3becb37cb2163919a3842a4c5fa6ec7130792",
19 | "version": "2.2.1"
20 | }
21 | },
22 | {
23 | "package": "ReactiveCocoa",
24 | "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveCocoa.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "b3d48751cf06060de602f151a886def4c7f4c73b",
28 | "version": "11.0.0"
29 | }
30 | },
31 | {
32 | "package": "ReactiveSwift",
33 | "repositoryURL": "https://github.com/ReactiveCocoa/ReactiveSwift",
34 | "state": {
35 | "branch": null,
36 | "revision": "3f4351d04115fd8797802d9b2d17b812cd761602",
37 | "version": "6.3.0"
38 | }
39 | }
40 | ]
41 | },
42 | "version": 1
43 | }
44 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/100.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/114.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/144.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/50.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/57.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/72.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/BackgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xFF",
10 | "red" : "0xFF"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x00",
27 | "green" : "0x00",
28 | "red" : "0x00"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/HighlightedCellBackgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "229",
9 | "green" : "231",
10 | "red" : "229"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "24",
27 | "green" : "26",
28 | "red" : "24"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadBackgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "255",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0",
27 | "green" : "0",
28 | "red" : "0"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadBlackWhiteColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0",
9 | "green" : "0",
10 | "red" : "0"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "255",
27 | "green" : "255",
28 | "red" : "255"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadBlueColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "0",
10 | "red" : "0"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadForegroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "160",
9 | "green" : "162",
10 | "red" : "160"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "95",
27 | "green" : "97",
28 | "red" : "95"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadGreenColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0",
9 | "green" : "255",
10 | "red" : "0"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadIndigoColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "0",
10 | "red" : "165"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadOrangeColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0",
9 | "green" : "165",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadRedColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0",
9 | "green" : "0",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadVioletColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "0",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/NotepadYellowColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0",
9 | "green" : "255",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/PlaceholderTextColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "196",
9 | "green" : "198",
10 | "red" : "196"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "55",
27 | "green" : "57",
28 | "red" : "55"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/PlaceholderTextInvertedColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "255",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "255",
27 | "green" : "255",
28 | "red" : "255"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/PopoverBorderColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "196",
9 | "green" : "198",
10 | "red" : "196"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "38",
27 | "green" : "41",
28 | "red" : "38"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/PrimaryTextColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x00",
9 | "green" : "0x00",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xFF",
27 | "green" : "0xFF",
28 | "red" : "0xFF"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/PrimaryTextInvertedColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "255",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "255",
27 | "green" : "255",
28 | "red" : "255"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/SecondaryBackgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF2",
9 | "green" : "0xF4",
10 | "red" : "0xF2"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x0C",
27 | "green" : "0x0E",
28 | "red" : "0x0C"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/SecondaryTextColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "160",
9 | "green" : "162",
10 | "red" : "160"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "95",
27 | "green" : "97",
28 | "red" : "95"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/SecondaryTextInvertedColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "255",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "255",
27 | "green" : "255",
28 | "red" : "255"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/SelectedCellBackgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "19",
9 | "green" : "148",
10 | "red" : "67"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "13",
27 | "green" : "104",
28 | "red" : "47"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/SeparatorColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xC4",
9 | "green" : "0xC6",
10 | "red" : "0xC4"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x26",
27 | "green" : "0x29",
28 | "red" : "0x26"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/TintColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x13",
9 | "green" : "0x94",
10 | "red" : "0x43"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x0D",
27 | "green" : "0x68",
28 | "red" : "0x2F"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Colors/TintInvertedColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "255",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "255",
27 | "green" : "255",
28 | "red" : "255"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-A-10A.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-A-10A.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-A-10A.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-A-10C.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-A-10C.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-A-10C.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-AJS37.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-AJS37.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-AJS37.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-AV8BNA.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-AV8BNA.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-AV8BNA.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Bf-109K-4.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Bf-109K-4.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Bf-109K-4.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-C-101.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-C-101.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-C-101.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-ChristenEagleII.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-ChristenEagleII.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-ChristenEagleII.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-14.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-14.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-14.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-15C.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-15C.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-15C.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-16C.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-16C.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-16C.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-5E.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-5E.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-5E.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-86.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-86.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-F-86.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FA-18C.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FA-18C.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FA-18C.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FW-190A8.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FW-190A8.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FW-190A8.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FW-190D9.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FW-190D9.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FW-190D9.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FlamingCliffs.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FlamingCliffs.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-FlamingCliffs.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Generic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Generic.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Generic.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-I-16.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-I-16.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-I-16.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-JF-17.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-JF-17.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-JF-17.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Ka-50.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Ka-50.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Ka-50.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-L-39C.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-L-39C.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-L-39C.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-M-2000C.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-M-2000C.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-M-2000C.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Mi-8MTV2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Mi-8MTV2.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Mi-8MTV2.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-15bis.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-15bis.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-15bis.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-19P.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-19P.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-19P.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-21bis.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-21bis.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-21bis.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-29.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-29.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-MiG-29.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-P-47D-30.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-P-47D-30.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-P-47D-30.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-P-51D.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-P-51D.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-P-51D.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-SA342.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-SA342.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-SA342.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-SpitfireLFMkIX.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-SpitfireLFMkIX.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-SpitfireLFMkIX.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-25A.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-25A.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-25A.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-27.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-27.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-27.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-33.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon-38x38.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-33.imageset/icon-38x38.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Su-33.imageset/icon-38x38.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-UH-1H.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-UH-1H.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-UH-1H.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Yak-52.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Yak-52.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Aircraft/Icon-AircraftModule-Yak-52.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Generic.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Generic.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Generic.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Nevada.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Nevada.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Nevada.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Normandy.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Normandy.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-Normandy.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-PersianGulf.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-PersianGulf.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-PersianGulf.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-TheChannel.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "icon.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-TheChannel.imageset/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Assets.xcassets/Module Icons/Terrain/Icon-TerrainModule-TheChannel.imageset/icon.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Data/Fallback/ReferenceDataIndex.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0A",
3 | "description": "TacBoard Reference",
4 | "objects": []
5 | }
6 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Data/Help/A10C.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Data/Help/A10C.png
--------------------------------------------------------------------------------
/TacBoard/Resources/Data/Help/ChecklistITunesExample.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Data/Help/ChecklistITunesExample.jpg
--------------------------------------------------------------------------------
/TacBoard/Resources/Data/Help/ChecklistShareExample.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xchrishawk/TacBoard/9cac986032839bdd40dc8002a4f8860e10f599fe/TacBoard/Resources/Data/Help/ChecklistShareExample.jpg
--------------------------------------------------------------------------------
/TacBoard/Resources/Data/Help/Help.css:
--------------------------------------------------------------------------------
1 | :root {
2 |
3 | /* We support light and dark color schemes */
4 | color-scheme: light dark;
5 |
6 | /* Main font for entire document */
7 | font-family: "Galvji", "Helvetica", sans-serif;
8 |
9 | }
10 |
11 | /* Light color scheme configuration. */
12 | @media (prefers-color-scheme: light) {
13 |
14 | /* Configure colors for light mode (should match colors in Assets.xcassets) */
15 | :root {
16 | --background-color: #ffffff;
17 | --primary-text-color: #000000;
18 | --secondary-background-color: #f2f4f2;
19 | --separator-color: #c4c6c4;
20 | --tint-color: #439413;
21 | }
22 |
23 | }
24 |
25 | /* Dark color scheme configuration. */
26 | @media (prefers-color-scheme: dark) {
27 |
28 | /* Configure colors for dark mode (should match colors in Assets.xcassets) */
29 | :root {
30 | --background-color: #000000;
31 | --primary-text-color: #ffffff;
32 | --secondary-background-color: #0c0e0c;
33 | --separator-color: #262926;
34 | --tint-color: #2f680d;
35 | }
36 |
37 | }
38 |
39 | /* Set page background color according to mode. */
40 | html {
41 | background-color: var(--background-color);
42 | color: var(--primary-text-color);
43 | }
44 |
45 | /* Use application tint color for all types of links. */
46 | a:link, a:visited { color: var(--tint-color); }
47 |
48 | /* Add some basic padding around the border of the page. */
49 | body { padding: 10px 10px 10px 10px; }
50 |
51 | /* Additional spacing for list items. */
52 | li { margin-bottom: 5px; }
53 |
54 | /* Screen shots. */
55 | img.screenshot {
56 | border-radius: 10px;
57 | display: block;
58 | margin-left: auto;
59 | margin-right: auto;
60 | max-width: 600px;
61 | width: 100%;
62 | }
63 |
--------------------------------------------------------------------------------
/TacBoard/Resources/Data/Modules/Base.lproj/TerrainModules.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "key": "Caucasus",
4 | "title": "Caucasus",
5 | "author": "Eagle Dynamics",
6 | "isDefaultEnabled": true,
7 | "isPrimary": true
8 | },
9 | {
10 | "key": "Nevada",
11 | "title": "Nevada Test and Training Range",
12 | "compactTitle": "Nevada",
13 | "author": "Eagle Dynamics",
14 | "isDefaultEnabled": true,
15 | "isPrimary": true
16 | },
17 | {
18 | "key": "Normandy",
19 | "title": "Normandy 1944",
20 | "author": "Ugra Media",
21 | "isDefaultEnabled": false,
22 | "isPrimary": false
23 | },
24 | {
25 | "key": "PersianGulf",
26 | "title": "Persian Gulf",
27 | "author": "Eagle Dynamics",
28 | "isDefaultEnabled": false,
29 | "isPrimary": false
30 | },
31 | {
32 | "key": "TheChannel",
33 | "title": "The Channel",
34 | "author": "Eagle Dynamics",
35 | "isDefaultEnabled": false,
36 | "isPrimary": false
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/Model/AirportCommFrequency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportCommFrequency.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/6/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Class representing an airport communications frequency.
12 | class AirportCommFrequency: Decodable {
13 |
14 | // MARK: Initialization
15 |
16 | /// Initializes a new instance with the specified values.
17 | init(title: String, band: RadioFrequencyBand, modulation: RadioModulationType, frequency: Double) {
18 | self.title = title
19 | self.band = band
20 | self.modulation = modulation
21 | self.frequency = Measurement(value: frequency, unit: .hertz)
22 | }
23 |
24 | // MARK: Fields
25 |
26 | /// The title of the frequencies.
27 | let title: String
28 |
29 | /// The frequency band.
30 | let band: RadioFrequencyBand
31 |
32 | /// The modulation type.
33 | let modulation: RadioModulationType
34 |
35 | /// The frequency.
36 | let frequency: Measurement
37 |
38 | // MARK: Codable
39 |
40 | /// `CodingKeys` enum for this class.
41 | private enum CodingKeys: String, CodingKey {
42 | case title
43 | case band
44 | case modulation
45 | case frequency
46 | }
47 |
48 | /// Initializes a new instance from the specified `Decoder`.
49 | convenience required init(from decoder: Decoder) throws {
50 | let container = try decoder.container(keyedBy: CodingKeys.self)
51 | self.init(title: try container.decode(String.self, forKey: .title),
52 | band: try container.decode(RadioFrequencyBand.self, forKey: .band),
53 | modulation: try container.decode(RadioModulationType.self, forKey: .modulation),
54 | frequency: try container.decode(Double.self, forKey: .frequency))
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/Model/AirportFrequencyBand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportFrequencyBand.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/6/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of airport communications frequency bands.
12 | enum FrequencyBand: String, Codable, CustomStringConvertible {
13 |
14 | // MARK: Cases
15 |
16 | /// HF frequency band.
17 | case hf = "HF"
18 |
19 | /// Low VHF frequency band.
20 | case vhfLow = "VHFLow"
21 |
22 | /// High VHF frequency band.
23 | case vhfHigh = "VHFHigh"
24 |
25 | /// UHF frequency band.
26 | case uhf = "UHF"
27 |
28 | // MARK: Properties
29 |
30 | /// Returns a `String` description of this enum.
31 | var description: String {
32 | switch self {
33 | case .hf:
34 | return LocalizableString(.airportFrequencyBandHF)
35 | case .vhfLow:
36 | return LocalizableString(.airportFrequencyBandVHFLow)
37 | case .vhfHigh:
38 | return LocalizableString(.airportFrequencyBandVHFHigh)
39 | case .uhf:
40 | return LocalizableString(.airportFrequencyBandUHF)
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/Model/AirportImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportImage.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/6/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Class representing an airport image.
13 | class AirportImage: Decodable {
14 |
15 | // MARK: Initialization
16 |
17 | /// Initializes a new instance with the specified values.
18 | init(title: String, compactTitle: String? = nil, credit: String? = nil, location: ResourceLocation) {
19 | self.title = title
20 | self.compactTitle = compactTitle ?? title
21 | self.credit = credit
22 | self.location = location
23 | }
24 |
25 | // MARK: Properties
26 |
27 | /// The title of the image.
28 | let title: String
29 |
30 | /// The title of the image, shortened for compact layouts.
31 | let compactTitle: String
32 |
33 | /// The image credit, or `nil` if there is no credit.
34 | let credit: String?
35 |
36 | /// The location of the image file.
37 | let location: ResourceLocation
38 |
39 | // MARK: Codable
40 |
41 | /// The `CodingKey` enum for this class.
42 | private enum CodingKeys: String, CodingKey {
43 | case title
44 | case compactTitle
45 | case credit
46 | case asset
47 | case path
48 | }
49 |
50 | /// Initializes a new instance with the specified `Decoder`.
51 | convenience required init(from decoder: Decoder) throws {
52 |
53 | let container = try decoder.container(keyedBy: CodingKeys.self)
54 |
55 | let title = try container.decode(String.self, forKey: .title)
56 | let compactTitle = try container.decodeOrDefault(String?.self, forKey: .compactTitle, default: nil)
57 | let credit = try container.decodeOrDefault(String?.self, forKey: .credit, default: nil)
58 |
59 | if let asset = try? container.decode(String.self, forKey: .asset) {
60 |
61 | // Image is a local asset
62 | self.init(title: title,
63 | compactTitle: compactTitle,
64 | credit: credit,
65 | location: .asset(name: asset))
66 |
67 | } else if let path = try? container.decode(String.self, forKey: .path) {
68 |
69 | // Image is a path relative to the base content URL
70 | self.init(title: title,
71 | compactTitle: compactTitle,
72 | credit: credit,
73 | location: .relative(path: path))
74 |
75 | } else {
76 |
77 | // No location specified
78 | throw DecodingError.dataCorruptedError(forKey: .asset, in: container, debugDescription: "No image location specified!")
79 |
80 | }
81 |
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/Model/AirportRunway.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportRunway.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/4/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Class containing information about a specific runway at an airport.
12 | class AirportRunway: Decodable {
13 |
14 | // MARK: Initialization
15 |
16 | /// Initializes a new instance with the specified values.
17 | init(identifier: String,
18 | reciprocalIdentifier: String,
19 | heading: Double,
20 | reciprocalHeading: Double,
21 | length: Double,
22 | width: Double) {
23 |
24 | self.identifier = identifier
25 | self.reciprocalIdentifier = reciprocalIdentifier
26 | self.heading = Measurement(value: heading, unit: .degrees)
27 | self.reciprocalHeading = Measurement(value: reciprocalHeading, unit: .degrees)
28 | self.length = Measurement(value: length, unit: .feet)
29 | self.width = Measurement(value: width, unit: .feet)
30 |
31 | }
32 |
33 | // MARK: Properties
34 |
35 | /// The identifier of the runway.
36 | let identifier: String
37 |
38 | /// The identifier of the reciprocal runway.
39 | let reciprocalIdentifier: String
40 |
41 | /// The heading of the runway.
42 | let heading: Measurement
43 |
44 | /// The reciprocal heading of the runway.
45 | let reciprocalHeading: Measurement
46 |
47 | /// The length of the runway.
48 | let length: Measurement
49 |
50 | /// The width of the runway.
51 | let width: Measurement
52 |
53 | // MARK: Codable
54 |
55 | /// `CodingKey` enum for this class.
56 | private enum CodingKeys: String, CodingKey {
57 | case identifier
58 | case reciprocalIdentifier
59 | case heading
60 | case reciprocalHeading
61 | case length
62 | case width
63 | }
64 |
65 | /// Initializes a new instance with the specified decoder.
66 | convenience required init(from decoder: Decoder) throws {
67 | let container = try decoder.container(keyedBy: CodingKeys.self)
68 | self.init(identifier: try container.decode(String.self, forKey: .identifier),
69 | reciprocalIdentifier: try container.decode(String.self, forKey: .reciprocalIdentifier),
70 | heading: try container.decode(Double.self, forKey: .heading),
71 | reciprocalHeading: try container.decode(Double.self, forKey: .reciprocalHeading),
72 | length: try container.decode(Double.self, forKey: .length),
73 | width: try container.decode(Double.self, forKey: .width))
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/Model/AirportType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportType.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/4/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of airport types.
12 | enum AirportType: String, Codable, CustomStringConvertible {
13 |
14 | // MARK: Cases
15 |
16 | /// The airport is a civilian airport.
17 | case civilian = "Civilian"
18 |
19 | /// The airport is a military airport.
20 | case military = "Military"
21 |
22 | // MARK: Properties
23 |
24 | /// A `String` describing this enum value.
25 | var description: String {
26 | switch self {
27 | case .civilian:
28 | return LocalizableString(.airportTypeCivilian)
29 | case .military:
30 | return LocalizableString(.airportTypeMilitary)
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/UI/AirportDetailTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportDetailTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/6/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// `UITableViewCell` subclass for displaying airport details.
13 | class AirportDetailTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | // MARK: Outlets
16 |
17 | /// The label for the title of the data contained in the cell.
18 | @IBOutlet var titleLabel: UILabel?
19 |
20 | /// The auxiliary data stack view, located immediately to the right of the title label.
21 | @IBOutlet var auxiliaryDataView: UIStackView?
22 |
23 | /// The first auxiliary data label.
24 | @IBOutlet var auxiliaryData1Label: UILabel?
25 |
26 | /// The second auxiliary data label.
27 | @IBOutlet var auxiliaryData2Label: UILabel?
28 |
29 | /// The first (i.e., top) data label.
30 | @IBOutlet var data1Label: UILabel?
31 |
32 | /// The second (i.e., bottom) data label.
33 | @IBOutlet var data2Label: UILabel?
34 |
35 | /// The placeholder text label.
36 | @IBOutlet var placeholderLabel: UILabel?
37 |
38 | /// The accessory button label.
39 | @IBOutlet var accessoryButton: UIButton?
40 |
41 | // MARK: UITableViewCell Overrides
42 |
43 | /// Initializes the cell after it is deserialized from the nib.
44 | override func awakeFromNib() {
45 | super.awakeFromNib()
46 | initializeCell()
47 | }
48 |
49 | /// Initializes the cell when it is about to be reused.
50 | override func prepareForReuse() {
51 | super.prepareForReuse()
52 | initializeCell()
53 | }
54 |
55 | // MARK: Private Utility
56 |
57 | /// Initializes the state of the cell.
58 | private func initializeCell() {
59 |
60 | accessoryType = .none
61 | selectionStyle = .none
62 |
63 | titleLabel?.text = nil
64 | titleLabel?.safeIsHidden = true
65 |
66 | auxiliaryDataView?.safeIsHidden = true
67 |
68 | auxiliaryData1Label?.text = nil
69 | auxiliaryData1Label?.safeIsHidden = true
70 |
71 | auxiliaryData2Label?.text = nil
72 | auxiliaryData2Label?.safeIsHidden = false
73 |
74 | data1Label?.text = nil
75 | data1Label?.safeIsHidden = true
76 |
77 | data2Label?.text = nil
78 | data2Label?.safeIsHidden = true
79 |
80 | placeholderLabel?.text = nil
81 | placeholderLabel?.safeIsHidden = true
82 |
83 | accessoryButton?.setImage(nil, for: .normal)
84 | accessoryButton?.safeIsHidden = true
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/UI/AirportListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportListViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/6/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveCocoa
11 | import ReactiveSwift
12 | import UIKit
13 |
14 | /// Master view controller for the airports page.
15 | class AirportListViewController: UIViewController, UITextFieldDelegate {
16 |
17 | // MARK: Fields
18 |
19 | private let viewModel: AirportViewModel
20 |
21 | // MARK: Outlets
22 |
23 | @IBOutlet private var searchTextField: UITextField?
24 |
25 | // MARK: Initialization
26 |
27 | /// Initializer not available.
28 | @available(*, unavailable)
29 | required init?(coder: NSCoder) { fatalNotAvailable() }
30 |
31 | /// Initializes a new instance with the specified view model and coder.
32 | init?(coder: NSCoder, viewModel: AirportViewModel) {
33 | self.viewModel = viewModel
34 | super.init(coder: coder)
35 | }
36 |
37 | // MARK: UIViewController Overrides
38 |
39 | /// Initializes the view controller after the view loads.
40 | override func viewDidLoad() {
41 | super.viewDidLoad()
42 | initializeBindings()
43 | }
44 |
45 | // MARK: Segue Actions
46 |
47 | /// Creates and returns an `AirportListTableViewController` instance.
48 | @IBSegueAction
49 | private func createAirportListTableViewController(_ coder: NSCoder) -> UIViewController? {
50 | return AirportListTableViewController(coder: coder, viewModel: viewModel)
51 | }
52 |
53 | // MARK: UITextFieldDelegate
54 |
55 | /// Returns `true` if the text field should stop editing.
56 | func textFieldShouldReturn(_ textField: UITextField) -> Bool {
57 | textField.resignFirstResponder()
58 | return true
59 | }
60 |
61 | // MARK: Private Utility
62 |
63 | /// Initializes ReactiveSwift bindings.
64 | private func initializeBindings() {
65 |
66 | // Send search text to the view model
67 | if let searchTextField = searchTextField {
68 | viewModel.searchText <~ searchTextField.reactive.continuousTextValues
69 | }
70 |
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/TacBoard/Source/Airports/UI/AirportTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AirportTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/4/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Cell for displaying airports.
13 | class AirportTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | /// The label for the airport's identifier.
16 | @IBOutlet var identifierLabel: UILabel?
17 |
18 | /// The label for the airport's name.
19 | @IBOutlet var nameLabel: UILabel?
20 |
21 | /// The label for the country flag emoji.
22 | @IBOutlet var countryFlagLabel: UILabel?
23 |
24 | /// The label for the airport's city.
25 | @IBOutlet var callsignLabel: UILabel?
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/Model/ChecklistItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistItem.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 |
12 | // MARK: - ChecklistItem
13 |
14 | /// Protocol for objects representing items which may be displayed in a checklist.
15 | protocol ChecklistItem: class, Decodable {
16 |
17 | // MARK: Properties
18 |
19 | /// A unique key for this item.
20 | var key: String { get }
21 |
22 | /// The main text of the item.
23 | var text: NSAttributedString { get }
24 |
25 | /// The subtext of the item, or `nil` if there is no subtext.
26 | var subtext: String? { get }
27 |
28 | /// The action for the user to take, or `nil` if there is no action.
29 | var action: String? { get }
30 |
31 | /// The comment for this item, or `nil` if there is no comment.
32 | var comment: String? { get }
33 |
34 | }
35 |
36 | // MARK: - ChecklistCompletableItem
37 |
38 | /// Protocol for objects representing "completable" items which may be displayed in a checklist.
39 | protocol ChecklistCompletableItem: ChecklistItem {
40 |
41 | /// If set to `true`, this item has been completed.
42 | var isComplete: MutableProperty { get }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/Model/ChecklistSection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistSection.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 |
12 | /// Object representing an individual section in a checklist.
13 | class ChecklistSection: Decodable {
14 |
15 | // MARK: Initialization
16 |
17 | /// Initializes a new instance with the specified values.
18 | init(title: String? = nil, items: [ChecklistItem] = []) {
19 | self.title = title
20 | self.items = items
21 | }
22 |
23 | // MARK: Properties
24 |
25 | /// The title of the section, or `nil` if there is no title.
26 | let title: String?
27 |
28 | /// The checklist items contained in the section.
29 | let items: [ChecklistItem]
30 |
31 | // MARK: Codable
32 |
33 | /// `CodingKey` enum for this class.
34 | private enum CodingKeys: String, CodingKey {
35 | case title
36 | case items
37 | }
38 |
39 | /// `CodingKey` enum for determining the type of each item.
40 | private enum ItemTypeKeys: String, CodingKey {
41 | case type
42 | }
43 |
44 | /// Enumeration of the known item types.
45 | private enum ItemType: String, Codable {
46 | case `default` = "Default"
47 | case comment = "Comment"
48 | }
49 |
50 | /// Initializes a new instance with the specified `Decoder`.
51 | convenience required init(from decoder: Decoder) throws {
52 |
53 | let container = try decoder.container(keyedBy: CodingKeys.self)
54 |
55 | // We need two containers here - one to decode the type, and a second to decode the item itself.
56 | // We can't use the same container for both operations because decoding the type advances the iterator to the next item
57 | var itemsContainerForCheckingType = try container.nestedUnkeyedContainer(forKey: .items)
58 | var itemsContainerForDecodingItems = itemsContainerForCheckingType
59 |
60 | // Scan through the list of items
61 | var items = [ChecklistItem]()
62 | while !itemsContainerForCheckingType.isAtEnd {
63 |
64 | // Determine the type of this item
65 | let itemContainerForCheckingType = try itemsContainerForCheckingType.nestedContainer(keyedBy: ItemTypeKeys.self)
66 | let itemType = try itemContainerForCheckingType.decodeOrDefault(ItemType.self, forKey: .type, default: .default)
67 |
68 | // Append an item of the correct type based on the decoded type
69 | switch itemType {
70 | case .default:
71 | items.append(try itemsContainerForDecodingItems.decode(ChecklistDefaultItem.self))
72 | case .comment:
73 | items.append(try itemsContainerForDecodingItems.decode(ChecklistCommentItem.self))
74 | }
75 |
76 | }
77 |
78 | self.init(title: try container.decodeOrDefault(String?.self, forKey: .title, default: nil),
79 | items: items)
80 |
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/Model/Items/ChecklistCommentItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistCommentItem.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 |
12 | /// Object representing a non-checkable comment item in a checklist.
13 | class ChecklistCommentItem: ChecklistItem, Decodable {
14 |
15 | // MARK: Initialization
16 |
17 | /// Initializes a new instance with the specified values.
18 | init(key: DataIndexKey,
19 | text: NSAttributedString,
20 | subtext: String? = nil,
21 | action: String? = nil,
22 | comment: String? = nil,
23 | isSmallText: Bool = false) {
24 |
25 | self.key = key
26 | self.text = text
27 | self.subtext = subtext
28 | self.action = action
29 | self.comment = comment
30 | self.isSmallText = isSmallText
31 |
32 | }
33 |
34 | // MARK: Properties
35 |
36 | /// A unique key for this item.
37 | let key: DataIndexKey
38 |
39 | /// The text of the item.
40 | let text: NSAttributedString
41 |
42 | /// The subtext of the item, or `nil` if there is no subtext.
43 | let subtext: String?
44 |
45 | /// The action for the user to take, or `nil` if there is no action.
46 | let action: String?
47 |
48 | /// The comment for this item, or `nil` if there is no comment.
49 | let comment: String?
50 |
51 | /// If set to `true`, this comment should be displayed in a smaller font size.
52 | let isSmallText: Bool
53 |
54 | // MARK: Codable
55 |
56 | /// `CodingKey` enum for this class.
57 | private enum CodingKeys: String, CodingKey {
58 | case text
59 | case subtext
60 | case action
61 | case comment
62 | case small
63 | }
64 |
65 | /// Initializes a new instance with the specified `Decoder`.
66 | convenience required init(from decoder: Decoder) throws {
67 |
68 | let container = try decoder.container(keyedBy: CodingKeys.self)
69 |
70 | guard
71 | let textMarkup = try? container.decode(String.self, forKey: .text),
72 | let text = NSAttributedString(markup: textMarkup)
73 | else { throw DecodingError.dataCorruptedError(forKey: .text, in: container, debugDescription: "Invalid text!") }
74 |
75 | self.init(key: ChecklistDataIndex.key(for: decoder),
76 | text: text,
77 | subtext: try container.decodeOrDefault(String?.self, forKey: .subtext, default: nil),
78 | action: try container.decodeOrDefault(String?.self, forKey: .action, default: nil),
79 | comment: try container.decodeOrDefault(String?.self, forKey: .comment, default: nil),
80 | isSmallText: try container.decodeOrDefault(Bool.self, forKey: .small, default: false))
81 |
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/Model/Items/ChecklistDefaultItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistDefaultItem.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 |
12 | /// Object representing a standard, checkable item in a checklist.
13 | class ChecklistDefaultItem: ChecklistCompletableItem, Decodable {
14 |
15 | // MARK: Initialization
16 |
17 | /// Initializes a new instance with the specified values.
18 | init(key: DataIndexKey,
19 | text: NSAttributedString,
20 | subtext: String? = nil,
21 | action: String? = nil,
22 | comment: String? = nil,
23 | isComplete: Bool = false) {
24 |
25 | self.key = key
26 | self.text = text
27 | self.subtext = subtext
28 | self.action = action
29 | self.comment = comment
30 | self.isComplete = MutableProperty(isComplete)
31 |
32 | }
33 |
34 | // MARK: Properties
35 |
36 | /// A unique key for this checklist item.
37 | let key: DataIndexKey
38 |
39 | /// The text of the item.
40 | let text: NSAttributedString
41 |
42 | /// The subtext of the item, or `nil` if there is no subtext.
43 | let subtext: String?
44 |
45 | /// The action for the user to take, or `nil` if there is no action.
46 | let action: String?
47 |
48 | /// The comment for this item, or `nil` if there is no comment.
49 | let comment: String?
50 |
51 | /// If set to `true`, this checklist item has been completed.
52 | let isComplete: MutableProperty
53 |
54 | // MARK: Codable
55 |
56 | /// `CodingKey` enum for this class.
57 | private enum CodingKeys: String, CodingKey {
58 | case text
59 | case subtext
60 | case action
61 | case comment
62 | case isComplete
63 | }
64 |
65 | /// Initializes a new instance with the specified `Decoder`.
66 | convenience required init(from decoder: Decoder) throws {
67 |
68 | let container = try decoder.container(keyedBy: CodingKeys.self)
69 |
70 | guard
71 | let textMarkup = try? container.decode(String.self, forKey: .text),
72 | let text = NSAttributedString(markup: textMarkup)
73 | else { throw DecodingError.dataCorruptedError(forKey: .text, in: container, debugDescription: "Invalid text!") }
74 |
75 | self.init(key: ChecklistDataIndex.key(for: decoder),
76 | text: text,
77 | subtext: try container.decodeOrDefault(String?.self, forKey: .subtext, default: nil),
78 | action: try container.decodeOrDefault(String?.self, forKey: .action, default: nil),
79 | comment: try container.decodeOrDefault(String?.self, forKey: .comment, default: nil),
80 | isComplete: try container.decodeOrDefault(Bool.self, forKey: .isComplete, default: false))
81 |
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/UI/ChecklistBinderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistBinderViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 | import UIKit
12 |
13 | /// Concrete implementation of `BinderViewController` for checklists.
14 | class ChecklistBinderViewController: BinderViewController {
15 |
16 | // MARK: Outlets
17 |
18 | @IBOutlet private var resetAllChecklistsBarButtonItem: UIBarButtonItem?
19 |
20 | // MARK: BinderViewController Overrides
21 |
22 | /// Returns a `ChecklistFolderViewController` for the specified folder.
23 | override func folderViewController(coder: NSCoder, viewModel: ChecklistViewModel, folder: Folder) -> UIViewController? {
24 | return ChecklistFolderViewController(coder: coder, viewModel: viewModel, folder: folder)
25 | }
26 |
27 | /// Returns a `UITableViewCell` for the specified folder.
28 | override func tableView(_ tableView: UITableView, cellForFolder folder: Folder, at indexPath: IndexPath) -> UITableViewCell {
29 | let cell = tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! FolderTableViewCell
30 | cell.configure(for: folder)
31 | return cell
32 | }
33 |
34 | /// Returns a `String` explaining why there are no binders available.
35 | override func noItemsAvailableExplanation(viewModel: ChecklistViewModel) -> String {
36 | if viewModel.enabledAircraftModules.value.isEmpty {
37 | return LocalizableString(.checklistBinderEnableAircraftModule)
38 | } else {
39 | return LocalizableString(.checklistBinderNoMatches)
40 | }
41 | }
42 |
43 | // MARK: Actions
44 |
45 | /// The user pressed the "reset all checklists" bar button item.
46 | @IBAction
47 | private func resetAllChecklistsBarButtonItemPressed(_ sender: UIBarButtonItem) {
48 |
49 | let alert = UIAlertController(title: LocalizableString(.checklistResetAllAlertTitle),
50 | message: LocalizableString(.checklistResetAllAlertBinderMessage),
51 | preferredStyle: .alert)
52 |
53 | alert.addAction(UIAlertAction(title: LocalizableString(.genericOK), style: .destructive, handler: { _ in
54 | for binder in self.viewModel.allBinders.value {
55 | for folder in binder.folders {
56 | folder.setIsComplete(false)
57 | }
58 | }
59 | }))
60 | alert.addAction(UIAlertAction(title: LocalizableString(.genericCancel), style: .default, handler: nil))
61 |
62 | present(alert, animated: true, completion: nil)
63 |
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/UI/ChecklistFolderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistFolderViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveCocoa
11 | import ReactiveSwift
12 | import UIKit
13 |
14 | /// Concrete implementation of `FolderVieController` for checklists.
15 | class ChecklistFolderViewController: FolderViewController {
16 |
17 | // MARK: Outlets
18 |
19 | @IBOutlet private var resetAllChecklistsBarButtonItem: UIBarButtonItem?
20 |
21 | // MARK: FolderViewController Overrides
22 |
23 | /// Returns the title for the folders section.
24 | override var folderSectionTitle: String {
25 | return LocalizableString(.checklistFoldersSectionTitle)
26 | }
27 |
28 | /// Returns the title for the items section.
29 | override var itemSectionTitle: String {
30 | return LocalizableString(.checklistProceduresSectionTitle)
31 | }
32 |
33 | /// Returns a `ChecklistFolderViewController` for the specified folder.
34 | override func folderViewController(coder: NSCoder, viewModel: ChecklistViewModel, folder: Folder) -> UIViewController? {
35 | return ChecklistFolderViewController(coder: coder, viewModel: viewModel, folder: folder)
36 | }
37 |
38 | /// Returns a `UITableViewCell` for the specified folder.
39 | override func tableView(_ tableView: UITableView, cellForFolder folder: Folder, at indexPath: IndexPath) -> UITableViewCell {
40 | let cell = tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! FolderTableViewCell
41 | cell.configure(for: folder)
42 | return cell
43 | }
44 |
45 | /// Returns a `UITableViewCell` for the specified procedure.
46 | override func tableView(_ tableView: UITableView, cellForItem item: ChecklistProcedure, at indexPath: IndexPath) -> UITableViewCell {
47 |
48 | let cell = tableView.dequeueReusableCell(withIdentifier: "ProcedureCell", for: indexPath) as! ChecklistProcedureTableViewCell
49 |
50 | // Basic info
51 | cell.titleLabel?.text = item.title
52 | cell.subtitleLabel?.text = item.subtitle
53 | cell.subtitleLabel?.safeIsHidden = item.subtitle.isNilOrEmpty
54 | cell.accessoryType = (traitCollection.horizontalSizeClass == .compact ? .disclosureIndicator : .none)
55 |
56 | // Auto-update visibility of isComplete image view
57 | if let isCompleteImageView = cell.isCompleteImageView {
58 | isCompleteImageView.reactive.alpha <~ item.isComplete.producer.take(until: cell.reactive.prepareForReuse).map { ($0 ? 1.0 : 0.0) }
59 | }
60 |
61 | return cell
62 |
63 | }
64 |
65 | // MARK: Actions
66 |
67 | /// The user pressed the "reset all checklists" bar button item.
68 | @IBAction
69 | private func resetAllChecklistsBarButtonItemPressed(_ sender: UIBarButtonItem) {
70 |
71 | let alert = UIAlertController(title: LocalizableString(.checklistResetAllAlertTitle),
72 | message: String(format: LocalizableString(.checklistResetAllAlertFolderMessage), folder.title),
73 | preferredStyle: .alert)
74 |
75 | alert.addAction(UIAlertAction(title: LocalizableString(.genericOK), style: .destructive, handler: { _ in self.folder.setIsComplete(false) }))
76 | alert.addAction(UIAlertAction(title: LocalizableString(.genericCancel), style: .default, handler: nil))
77 |
78 | present(alert, animated: true, completion: nil)
79 |
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/UI/ChecklistItemTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistItemTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// `UITableViewCell` subclass displaying an individual item in a checklist procedure.
13 | class ChecklistItemTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | // MARK: Outlets
16 |
17 | /// The label for the text of the item.
18 | @IBOutlet var itemTextLabel: UILabel?
19 |
20 | /// The label for the subtext of the item.
21 | @IBOutlet var itemSubtextLabel: UILabel?
22 |
23 | /// The label for the action of the item.
24 | @IBOutlet var itemActionLabel: UILabel?
25 |
26 | /// The label for the comment of the item.
27 | @IBOutlet var itemCommentLabel: UILabel?
28 |
29 | /// An image view displaying a green check if the item is completed.
30 | @IBOutlet var isCompleteImageView: UIImageView?
31 |
32 | /// An image view displaying a greyed out check if the item is not completed.
33 | @IBOutlet var isNotCompleteImageView: UIImageView?
34 |
35 | /// Label showing the N/A text for non-completable items.
36 | @IBOutlet var isNotApplicableLabel: UILabel?
37 |
38 | /// The container view for the comment.
39 | /// - note: I attempted to use a stack view to embed the comment view, however this led to unresolvable
40 | /// autolayout warnings in the console log when initially displaying the cell.
41 | @IBOutlet var itemCommentView: UIView?
42 |
43 | /// The constraint to display or hide the comment view.
44 | @IBOutlet var itemCommentViewConstraint: NSLayoutConstraint?
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/TacBoard/Source/Checklists/UI/ChecklistProcedureTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChecklistProcedureTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// `UITableViewCell` subclass for selecting a procedure.
13 | class ChecklistProcedureTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | // MARK: Outlets
16 |
17 | /// The label displaying the procedure title.
18 | @IBOutlet var titleLabel: UILabel?
19 |
20 | /// The label displaying the procedure subtitle.
21 | @IBOutlet var subtitleLabel: UILabel?
22 |
23 | /// An image view displaying a check box if the procedure is complete.
24 | @IBOutlet var isCompleteImageView: UIImageView?
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/Array.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array where Element: Decodable {
12 |
13 | /// Loads an array from the JSON file at the specified URL.
14 | static func loadFromJSON(url: URL) throws -> [Element] {
15 |
16 | // Load data
17 | let data = try Data(contentsOf: url)
18 |
19 | // Decode and return
20 | let decoder = JSONDecoder()
21 | return try decoder.decode([Element].self, from: data)
22 |
23 | }
24 |
25 | }
26 |
27 | extension Array where Element: Encodable {
28 |
29 | /// Saves this array as a JSON file in the specified URL.
30 | func saveToJSON(url: URL) throws {
31 |
32 | // Encode data
33 | let encoder = JSONEncoder()
34 | let data = try encoder.encode(self)
35 |
36 | // Write data to URL
37 | try data.write(to: url)
38 |
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/CodingUserInfoKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CodingUserInfoKey.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/18/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension CodingUserInfoKey {
12 |
13 | /// The SHA-2 digest for the data used to generate a data index, represented as a `String`.
14 | static let dataIndexKey = CodingUserInfoKey(rawValue: "so.invictus.TacBoard.CodingUserInfoKey.dataIndexKey")!
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/Data.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/18/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import CryptoKit
10 | import Foundation
11 |
12 | extension Data {
13 |
14 | /// Returns a SHA256 digest string for this data object.
15 | var sha256DigestString: String {
16 | return SHA256.hash(data: self).compactMap { String(format: "%02x", $0) }.joined()
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/Decoder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Decoder.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/18/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Decoder {
12 |
13 | /// Returns a `String` representation of the `CodingPath` property.
14 | var codingPathString: String {
15 | return codingPath.compactMap { $0.stringValue }.joined(separator: "/")
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/Double.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/22/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Double {
12 |
13 | /// Returns `true` if this value is negative.
14 | var isNegative: Bool {
15 | return (self < 0.0)
16 | }
17 |
18 | /// The absolute value of this value.
19 | var absolute: Double {
20 | return abs(self)
21 | }
22 |
23 | /// Returns a negative version of this value.
24 | var negative: Double {
25 | guard !isNegative else { return self }
26 | return -self
27 | }
28 |
29 | /// Returns the fractional part of this value.
30 | var fractionalPart: Double {
31 | return truncatingRemainder(dividingBy: 1.0)
32 | }
33 |
34 | /// Returns a truncated `Int` equivalent to this value.
35 | var truncated: Int {
36 | return Int(trunc(self))
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/HTTPURLResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPURLResponse.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/23/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension HTTPURLResponse {
12 |
13 | // MARK: Types
14 |
15 | /// Struct containing constants for HTTP status codes.
16 | struct StatusCode {
17 |
18 | // MARK: Initialization
19 |
20 | private init() { }
21 |
22 | // MARK: Constants
23 |
24 | /// The HTTP status code is 200 (OK)
25 | static let ok: Int = 200
26 |
27 | }
28 |
29 | // MARK: Properties
30 |
31 | /// Returns `true` if `statusCode` is equal to `StatusCode.ok`.
32 | var isStatusCodeOK: Bool {
33 | return (statusCode == StatusCode.ok)
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/Int.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/22/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Int {
12 |
13 | /// Returns `true` if this value is negative.
14 | var isNegative: Bool {
15 | return self < 0
16 | }
17 |
18 | /// The absolute value of this value.
19 | var absolute: Int {
20 | return abs(self)
21 | }
22 |
23 | /// Returns a negative version of this value.
24 | var negative: Int {
25 | guard !isNegative else { return self }
26 | return -self
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/KeyedDecodingContainer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyedDecodingContainer.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension KeyedDecodingContainer {
12 |
13 | /// Decodes a value if it exists, otherwise returns a default.
14 | func decodeOrDefault(_ type: T.Type, forKey key: KeyedDecodingContainer.Key, default: T) throws -> T where T : Decodable {
15 | guard contains(key) else { return `default` }
16 | return try decode(type, forKey: key)
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/Set.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Set.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/7/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Set {
12 |
13 | /// Removes `value` from the `Set` if it is currently a member, and inserts it otherwise.
14 | mutating func toggle(_ value: Element) {
15 | if contains(value) {
16 | remove(value)
17 | } else {
18 | insert(value)
19 | }
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/String.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | // MARK: Constants
14 |
15 | /// An empty string constant.
16 | static let empty = String()
17 |
18 | }
19 |
20 | extension Optional where Wrapped == String {
21 |
22 | // MARK: Properties
23 |
24 | /// Returns `true` if this `String?` is either `nil` or non-`nil` but empty.
25 | var isNilOrEmpty: Bool {
26 | switch self {
27 | case .some(let string):
28 | return string.isEmpty
29 | case .none:
30 | return true
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/UIApplication.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIApplication.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIApplication {
13 |
14 | /// Returns `true` if the application is currently in landscape orientation.
15 | var isLandscape: Bool {
16 | return windows.first?.windowScene?.interfaceOrientation.isLandscape ?? false
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/UIColor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColor.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/2/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIColor {
13 |
14 | // MARK: Initialization
15 |
16 | /// Initializes with the specified color from the application assets.
17 | convenience init(application: ApplicationColor) {
18 | self.init(named: application.rawValue)!
19 | }
20 |
21 | // MARK: Methods
22 |
23 | /// Returns `true` if this color is equal to the specified color by directly comparing RGBA values.
24 | func isEqualToColor(_ color: UIColor) -> Bool {
25 |
26 | let (r1, g1, b1, a1) = components
27 | let (r2, g2, b2, a2) = color.components
28 |
29 | /// Compares `CGFloat`s for equality within a small epsilon.
30 | func almostEqual(_ x: CGFloat, _ y: CGFloat) -> Bool {
31 | let epsilon: CGFloat = 0.00000001
32 | return (abs(x - y) < epsilon)
33 | }
34 |
35 | return (almostEqual(r1, r2) && almostEqual(g1, g2) && almostEqual(b1, b2) && almostEqual(a1, a2))
36 |
37 | }
38 |
39 | /// Returns the RGBA components of this color.
40 | var components: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
41 |
42 | var r: CGFloat = 0.0
43 | var g: CGFloat = 0.0
44 | var b: CGFloat = 0.0
45 | var a: CGFloat = 0.0
46 |
47 | getRed(&r, green: &g, blue: &b, alpha: &a)
48 |
49 | return (r, g, b, a)
50 |
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/UIDevice.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIDevice.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIDevice {
13 |
14 | /// Returns `true` if the current `userInterfaceIdiom` is `.phone`.
15 | var isPhone: Bool {
16 | return userInterfaceIdiom == .phone
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/UIImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIImage.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/5/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import CoreImage
10 | import Foundation
11 | import UIKit
12 |
13 | extension UIImage {
14 |
15 | /// Returns an inverted copy of this image.
16 | var inverted: UIImage? {
17 |
18 | guard
19 | let cgImage = cgImage,
20 | let filter = CIFilter(name: "CIColorInvert")
21 | else { return nil }
22 |
23 | let ciImage = CIImage(cgImage: cgImage)
24 | filter.setValue(ciImage, forKey: kCIInputImageKey)
25 |
26 | guard
27 | let result = filter.value(forKey: kCIOutputImageKey) as? CIImage
28 | else { return nil }
29 |
30 | return UIImage(ciImage: result, scale: scale, orientation: imageOrientation)
31 |
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/UISplitViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UISplitViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/15/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 | import UIKit
12 |
13 | extension UISplitViewController {
14 |
15 | /// - note: This should only be called once for the life of the view controller!
16 | func observeSplitDisplayModeProperty(_ property: MutableProperty) {
17 |
18 | // Don't animate the first time
19 | var animated = false
20 |
21 | // Update any time the property changes
22 | property.producer.take(duringLifetimeOf: self).startWithValues { [unowned self] splitDisplayMode in
23 | self.updateDisplayMode(to: splitDisplayMode, animated: animated)
24 | animated = true
25 | }
26 |
27 | }
28 |
29 | // MARK: Private Utility
30 |
31 | /// Updates the visibility of the master view controller.
32 | private func updateDisplayMode(to splitDisplayMode: SplitDisplayMode, animated: Bool) {
33 |
34 | // Make sure something actually changed
35 | let displayMode = splitDisplayMode.displayMode
36 | guard displayMode != preferredDisplayMode else { return }
37 |
38 | // Animate (or not)
39 | // NOTE: If we are setting this to .automatic, then we need to set to .primaryHidden first to force close the master view controller
40 | UIView.animate(withDuration: animated ? Constants.defaultAnimationDuration : Constants.noAnimationDuration, animations: {
41 | self.preferredDisplayMode = (displayMode == .automatic ? .primaryHidden : displayMode)
42 | }, completion: { _ in
43 | guard displayMode == .automatic else { return }
44 | self.preferredDisplayMode = .automatic
45 | })
46 |
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/UIStoryboard.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIStoryboard.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/22/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | extension UIStoryboard {
13 |
14 | struct App {
15 |
16 | // MARK: Instantiation
17 |
18 | /// Not instantiable.
19 | private init() { }
20 |
21 | // MARK: Constants
22 |
23 | /// The `Airports` application storyboard.
24 | static let airports = UIStoryboard(name: "Airports", bundle: .main)
25 |
26 | /// The `Checklists` application storyboard.
27 | static let checklists = UIStoryboard(name: "Checklists", bundle: .main)
28 |
29 | /// The `Common` application storyboard.
30 | static let common = UIStoryboard(name: "Common", bundle: .main)
31 |
32 | /// The `Home` application storyboard.
33 | static let home = UIStoryboard(name: "Home", bundle: .main)
34 |
35 | /// The `LaunchScreen` application storyboard.
36 | static let launchScreen = UIStoryboard(name: "LaunchScreen", bundle: .main)
37 |
38 | /// The `Main` application storyboard.
39 | static let main = UIStoryboard(name: "Main", bundle: .main)
40 |
41 | /// The `Notepad` application storyboard.
42 | static let notepad = UIStoryboard(name: "Notepad", bundle: .main)
43 |
44 | /// The `Reference` application storyboard.
45 | static let reference = UIStoryboard(name: "Reference", bundle: .main)
46 |
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/UIView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIView.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/9/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveCocoa
11 | import ReactiveSwift
12 | import UIKit
13 |
14 | extension UIView {
15 |
16 | /// Sets `isHidden` if it is not equal to the current `isHidden`.
17 | var safeIsHidden: Bool {
18 | get { return isHidden }
19 | set {
20 |
21 | // WTF!
22 | // https://stackoverflow.com/a/56831635/434245
23 | guard newValue != isHidden else { return }
24 | isHidden = newValue
25 |
26 | }
27 | }
28 |
29 | }
30 |
31 | extension Reactive where Base: UIView {
32 |
33 | /// Binding target for `safeIsHidden`.
34 | var safeIsHidden: BindingTarget {
35 | return makeBindingTarget { $0.safeIsHidden = $1 }
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/URL.swift:
--------------------------------------------------------------------------------
1 | //
2 | // URL.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 8/31/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension URL {
12 |
13 | /// Returns a copy of this URL minus the fragment, if any.
14 | func deletingFragment() -> URL? {
15 |
16 | // Get components from the URL.
17 | guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else {
18 | return nil
19 | }
20 |
21 | // Delete the fragment and return the corresponding URL
22 | components.fragment = nil
23 | return components.url
24 |
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Extensions/WKWebView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WKWebView.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/16/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import WebKit
11 |
12 | extension WKWebView {
13 |
14 | /// Adds a `WKUserScript` to automatically load the CSS file at the specified document.
15 | /// - note: URL must be valid or the app will crash with a `fatalError()`.
16 | func addUserScriptToAutoLoadCSS(at url: URL) {
17 |
18 | // Get the resource at the specified URL
19 | guard var css = try? String(contentsOf: url) else { fatalInvalidResource() }
20 |
21 | // Create the script to add the style element
22 | css = css.components(separatedBy: .newlines).joined(separator: " ")
23 | let source = "var style = document.createElement('style');\nstyle.innerHTML = '\(css)'; document.head.appendChild(style);"
24 |
25 | // Run the script when the web view displays the HTML file
26 | let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
27 | configuration.userContentController.addUserScript(script)
28 |
29 |
30 | }
31 |
32 | /// Adds a `WKUserScript` to force zoom to 100% and disable user scaling.
33 | func addUserScriptToDisableScaling() {
34 |
35 | let source = "var meta = document.createElement('meta'); " +
36 | "meta.name = 'viewport';" +
37 | "meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" +
38 | "document.head.appendChild(meta);"
39 |
40 | let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
41 | configuration.userContentController.addUserScript(script)
42 |
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/ContentManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentManager.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/25/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 |
12 | /// Class managing the current content source for the application.
13 | /// - note: This class is not thread safe. All members are expected to be accessed on the main thread.
14 | class ContentManager {
15 |
16 | // MARK: Fields
17 |
18 | private let (reloadContentSignal, reloadContentObserver) = Signal.pipe()
19 |
20 | // MARK: Initialization / Singleton
21 |
22 | /// The shared `ContentManager` instance.
23 | static let shared = ContentManager()
24 |
25 | /// Initializes a new instance with the specified settings manager.
26 | private init() {
27 | self.source = MutableProperty(.default)
28 | }
29 |
30 | // MARK: Properties
31 |
32 | /// The currently selected content source for the application.
33 | let source: MutableProperty
34 |
35 | /// `Signal` sending a `Void` signal when content should be reloaded.
36 | var reloadContent: Signal {
37 | return reloadContentSignal
38 | }
39 |
40 | // MARK: Methods
41 |
42 | /// The base URL for the currently selected content source.
43 | var baseURL: URL {
44 | return ContentManager.baseURL(source: source.value)
45 | }
46 |
47 | /// Returns the URL for the content at the specified relative path.
48 | func url(forRelativePath path: String) -> URL {
49 | return ContentManager.url(forRelativePath: path, source: source.value)
50 | }
51 |
52 | /// Instructs the app to reload its content now.
53 | func reloadContentNow() {
54 | reloadContentObserver.send(value: ())
55 | }
56 |
57 | // MARK: Static Utility
58 |
59 | /// Returns the base URL for the specified content source.
60 | static func baseURL(source: ContentSource) -> URL {
61 | return source.baseURL
62 | }
63 |
64 | /// Returns the URL for the content at the specified relative path with the specified source.
65 | static func url(forRelativePath path: String, source: ContentSource) -> URL {
66 | return baseURL(source: source).appendingPathComponent(path)
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/ContentSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentSource.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/25/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of the valid content sources for the application.
12 | enum ContentSource: CustomStringConvertible {
13 |
14 | // MARK: Cases
15 |
16 | #if DEBUG
17 |
18 | /// The application will use the content embedded in the application bundle.
19 | case local
20 |
21 | /// The application will use the content in the CDN staging directory.
22 | case staging
23 |
24 | #endif
25 |
26 | /// The application will use the content in the CDN production directory.
27 | case production
28 |
29 | /// The application will use the embedded fallback resources.
30 | case fallback
31 |
32 | // MARK: Properties
33 |
34 | /// The default selected content source.
35 | static let `default`: ContentSource = {
36 | #if DEBUG
37 | return .local
38 | #else
39 | return .production
40 | #endif
41 | }()
42 |
43 | /// Returns the base URL for this content source.
44 | var baseURL: URL {
45 | switch self {
46 |
47 | #if DEBUG
48 | case .local:
49 | return Bundle.main.resourceURL!.appendingPathComponent("TacBoardData", isDirectory: true)
50 | case .staging:
51 | return URL(string: "http://www.invictus.so/app-content-stage/TacBoardData")!
52 | #endif
53 |
54 | case .production:
55 | return URL(string: "http://www.invictus.so/app-content/TacBoardData")!
56 | case .fallback:
57 | return Bundle.main.resourceURL!
58 |
59 | }
60 | }
61 |
62 | /// Returns a custom string for this enum value.
63 | var description: String {
64 | switch self {
65 |
66 | #if DEBUG
67 | case .local:
68 | return "Local"
69 | case .staging:
70 | return "Staging"
71 | #endif
72 |
73 | case .production:
74 | return "Production"
75 | case .fallback:
76 | return "Fallback"
77 |
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/DataIndexKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataIndexKey.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/18/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: - DataIndexKey
12 |
13 | /// An object used as a unique key for an object contained in a `DataIndex` instance.
14 | /// - note: For now, this is just a `typealias` for `String`.
15 | typealias DataIndexKey = String
16 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/Defaultable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Defaultable.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/3/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Protocol for objects defining a default value.
12 | protocol Defaultable {
13 |
14 | /// The default value for this type.
15 | static var `default`: Self { get }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/Folder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Folder.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/15/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Abstract base class for objects representing a "folder" of sub-items.
12 | class Folder- : Decodable, ReferenceEquatable where Item: BinderItem {
13 |
14 | // MARK: Initialization
15 |
16 | /// Initializes a new instance with the specified values.
17 | init(key: DataIndexKey, title: String, subfolders: [Folder
- ], items: [Item] = []) {
18 |
19 | self.key = key
20 | self.title = title
21 | self.subfolders = subfolders
22 | self.items = items
23 |
24 | }
25 |
26 | // MARK: Properties
27 |
28 | /// A unique key for this folder.
29 | let key: DataIndexKey
30 |
31 | /// The title of this folder.
32 | let title: String
33 |
34 | /// The subfolders contained by this folder.
35 | let subfolders: [Folder
- ]
36 |
37 | /// The items contained in this folder.
38 | let items: [Item]
39 |
40 | /// The total number of items contained in this folder and all of its subfolders.
41 | lazy var itemCount: Int = {
42 | return items.count + subfolders.reduce(0) { $0 + $1.itemCount }
43 | }()
44 |
45 | // MARK: Methods
46 |
47 | /// Returns `true` if this folder (or any of its subfolders) contains the specified folder.
48 | func contains(folder: Folder
- ) -> Bool {
49 | return subfolders.contains { $0 == folder || $0.contains(folder: folder) }
50 | }
51 |
52 | /// Returns `true` if this folder (or any of its subfolders) contains the specified item.
53 | func contains(item: Item) -> Bool {
54 | return items.contains { $0 == item || subfolders.contains { $0.contains(item: item) } }
55 | }
56 |
57 | /// Returns the first `Item` contained in this folder or its subfolders which matches `predicate`.
58 | func firstItem(where predicate: (Item) -> Bool) -> Item? {
59 |
60 | // First search our own items
61 | for item in items {
62 | if predicate(item) {
63 | return item
64 | }
65 | }
66 |
67 | // Then recursively search subfolders
68 | for subfolder in subfolders {
69 | if let item = subfolder.firstItem(where: predicate) {
70 | return item
71 | }
72 | }
73 |
74 | // If those both fail, return nil
75 | return nil
76 |
77 | }
78 |
79 | // MARK: Decodable
80 |
81 | /// `CodingKey` enum for this class.
82 | private enum CodingKeys: String, CodingKey {
83 | case title
84 | case subfolders
85 | case items
86 | }
87 |
88 | /// Initializes a new instance with the specified `Decoder`.
89 | convenience required init(from decoder: Decoder) throws {
90 | let container = try decoder.container(keyedBy: CodingKeys.self)
91 | self.init(key: DataIndex>.key(for: decoder),
92 | title: try container.decode(String.self, forKey: .title),
93 | subfolders: try container.decodeOrDefault([Folder
- ].self, forKey: .subfolders, default: []),
94 | items: try container.decodeOrDefault([Item].self, forKey: .items, default: []))
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/ISO3166.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ISO3166.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/4/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of ISO-3166 country codes.
12 | enum ISO3166: String, Codable {
13 |
14 | // MARK: Cases
15 |
16 | /// Country code for Georgia.
17 | case ge = "GE"
18 |
19 | /// Country code for Russia.
20 | case ru = "RU"
21 |
22 | /// Country code for the United States.
23 | case us = "US"
24 |
25 | // MARK: Properties
26 |
27 | /// Returns the flag emoji for this country.
28 | var flag: String {
29 | switch self {
30 | case .ge: return "🇬🇪"
31 | case .ru: return "🇷🇺"
32 | case .us: return "🇺🇸"
33 | }
34 | }
35 |
36 | /// Returns the name of this country.
37 | var name: String {
38 | switch self {
39 | case .ge: return LocalizableString(.iso3166Georgia)
40 | case .ru: return LocalizableString(.iso3166Russia)
41 | case .us: return LocalizableString(.iso3166UnitedStates)
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/NavaidType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NavaidType.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/7/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of the available navaid types.
12 | enum NavaidType: String, Codable, CustomStringConvertible {
13 |
14 | // MARK: Cases
15 |
16 | /// The navaid is a VOR.
17 | case VOR
18 |
19 | /// The navaid is a DME.
20 | case DME
21 |
22 | /// The navaid is a VOR/DME.
23 | case VORDME
24 |
25 | /// The navaid is a TACAN.
26 | case TACAN
27 |
28 | /// The navaid is a VORTAC.
29 | case VORTAC
30 |
31 | /// The navaid is an RSBN.
32 | case RSBN
33 |
34 | /// The navaid is an NDB.
35 | case NDB
36 |
37 | /// The navaid is an inner marker NDB.
38 | case innerMarker
39 |
40 | /// The navaid is an outer marker NDB.
41 | case outerMarker
42 |
43 | /// The navaid is an ILS.
44 | case ILS
45 |
46 | /// The navaid is a PRMG.
47 | case PRMG
48 |
49 | /// The navaid is an ICLS.
50 | case ICLS
51 |
52 | // MARK: CustomStringConvertible
53 |
54 | /// Returns a `String` description of this enum.
55 | var description: String {
56 | switch self {
57 | case .VOR:
58 | return LocalizableString(.navaidTypeVOR)
59 | case .DME:
60 | return LocalizableString(.navaidTypeDME)
61 | case .VORDME:
62 | return LocalizableString(.navaidTypeVORDME)
63 | case .TACAN:
64 | return LocalizableString(.navaidTypeTACAN)
65 | case .VORTAC:
66 | return LocalizableString(.navaidTypeVORTAC)
67 | case .RSBN:
68 | return LocalizableString(.navaidTypeRSBN)
69 | case .NDB:
70 | return LocalizableString(.navaidTypeNDB)
71 | case .innerMarker:
72 | return LocalizableString(.navaidTypeInnerMarker)
73 | case .outerMarker:
74 | return LocalizableString(.navaidTypeOuterMarker)
75 | case .ILS:
76 | return LocalizableString(.navaidTypeILS)
77 | case .PRMG:
78 | return LocalizableString(.navaidTypePRMG)
79 | case .ICLS:
80 | return LocalizableString(.navaidTypeICLS)
81 | }
82 | }
83 |
84 | /// The sorting order for this enum.
85 | var sortOrder: Int {
86 | switch self {
87 | case .ILS: return 0
88 | case .ICLS: return 1
89 | case .RSBN: return 2
90 | case .PRMG: return 3
91 | case .TACAN: return 4
92 | case .VORTAC: return 5
93 | case .VORDME: return 6
94 | case .VOR: return 7
95 | case .NDB: return 8
96 | case .outerMarker: return 9
97 | case .innerMarker: return 10
98 | case .DME: return 11
99 | }
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/RadioFrequencyBand.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RadioFrequencyBand.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/6/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of radio frequency bands.
12 | enum RadioFrequencyBand: String, Codable, CustomStringConvertible {
13 |
14 | // MARK: Cases
15 |
16 | /// HF frequency band.
17 | case HF = "HF"
18 |
19 | /// Low VHF frequency band.
20 | case VHFLow = "VHFLow"
21 |
22 | /// High VHF frequency band.
23 | case VHFHigh = "VHFHigh"
24 |
25 | /// UHF frequency band.
26 | case UHF = "UHF"
27 |
28 | // MARK: Properties
29 |
30 | /// Returns a `String` description of this enum.
31 | var description: String {
32 | switch self {
33 | case .HF:
34 | return LocalizableString(.radioFrequencyBandHF)
35 | case .VHFLow:
36 | return LocalizableString(.radioFrequencyBandVHFLow)
37 | case .VHFHigh:
38 | return LocalizableString(.radioFrequencyBandVHFHigh)
39 | case .UHF:
40 | return LocalizableString(.radioFrequencyBandUHF)
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/RadioModulationType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RadioModulationType.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/7/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of radio modulation types.
12 | enum RadioModulationType: String, Codable, CustomStringConvertible {
13 |
14 | // MARK: Cases
15 |
16 | /// Amplitude modulation.
17 | case AM
18 |
19 | /// Frequency modulation.
20 | case FM
21 |
22 | /// Amplitude and frequency modulation.
23 | case AMFM
24 |
25 | // MARK: CustomStringConvertible
26 |
27 | /// Returns a `String` description of this enum.
28 | var description: String {
29 | switch self {
30 | case .AM:
31 | return LocalizableString(.radioModulationTypeAM)
32 | case .FM:
33 | return LocalizableString(.radioModulationTypeFM)
34 | case .AMFM:
35 | return LocalizableString(.radioModulationTypeAMFM)
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/ReferenceEquatable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReferenceEquatable.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/15/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Protocol for objects implementing `Equatable` by checking reference equality.
12 | protocol ReferenceEquatable: class, Equatable { }
13 |
14 | extension ReferenceEquatable {
15 |
16 | /// Equality operator performing a reference comparison.
17 | static func ==(lhs: Self, rhs: Self) -> Bool {
18 | return (lhs === rhs)
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/Model/ResourceLocation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResourceLocation.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/23/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of supported resource locations.
12 | enum ResourceLocation {
13 |
14 | /// The image is contained in a local asset file.
15 | case asset(name: String)
16 |
17 | /// The image is a path relative to the current content base URL.
18 | case relative(path: String)
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/UI/BlockButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BlockButton.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/23/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// `UIButton` subclass accepting a block to be executed.
13 | class BlockButton: UIButton {
14 |
15 | // MARK: Variables
16 |
17 | /// A block to be executed on the `.touchUpInside` event.
18 | var touchUpInside: (() -> Void)?
19 |
20 | // MARK: UIButton Overrides
21 |
22 | /// Configures the control after it is deserialized from the nib.
23 | override func awakeFromNib() {
24 | super.awakeFromNib()
25 | addTarget(self, action: #selector(didTouchUpInside(_:)), for: .touchUpInside)
26 | }
27 |
28 | // MARK: Actions
29 |
30 | /// Handler for the `.touchUpInside` event.
31 | @IBAction
32 | private func didTouchUpInside(_ sender: UIButton) {
33 | touchUpInside?()
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/UI/FolderTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FolderTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/15/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Cell for displaying folders.
13 | class FolderTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | // MARK: Outlets
16 |
17 | /// The label for the title of the folder.
18 | @IBOutlet private var titleLabel: UILabel?
19 |
20 | // MARK: Methods
21 |
22 | /// Configures this cell for the specified folder.
23 | func configure
- (for folder: Folder
- ) {
24 | titleLabel?.text = folder.title
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/UI/PlaceholderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PlaceholderViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Placholder view controller for when no item is selected.
13 | class PlaceholderViewController: UIViewController {
14 |
15 | // TBD
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/UI/PopoverContainerViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PopoverContainerViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/3/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | // MARK: - PopoverContainerViewController
13 |
14 | /// Class for the settings view controller for the notepad.
15 | class PopoverContainerViewController: UIViewController {
16 |
17 | // MARK: Outlets
18 |
19 | @IBOutlet private var contentView: UIView?
20 |
21 | // MARK: UIViewController Overrides
22 |
23 | /// Initializes the view controller after the view loads.
24 | override func viewDidLoad() {
25 |
26 | super.viewDidLoad()
27 |
28 | // Configure popover presentation on iPad
29 | if !UIDevice.current.isPhone {
30 | contentView?.layer.borderColor = UIColor(application: .popoverBorder).cgColor
31 | contentView?.layer.borderWidth = Constants.popoverBorderWidth
32 | contentView?.layer.cornerRadius = Constants.popoverCornerRadius
33 | }
34 |
35 | }
36 |
37 | }
38 |
39 | // MARK: - ViewModelPopoverContainerViewController
40 |
41 | /// Subclass of `PopoverContainerViewController` accepting a view model for `IBSegueAction` purposes.
42 | class ViewModelPopoverContainerViewController: PopoverContainerViewController {
43 |
44 | // MARK: Initialization
45 |
46 | /// Initializer not available.
47 | @available(*, unavailable)
48 | required init?(coder: NSCoder) { fatalNotAvailable() }
49 |
50 | /// Initializes a new instance with the specified coder and view model.
51 | init?(coder: NSCoder, viewModel: ViewModelType) {
52 | self.viewModel = viewModel
53 | super.init(coder: coder)
54 | }
55 |
56 | // MARK: Properties
57 |
58 | /// The view model to pass on to the container view controller.
59 | let viewModel: ViewModelType
60 |
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/UI/SegmentedControl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SegmentedControl.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/2/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Custom segmented control with additional functionality.
13 | class SegmentedControl: UISegmentedControl {
14 |
15 | // MARK: Properties
16 |
17 | /// The text color to use for the title of non-selected segments.
18 | @IBInspectable var normalTitleTextColor: UIColor = .label {
19 | didSet { updateTitleTextAttributes() }
20 | }
21 |
22 | /// The text color to use for the title of selected segments.
23 | @IBInspectable var selectedTitleTextColor: UIColor = .label {
24 | didSet { updateTitleTextAttributes() }
25 | }
26 |
27 | // MARK: UIView Overrides
28 |
29 | /// Sets up the view after it is deserialized from the nib.
30 | override func awakeFromNib() {
31 | super.awakeFromNib()
32 | updateTitleTextAttributes()
33 | }
34 |
35 | // MARK: Methods
36 |
37 | /// Configures this segmented controller to display a segment for each item in the specified enum type.
38 | func configure(for _: T.Type, animated: Bool = false) {
39 |
40 | // Remove any existing segments
41 | removeAllSegments()
42 |
43 | // Add a segment for each item in the enum
44 | for (index, item) in T.allCases.enumerated() {
45 | insertSegment(withTitle: String(describing: item), at: index, animated: animated)
46 | }
47 |
48 | }
49 |
50 | /// Returns a value of the specified enum type for the currently selected index.
51 | func value(for _: T.Type) -> T where T.AllCases.Index == Int {
52 |
53 | // Note that this will crash if the selected segment index is not valid for the specified enum.
54 | // This is what we want in this case since it represents a programmer error.
55 | return T.allCases[selectedSegmentIndex]
56 |
57 | }
58 |
59 | /// Sets the selected segment index for the specified enum type.
60 | func setValue(_ value: T, for _: T.Type) where T.AllCases.Index == Int {
61 | guard let index = T.allCases.firstIndex(where: { $0 == value }) else { fatalError("Value not found!") }
62 | selectedSegmentIndex = index
63 | }
64 |
65 | // MARK: Private Utility
66 |
67 | /// Updates the title text attributes.
68 | private func updateTitleTextAttributes() {
69 |
70 | // I would prefer to not set the font here, but if we use the UIAppearance proxy then it overrides the text colors
71 | setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 14.0), .foregroundColor: normalTitleTextColor], for: .normal)
72 | setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 14.0), .foregroundColor: selectedTitleTextColor], for: .selected)
73 |
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/UI/SplitDisplayModeTogglingViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitDisplayModeTogglingViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/17/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Protocol for view controllers with a split mode toggle button.
13 | protocol SplitDisplayModeTogglingViewController {
14 |
15 | /// The `UIBarButtonItem` for toggling the split display mode.
16 | var splitDisplayModeBarButtonItem: UIBarButtonItem? { get }
17 |
18 | }
19 |
20 | extension SplitDisplayModeTogglingViewController where Self: UIViewController {
21 |
22 | /// Updates the visibility of `splitDisplayModeBarButtonItem` based on the specified trait collection, using the specified transition coordinator.
23 | /// - note: If `traitCollection` is `nil`, the view controller's current trait collection will be checked.
24 | func updateIsSplitDisplayModeButtonHidden(with traitCollection: UITraitCollection? = nil, coordinator: UIViewControllerTransitionCoordinator) {
25 | coordinator.animate(alongsideTransition: { _ in
26 | self.updateIsSplitDisplayModeButtonHidden(with: traitCollection)
27 | }, completion: nil)
28 | }
29 |
30 | /// Updates the visibility of `splitDisplayModeBarButtonItem` based on the specified trait collection.
31 | /// - note: If `traitCollection` is `nil`, the view controller's current trait collection will be checked.
32 | func updateIsSplitDisplayModeButtonHidden(with traitCollection: UITraitCollection? = nil) {
33 |
34 | let traitCollection = traitCollection ?? self.traitCollection
35 |
36 | // Always hide the button in compact horizontal size class
37 | guard traitCollection.horizontalSizeClass != .compact else {
38 | isSplitDisplayModeBarButtonItemHidden = true
39 | return
40 | }
41 |
42 | // If this is iPhone in regular horizontal size class (i.e., a Pro model in landscape mode), show the button
43 | guard !UIDevice.current.isPhone else {
44 | isSplitDisplayModeBarButtonItemHidden = false
45 | return
46 | }
47 |
48 | // Otherwise, this is an iPad. Hide the button in landscape mode
49 | isSplitDisplayModeBarButtonItemHidden = UIApplication.shared.isLandscape
50 |
51 | }
52 |
53 | /// Shows or hides the `splitDisplayModeBarButtonItem` bar button item.
54 | var isSplitDisplayModeBarButtonItemHidden: Bool {
55 | get {
56 |
57 | guard
58 | let splitDisplayModeBarButtonItem = splitDisplayModeBarButtonItem
59 | else { return false }
60 |
61 | return !(navigationItem.rightBarButtonItems?.contains { $0 === splitDisplayModeBarButtonItem } ?? false)
62 |
63 | }
64 | set {
65 |
66 | guard
67 | let splitDisplayModeBarButtonItem = splitDisplayModeBarButtonItem,
68 | newValue != isSplitDisplayModeBarButtonItemHidden
69 | else { return }
70 |
71 | if newValue {
72 | navigationItem.rightBarButtonItems?.removeAll { $0 === splitDisplayModeBarButtonItem }
73 | } else {
74 | navigationItem.rightBarButtonItems?.append(splitDisplayModeBarButtonItem)
75 | }
76 |
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/TacBoard/Source/Common/UI/TableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/7/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// `UITableViewController` subclass with additional tweaks.
13 | class TableViewController: UITableViewController {
14 |
15 | // MARK: UITableViewDelegate
16 |
17 | /// Configures the font of each header view when it is displayed.
18 | override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
19 |
20 | //
21 | // NOTE
22 | //
23 | // This is a workaround for an apparent bug. The font is correctly set by the appearance proxy the *first*
24 | // time the view is displayed, however, if we refresh the table view with reloadData() it reverts back to
25 | // the default system fault.
26 | //
27 | // To work around this, we manually update the font of the header view here.
28 | //
29 | guard let header = view as? UITableViewHeaderFooterView else { return }
30 | header.textLabel?.font = UIFont.systemFont(ofSize: Constants.verySmallTextSize)
31 |
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/TacBoard/Source/Home/HomeHelpViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeHelpViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/12/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import WebKit
12 |
13 | /// View controller for displaying the help page.
14 | class HomeHelpViewController: UIViewController, WKNavigationDelegate {
15 |
16 | // MARK: Outlets
17 |
18 | @IBOutlet private var webView: WKWebView?
19 |
20 | // MARK: Properties
21 |
22 | /// If set to a non-`nil` value, the page will start at the specified anchor.
23 | var initialAnchor: String? = nil
24 |
25 | // MARK: UIViewController Overrides
26 |
27 | /// Sets up the view controller after the view has loaded.
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 | initializeWebView()
31 | }
32 |
33 | // MARK: WKNavigationDelegate
34 |
35 | /// Decides the navigation policy for the specified action.
36 | func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
37 |
38 | // This is only relevante for links to external domains
39 | guard
40 | navigationAction.navigationType == .linkActivated,
41 | let url = navigationAction.request.url,
42 | !url.isFileURL
43 | else { decisionHandler(.allow); return }
44 |
45 | // Open the URL in an external browser
46 | UIApplication.shared.open(url, options: [:], completionHandler: nil)
47 | decisionHandler(.cancel)
48 |
49 | }
50 |
51 | // MARK: Private Utility
52 |
53 | /// Initializes the web view to display the help page.
54 | private func initializeWebView() {
55 |
56 | webView?.navigationDelegate = self
57 |
58 | let fragment: String = {
59 | guard let initialAnchor = initialAnchor else { return "" }
60 | return "#\(initialAnchor)"
61 | }()
62 |
63 | guard
64 | let baseURL = Bundle.main.url(forResource: "Help", withExtension: "html", subdirectory: "Help"),
65 | let loadURL = URL(string: "\(baseURL.absoluteString)\(fragment)")
66 | else { return }
67 |
68 | webView?.loadFileURL(loadURL, allowingReadAccessTo: loadURL.deletingLastPathComponent())
69 |
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/TacBoard/Source/Home/HomeTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/7/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Cell used on the home page.
13 | class HomeTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | // MARK: Outlets
16 |
17 | /// The icon image view.
18 | @IBOutlet var iconImageView: UIImageView?
19 |
20 | /// The title label.
21 | @IBOutlet var titleLabel: UILabel?
22 |
23 | /// The subtitle label.
24 | @IBOutlet var subtitleLabel: UILabel?
25 |
26 | /// The stack view for the auxiliary data field.
27 | @IBOutlet var auxiliaryDataStackView: UIStackView?
28 |
29 | /// The image view for the auxiliary data icon.
30 | @IBOutlet var auxiliaryDataIconImageView: UIImageView?
31 |
32 | /// The label for the auxiliary data text.
33 | @IBOutlet var auxiliaryDataLabel: UILabel?
34 |
35 | // MARK: UITableViewCell Overrides
36 |
37 | /// Initializes the cell when it is deserialized from the nib.
38 | override func awakeFromNib() {
39 | super.awakeFromNib()
40 | initializeCell()
41 | }
42 |
43 | /// Initializes the cell when it is about to be reused.
44 | override func prepareForReuse() {
45 | super.prepareForReuse()
46 | initializeCell()
47 | }
48 |
49 | // MARK: Methods
50 |
51 | /// Configures this cell for the specified module.
52 | func configure(for module: ModuleType, isEnabled: Bool) where ModuleType: Module {
53 |
54 | iconImageView?.image = module.icon
55 | iconImageView?.safeIsHidden = (module.icon == nil)
56 |
57 | titleLabel?.text = (traitCollection.horizontalSizeClass == .compact ? module.compactTitle : module.title)
58 | titleLabel?.safeIsHidden = false
59 |
60 | subtitleLabel?.text = module.author
61 | subtitleLabel?.safeIsHidden = module.author.isNilOrEmpty
62 |
63 | accessoryType = (isEnabled ? .checkmark : .none)
64 |
65 | }
66 |
67 | // MARK: Private Utility
68 |
69 | /// Sets the cell to its initial state.
70 | private func initializeCell() {
71 |
72 | iconImageView?.image = nil
73 | iconImageView?.safeIsHidden = true
74 |
75 | titleLabel?.text = nil
76 | titleLabel?.safeIsHidden = true
77 |
78 | subtitleLabel?.text = nil
79 | subtitleLabel?.safeIsHidden = true
80 |
81 | auxiliaryDataStackView?.safeIsHidden = true
82 |
83 | auxiliaryDataIconImageView?.image = nil
84 | auxiliaryDataIconImageView?.safeIsHidden = true
85 |
86 | auxiliaryDataLabel?.text = nil
87 | auxiliaryDataLabel?.safeIsHidden = true
88 |
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/TacBoard/Source/Home/HomeTitleTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeTitleTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/9/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Custom cell for the app title on the home page.
13 | class HomeTitleTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | // MARK: Outlets
16 |
17 | /// The label for the application title.
18 | @IBOutlet var titleLabel: UILabel?
19 |
20 | /// The label for the application version.
21 | @IBOutlet var versionLabel: UILabel?
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/TacBoard/Source/Main/MainViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Chris Vig on 9/21/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// The main view controller for the application.
13 | class MainViewController: UIViewController {
14 |
15 | // MARK: Fields
16 |
17 | private var embeddedTabBarController: UITabBarController?
18 |
19 | // MARK: UIViewController Overrides
20 |
21 | /// Prepares for the specified segue.
22 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
23 | switch segue.identifier {
24 |
25 | case "EmbedTabBarController":
26 | guard let controller = segue.destination as? UITabBarController else { fatalInvalidSegue() }
27 | embeddedTabBarController = controller
28 |
29 | default:
30 | break
31 |
32 | }
33 | }
34 |
35 | /// Displays the help page with the specified initial anchor.
36 | func showHelp(initialAnchor: String? = nil) {
37 |
38 | let helpViewController: HomeHelpViewController = UIStoryboard.App.home.instantiateViewController(identifier: "Help")
39 | helpViewController.initialAnchor = initialAnchor
40 |
41 | showHomePageStack([helpViewController])
42 |
43 | }
44 |
45 | /// Displays the user content list.
46 | func showUserContentList() {
47 |
48 | let aboutViewController: HomeAboutViewController = UIStoryboard.App.home.instantiateViewController(identifier: "About")
49 | let userContentListViewController: UserContentListViewController = UIStoryboard.App.home.instantiateViewController(identifier: "UserContentList")
50 |
51 | showHomePageStack([aboutViewController, userContentListViewController])
52 |
53 | }
54 |
55 | // MARK: Properties
56 |
57 | /// The root `MainViewController` for the application, if one is active.
58 | static var active: MainViewController? {
59 |
60 | guard
61 | let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
62 | let window = scene.windows.first,
63 | let controller = window.rootViewController as? MainViewController
64 | else { return nil }
65 |
66 | return controller
67 |
68 | }
69 |
70 | // MARK: Private Utility
71 |
72 | /// Displays the specified page stack from the home page.
73 | private func showHomePageStack(_ controllers: [UIViewController]) {
74 |
75 | guard
76 | let embeddedTabBarController = embeddedTabBarController,
77 | let embeddedViewControllers = embeddedTabBarController.viewControllers,
78 | let homeIndex = embeddedViewControllers.firstIndex(where: { ($0 as? UINavigationController)?.viewControllers.first is HomeViewController }),
79 | let homeNavigationController = embeddedViewControllers[homeIndex] as? UINavigationController,
80 | let homeViewController = homeNavigationController.viewControllers.first as? HomeViewController
81 | else { return }
82 |
83 | homeNavigationController.setViewControllers([homeViewController] + controllers, animated: false)
84 | embeddedTabBarController.selectedIndex = homeIndex
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/TacBoard/Source/Modules/Model/AircraftModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AircraftModule.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/7/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Enumeration of the aircraft modules supported by DCS.
13 | final class AircraftModule: Module {
14 |
15 | // MARK: Constants
16 |
17 | /// An array of all available aircraft modules.
18 | static var all: [AircraftModule] = {
19 |
20 | guard
21 | let url = Bundle.main.url(forResource: "AircraftModules", withExtension: "json")
22 | else { fatalInvalidResource() }
23 |
24 | do {
25 | let modules = try [AircraftModule].loadFromJSON(url: url)
26 | return modules.sorted { $0.title < $1.title }
27 | } catch {
28 | NSLog("Failed to deserialize aircraft modules! \(error)")
29 | fatalInvalidResource()
30 | }
31 |
32 | }()
33 |
34 | // MARK: Properties
35 |
36 | /// A `UIImage` representing the icon for this module.
37 | lazy var icon: UIImage? = {
38 | return UIImage(named: "Icon-AircraftModule-\(key)") ?? UIImage(named: "Icon-AircraftModule-Generic")
39 | }()
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/TacBoard/Source/Modules/Model/TerrainModule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TerrainModule.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/7/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Enumeration of the terrain modules supported by DCS.
13 | final class TerrainModule: Module {
14 |
15 | // MARK: Constants
16 |
17 | /// An array of all available terrain modules.
18 | static var all: [TerrainModule] = {
19 |
20 | guard
21 | let url = Bundle.main.url(forResource: "TerrainModules", withExtension: "json")
22 | else { fatalInvalidResource() }
23 |
24 | do {
25 | let modules = try [TerrainModule].loadFromJSON(url: url)
26 | return modules.sorted { $0.title < $1.title }
27 | } catch {
28 | NSLog("Failed to deserialize terrain modules! \(error)")
29 | fatalInvalidResource()
30 | }
31 |
32 | }()
33 |
34 | // MARK: Properties
35 |
36 | /// Returns a `UIImage` representing the icon for this terrain module.
37 | lazy var icon: UIImage? = {
38 | return UIImage(named: "Icon-TerrainModule-\(self.key)") ?? UIImage(named: "Icon-TerrainModule-Generic")
39 | }()
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/TacBoard/Source/Notepad/Model/NotepadPage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotepadPage.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/3/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of notepad pages.
12 | enum NotepadPage: String, CaseIterable, CustomStringConvertible, Defaultable {
13 |
14 | // MARK: Cases
15 |
16 | /// A blank page.
17 | case blank1
18 |
19 | /// A blank page.
20 | case blank2
21 |
22 | /// A 9-line pre-attack briefing.
23 | case nineLineCAS
24 |
25 | // MARK: Constants
26 |
27 | /// The default notepad page to display.
28 | static let `default`: NotepadPage = .blank1
29 |
30 | // MARK: Properties
31 |
32 | /// A string describing this enum value.
33 | var description: String {
34 | switch self {
35 | case .blank1:
36 | return LocalizableString(.notepadPageBlank1)
37 | case .blank2:
38 | return LocalizableString(.notepadPageBlank2)
39 | case .nineLineCAS:
40 | return LocalizableString(.notepadPageNineLineCAS)
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/TacBoard/Source/Notepad/Model/NotepadPath.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotepadPath.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/2/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Struct containing a single path drawn by the user.
13 | struct NotepadPath {
14 |
15 | // MARK: Constants
16 |
17 | /// The default path color.
18 | static var defaultColor: UIColor {
19 | return UIColor(application: .tint)
20 | }
21 |
22 | /// The minimum path width.
23 | static var minimumWidth: CGFloat {
24 | return (UIDevice.current.isPhone ? 3.0 : 5.0)
25 | }
26 |
27 | /// The maximum path width.
28 | static var maximumWidth: CGFloat {
29 | return (UIDevice.current.isPhone ? 36.0 : 60.0)
30 | }
31 |
32 | /// The default path width.
33 | static var defaultWidth: CGFloat {
34 | return (UIDevice.current.isPhone ? 6.0 : 10.0)
35 | }
36 |
37 | // MARK: Fields
38 |
39 | private let mutablePath: CGMutablePath
40 |
41 | // MARK: Initialization
42 |
43 | /// Initializes a new instance with the specified values.
44 | init(at point: CGPoint? = nil, color: UIColor = NotepadPath.defaultColor, width: CGFloat = NotepadPath.defaultWidth) {
45 | self.mutablePath = CGMutablePath()
46 | if let point = point { self.mutablePath.move(to: point) }
47 | self.color = color
48 | self.width = width
49 | }
50 |
51 | // MARK: Properties
52 |
53 | /// The path to draw.
54 | var path: CGPath { mutablePath.copy()! }
55 |
56 | /// The color of the path.
57 | let color: UIColor
58 |
59 | /// The width of the path.
60 | let width: CGFloat
61 |
62 | // MARK: Methods
63 |
64 | /// Moves the path to the specified point.
65 | mutating func move(to point: CGPoint) {
66 | mutablePath.move(to: point)
67 | }
68 |
69 | /// Adds a line to the specified point.
70 | mutating func addLine(to point: CGPoint) {
71 | mutablePath.addLine(to: point)
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/TacBoard/Source/Notepad/Model/NotepadViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotepadViewModel.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/3/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ReactiveSwift
11 | import UIKit
12 |
13 | /// View model for the notepad pages.
14 | class NotepadViewModel {
15 |
16 | // MARK: Fields
17 |
18 | private var pathsLookup: [NotepadPage: MutableProperty<[NotepadPath]>]
19 | private var activePathLookup: [NotepadPage: MutableProperty]
20 | private var isEmptyLookup: [NotepadPage: Property]
21 |
22 | // MARK: Initialization
23 |
24 | /// Initializes a new instance with the specified settings manager.
25 | init(settingsManager: SettingsManager = SettingsManager.shared) {
26 |
27 | // Persisted properties
28 | self.selectedPage = settingsManager.notepadSelectedPage
29 | self.selectedPathColor = settingsManager.notepadSelectedPathColor
30 | self.selectedPathWidth = settingsManager.notepadSelectedPathWidth
31 |
32 | self.pathsLookup = NotepadPage.allCases.reduce(into: [:]) { lookup, page in
33 | lookup[page] = MutableProperty([])
34 | }
35 | self.activePathLookup = NotepadPage.allCases.reduce(into: [:]) { lookup, page in
36 | lookup[page] = MutableProperty(nil)
37 | }
38 |
39 | // Derived properties
40 | let pathsLookup = self.pathsLookup // ugh
41 | self.isEmptyLookup = NotepadPage.allCases.reduce(into: [:]) { lookup, page in
42 | lookup[page] = Property(initial: false, then: pathsLookup[page]!.producer.map { $0.isEmpty })
43 | }
44 |
45 | }
46 |
47 | // MARK: Properties
48 |
49 | /// The currently selected notepad page.
50 | let selectedPage: MutableProperty
51 |
52 | /// The currently active path color.
53 | let selectedPathColor: MutableProperty
54 |
55 | /// The currently active path width.
56 | let selectedPathWidth: MutableProperty
57 |
58 | /// The color to be used for the currently active path.
59 | lazy var activePathColor: Property = {
60 | return Property(self.selectedPathColor)
61 | }()
62 |
63 | /// The line width to be used for the currently active path.
64 | lazy var activePathWidth: Property = {
65 | let producer = SignalProducer.combineLatest(self.selectedPathColor.producer, self.selectedPathWidth.producer)
66 | return Property(initial: self.selectedPathWidth.value, then: producer.map { (color, width) -> CGFloat in
67 | if color.isEqualToColor(UIColor(application: .notepadBackground)) {
68 |
69 | // If the eraser is selected, then make the pen wider to make it easier to erase things
70 | return width * 4.0
71 |
72 | } else {
73 |
74 | // Otherwise, just use the user-selected width
75 | return width
76 |
77 | }
78 | })
79 | }()
80 |
81 | /// Returns a `MutableProperty` containing the paths for the specified page.
82 | func paths(page: NotepadPage) -> MutableProperty<[NotepadPath]> {
83 | return pathsLookup[page]! // guaranteed non-nil
84 | }
85 |
86 | /// Returns a `MutableProperty` containing the active path for the specified page, or `nil` if there is no active path.
87 | func activePath(page: NotepadPage) -> MutableProperty {
88 | return activePathLookup[page]! // guaranteed non-nil
89 | }
90 |
91 | /// Returns a `Property` containing `true` if the specified page has no paths.
92 | func isEmpty(page: NotepadPage) -> Property {
93 | return isEmptyLookup[page]! // guaranteed non-nil
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/TacBoard/Source/Notepad/UI/NotepadDrawingGestureRecognizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotepadDrawingGestureRecognizer.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/2/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 | import UIKit.UIGestureRecognizerSubclass
12 |
13 | /// Custom `UIGestureRecognizer` subclass for the notepad.
14 | class NotepadDrawingGestureRecognizer: UIGestureRecognizer {
15 |
16 | // MARK: UIGestureRecognizer Overrides
17 |
18 | /// A touch event began.
19 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
20 | state = .began
21 | }
22 |
23 | /// A touch event moved.
24 | override func touchesMoved(_ touches: Set, with event: UIEvent) {
25 | state = .changed
26 | }
27 |
28 | /// A touch event ended.
29 | override func touchesEnded(_ touches: Set, with event: UIEvent) {
30 | state = .ended
31 | }
32 |
33 | /// A touch event was cancelled.
34 | override func touchesCancelled(_ touches: Set, with event: UIEvent) {
35 | state = .cancelled
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/TacBoard/Source/Notepad/UI/NotepadDrawingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotepadDrawingView.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/2/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// `UIView` subclass for the `NotepadDrawingViewController` view controller.
13 | class NotepadDrawingView: UIView {
14 |
15 | // MARK: Properties
16 |
17 | /// The paths to display.
18 | var paths: [NotepadPath] = [] {
19 | didSet { setNeedsDisplay() }
20 | }
21 |
22 | /// Returns a `CGAffineTransform` converting view coordinates to normalized coordinates.
23 | var transformViewToNormalized: CGAffineTransform {
24 | return CGAffineTransform(scaleX: (1.0 / bounds.width), y: (1.0 / bounds.height))
25 | }
26 |
27 | /// Returns a `CGAffineTransform` converting normalized coordinates to view coordinates.
28 | var transformNormalizedToView: CGAffineTransform {
29 | return CGAffineTransform(scaleX: bounds.width, y: bounds.height)
30 | }
31 |
32 | // MARK: UIView Overrides
33 |
34 | /// Draws the view.
35 | override func draw(_ rect: CGRect) {
36 |
37 | // Get the drawing context
38 | guard let context = UIGraphicsGetCurrentContext() else { return }
39 |
40 | // Clear the drawing area
41 | context.setFillColor(UIColor.clear.cgColor)
42 | context.fill(bounds)
43 |
44 | // Make sure we have paths to draw
45 | guard !paths.isEmpty else { return }
46 |
47 | context.saveGState()
48 |
49 | // Common configuration
50 | context.setLineCap(.round)
51 | context.setLineJoin(.round)
52 |
53 | // Loop through each path we need to draw
54 | for path in paths {
55 |
56 | // Add the path, converting to view units
57 | context.saveGState()
58 | context.concatenate(transformNormalizedToView)
59 | context.addPath(path.path)
60 | context.restoreGState()
61 |
62 | // Draw the path
63 | context.saveGState()
64 | context.setStrokeColor(path.color.cgColor)
65 | context.setLineWidth(path.width)
66 | context.strokePath()
67 | context.restoreGState()
68 |
69 | }
70 |
71 | context.restoreGState()
72 |
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/TacBoard/Source/Reference/Model/ReferenceDocumentType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReferenceDocumentType.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/16/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of valid reference document types.
12 | enum ReferenceDocumentType: String, Codable {
13 |
14 | // MARK: Cases
15 |
16 | /// Document is an HTML document.
17 | case html
18 |
19 | // MARK: Properties
20 |
21 | /// The filename extension for this document type.
22 | var `extension`: String {
23 | switch self {
24 | case .html:
25 | return "html"
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/TacBoard/Source/Reference/UI/ReferenceBinderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReferenceBinderViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/16/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Concrete implementation of `BinderViewController` for reference documents.
13 | class ReferenceBinderViewController: BinderViewController {
14 |
15 | // MARK: BinderViewController Overrides
16 |
17 | /// Returns a `ReferenceFolderViewController` for the specified folder.
18 | override func folderViewController(coder: NSCoder, viewModel: ReferenceViewModel, folder: Folder) -> UIViewController? {
19 | return ReferenceFolderViewController(coder: coder, viewModel: viewModel, folder: folder)
20 | }
21 |
22 | /// Returns a `UITableViewCell` for the specified folder.
23 | override func tableView(_ tableView: UITableView, cellForFolder folder: Folder, at indexPath: IndexPath) -> UITableViewCell {
24 | let cell = tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! FolderTableViewCell
25 | cell.configure(for: folder)
26 | return cell
27 | }
28 |
29 | /// Returns a `String` explaining why there are no binders available.
30 | override func noItemsAvailableExplanation(viewModel: ReferenceViewModel) -> String {
31 | return LocalizableString(.referenceBinderNoMatches)
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/TacBoard/Source/Reference/UI/ReferenceDocumentTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReferenceDocumentTableViewCell.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/16/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Class for `UITableViewCell`s displaying reference documents.
13 | class ReferenceDocumentTableViewCell: SelectableHighlightableTableViewCell {
14 |
15 | // MARK: Outlets
16 |
17 | /// The label for the document title.
18 | @IBOutlet var titleLabel: UILabel?
19 |
20 | /// The label for the document subtitle.
21 | @IBOutlet var subtitleLabel: UILabel?
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/TacBoard/Source/Reference/UI/ReferenceFolderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReferenceFolderViewController.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/16/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Concrete implementation of `FolderViewController` for reference documents.
13 | class ReferenceFolderViewController: FolderViewController {
14 |
15 | // MARK: FolderViewController Overrides
16 |
17 | /// Returns the title for the folders section.
18 | override var folderSectionTitle: String {
19 | return LocalizableString(.referenceFoldersSectionTitle)
20 | }
21 |
22 | /// Returns the title for the items section.
23 | override var itemSectionTitle: String {
24 | return LocalizableString(.referenceDocumentsSectionTitle)
25 | }
26 |
27 | /// Returns a `ReferenceFolderViewController` for the specified folder.
28 | override func folderViewController(coder: NSCoder, viewModel: ReferenceViewModel, folder: Folder) -> UIViewController? {
29 | return ReferenceFolderViewController(coder: coder, viewModel: viewModel, folder: folder)
30 | }
31 |
32 | /// Returns a `UITableViewCell` for the specified folder.
33 | override func tableView(_ tableView: UITableView, cellForFolder folder: Folder, at indexPath: IndexPath) -> UITableViewCell {
34 | let cell = tableView.dequeueReusableCell(withIdentifier: "FolderCell", for: indexPath) as! FolderTableViewCell
35 | cell.configure(for: folder)
36 | return cell
37 | }
38 |
39 | /// Returns `UITableViewCell` for the specified reference document.
40 | override func tableView(_ tableView: UITableView, cellForItem item: ReferenceDocument, at indexPath: IndexPath) -> UITableViewCell {
41 |
42 | let cell = tableView.dequeueReusableCell(withIdentifier: "DocumentCell", for: indexPath) as! ReferenceDocumentTableViewCell
43 |
44 | cell.titleLabel?.text = item.title
45 | cell.subtitleLabel?.text = item.subtitle
46 | cell.subtitleLabel?.safeIsHidden = item.subtitle.isNilOrEmpty
47 | cell.accessoryType = (traitCollection.horizontalSizeClass == .compact ? .disclosureIndicator : .none)
48 |
49 | return cell
50 |
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/TacBoard/Source/Settings/Model/SplitDisplayMode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitDisplayMode.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/4/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Enumeration of split master view controller display modes.
13 | enum SplitDisplayMode: String, CaseIterable, Defaultable {
14 |
15 | // MARK: Cases
16 |
17 | /// The master view controller is always shown.
18 | case show
19 |
20 | /// The master view controller is hidden by default. The user can swipe from the side to display it.
21 | case hide
22 |
23 | // MARK: Constants
24 |
25 | /// The default split display mode.
26 | static let `default`: SplitDisplayMode = .show
27 |
28 | // MARK: Properties
29 |
30 | /// The `UISplitViewController.DisplayMode` enum corresponding to this value.
31 | var displayMode: UISplitViewController.DisplayMode {
32 | switch self {
33 | case .show:
34 | return .allVisible
35 | case .hide:
36 | return .automatic
37 | }
38 | }
39 |
40 | // MARK: Methods
41 |
42 | /// Toggles the value of this enum.
43 | mutating func toggle() {
44 | self = {
45 | switch self {
46 | case .show:
47 | return .hide
48 | case .hide:
49 | return .show
50 | }
51 | }()
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/TacBoard/Source/Settings/Model/UnitFormat.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UnitFormat.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/6/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Enumeration of supported unit formats.
12 | enum UnitFormat: String, CaseIterable, CustomStringConvertible, Defaultable {
13 |
14 | // MARK: Cases
15 |
16 | /// Imperial (i.e., crappy American) units.
17 | case imperial
18 |
19 | /// Metric (i.e., sane) units.
20 | case metric
21 |
22 | // MARK: Constants
23 |
24 | /// The default unit format.
25 | static let `default`: UnitFormat = .imperial // 🇺🇸
26 |
27 | // MARK: Properties
28 |
29 | /// Returns a `String` description of this enum.
30 | var description: String {
31 | switch self {
32 | case .imperial:
33 | return LocalizableString(.unitFormatImperial)
34 | case .metric:
35 | return LocalizableString(.unitFormatMetric)
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/TacBoard/Source/Utility/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// Main application delegate class.
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | // MARK: Fields
16 |
17 | private var isAppearanceSetupComplete = false
18 |
19 | // MARK: UIApplicationDelegate
20 |
21 | /// Override point for customization after application launch.
22 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
23 |
24 | NSLog("Started \(AppInfo.shared.name)...\n- Version: \(AppInfo.shared.version) Build \(AppInfo.shared.build)\n- Build Date: \(AppInfo.shared.date)\n- Git Commit: \(AppInfo.shared.commit)")
25 |
26 | // Initialize version manager
27 | _ = VersionManager.shared
28 |
29 | // Initialize global appearance
30 | overrideAppearance()
31 |
32 | return true
33 |
34 | }
35 |
36 | /// Called when a new scene session is being created.
37 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
38 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
39 | }
40 |
41 | /// Called when the user discards a scene session.
42 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
43 | // no-op
44 | }
45 |
46 | // MARK: Private Utility
47 |
48 | /// Overrides the the default system appearance with our customized settings.
49 | private func overrideAppearance() {
50 |
51 | guard !isAppearanceSetupComplete else { return }
52 | defer { isAppearanceSetupComplete = true }
53 |
54 | // Override system fonts
55 | UIFont.overrideSystemFonts()
56 |
57 | // Override additional UI components
58 | UILabel.appearance(whenContainedInInstancesOf: [UITableViewHeaderFooterView.self]).font = UIFont.systemFont(ofSize: Constants.verySmallTextSize)
59 | UINavigationBar.appearance().titleTextAttributes = [.font: UIFont.systemFont(ofSize: Constants.defaultTextSize)] // this is normally bold, but I like it better this way
60 | UITabBarItem.appearance().setTitleTextAttributes([.font: UIFont.systemFont(ofSize: Constants.verySmallTextSize)], for: .normal)
61 | UITabBarItem.appearance().setTitleTextAttributes([.font: UIFont.boldSystemFont(ofSize: Constants.verySmallTextSize)], for: .selected) // TODO: doesn't work???
62 |
63 | // Set tint color of UIAlertControllers
64 | UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = UIColor(application: .tint)
65 |
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/TacBoard/Source/Utility/AppInfo.h:
--------------------------------------------------------------------------------
1 | //
2 | // AppInfo.h
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/12/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /**
14 | Class containing information about the current build of the application.
15 | */
16 | @interface AppInfo : NSObject
17 |
18 | #pragma mark Initialization / Singleton
19 |
20 | /** The shared instance of this class. */
21 | @property (class, nonatomic, readonly) AppInfo *sharedInstance
22 | NS_SWIFT_NAME(shared);
23 |
24 | - (instancetype)init NS_UNAVAILABLE;
25 |
26 | #pragma mark Properties
27 |
28 | /** The display name of the application. */
29 | @property (nonatomic, readonly) NSString *name;
30 |
31 | /** The version string of the application. */
32 | @property (nonatomic, readonly) NSString *version;
33 |
34 | /** The major version number of the application. */
35 | @property (nonatomic, readonly) NSInteger versionMajor;
36 |
37 | /** The minor version number of the application. */
38 | @property (nonatomic, readonly) NSInteger versionMinor;
39 |
40 | /** The revision version number of the application. */
41 | @property (nonatomic, readonly) NSInteger versionRevision;
42 |
43 | /** The build number of the application. */
44 | @property (nonatomic, readonly) NSString *build;
45 |
46 | /** The build date of the application. */
47 | @property (nonatomic, readonly) NSDate *date;
48 |
49 | /** The Git commit hash of the application. */
50 | @property (nonatomic, readonly) NSString *commit;
51 |
52 | @end
53 |
54 | NS_ASSUME_NONNULL_END
55 |
--------------------------------------------------------------------------------
/TacBoard/Source/Utility/AppInfo.m:
--------------------------------------------------------------------------------
1 | //
2 | // AppInfo.m
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/12/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "AppInfo.h"
11 | #import "Version.h"
12 |
13 | @implementation AppInfo
14 |
15 | #pragma mark Initialization / Singleton
16 |
17 | + (AppInfo *)sharedInstance
18 | {
19 | static AppInfo *sharedInstance;
20 | static dispatch_once_t once;
21 | dispatch_once(&once, ^{
22 | sharedInstance = [[AppInfo alloc] initPrivate];
23 | });
24 | return sharedInstance;
25 | }
26 |
27 | - (instancetype)initPrivate
28 | {
29 | return [super init];
30 | }
31 |
32 | #pragma mark Properties
33 |
34 | - (NSString *)name
35 | {
36 | return [self stringForKey:@"CFBundleName"];
37 | }
38 |
39 | - (NSString *)version
40 | {
41 | return [self stringForKey:@"CFBundleShortVersionString"];
42 | }
43 |
44 | - (NSInteger)versionMajor
45 | {
46 | NSInteger ret;
47 | return ([self getVersionMajor:&ret versionMinor:nil versionRevision:nil] ? ret : -1);
48 | }
49 |
50 | - (NSInteger)versionMinor
51 | {
52 | NSInteger ret;
53 | return ([self getVersionMajor:nil versionMinor:&ret versionRevision:nil] ? ret : -1);
54 | }
55 |
56 | - (NSInteger)versionRevision
57 | {
58 | NSInteger ret;
59 | return ([self getVersionMajor:nil versionMinor:nil versionRevision:&ret] ? ret : -1);
60 | }
61 |
62 | - (NSString *)build
63 | {
64 | return [self stringForKey:@"CFBundleVersion"];
65 | }
66 |
67 | - (NSDate *)date
68 | {
69 | return [NSDate dateWithTimeIntervalSince1970:BUILD_DATE];
70 | }
71 |
72 | - (NSString *)commit
73 | {
74 | return GIT_COMMIT;
75 | }
76 |
77 | #pragma mark Private Utility
78 |
79 | - (NSString *)stringForKey:(NSString *)key
80 | {
81 | id value = NSBundle.mainBundle.infoDictionary[key];
82 | return ([value isKindOfClass:NSString.class] ? (NSString *)value : NSString.string);
83 | }
84 |
85 | - (BOOL)getVersionMajor:(NSInteger *)outVersionMajor
86 | versionMinor:(NSInteger *)outVersionMinor
87 | versionRevision:(NSInteger *)outVersionRevision
88 | {
89 | static BOOL success = NO;
90 | static NSInteger versionMajor = 0;
91 | static NSInteger versionMinor = 0;
92 | static NSInteger versionRevision = 0;
93 |
94 | static dispatch_once_t once;
95 | dispatch_once(&once, ^{
96 |
97 | NSRegularExpression *regex = [[NSRegularExpression alloc] initWithPattern:@"([0-9]+)\\.([0-9]+)\\.([0-9]+)" options:0 error:nil];
98 | NSString *version = self.version;
99 | NSArray *matches = [regex matchesInString:version options:0 range:NSMakeRange(0, version.length)];
100 | if (matches.count == 0 || matches.firstObject.numberOfRanges != 4)
101 | {
102 | return;
103 | }
104 |
105 | versionMajor = [version substringWithRange:[matches.firstObject rangeAtIndex:1]].integerValue;
106 | versionMinor = [version substringWithRange:[matches.firstObject rangeAtIndex:2]].integerValue;
107 | versionRevision = [version substringWithRange:[matches.firstObject rangeAtIndex:3]].integerValue;
108 | success = YES;
109 |
110 | });
111 |
112 | if (outVersionMajor) { *outVersionMajor = versionMajor; }
113 | if (outVersionMinor) { *outVersionMinor = versionMinor; }
114 | if (outVersionRevision) { *outVersionRevision = versionRevision; }
115 | return success;
116 | }
117 |
118 | @end
119 |
--------------------------------------------------------------------------------
/TacBoard/Source/Utility/Assets.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Assets.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | // MARK: - ApplicationColor
13 |
14 | /// Enumeration of colors defined in the application assets.
15 | enum ApplicationColor: String {
16 |
17 | // MARK: Cases
18 |
19 | /// The default background color.
20 | case background = "BackgroundColor"
21 |
22 | /// The background color for highlighted cells.
23 | case highlightedCellBackground = "HighlightedCellBackgroundColor"
24 |
25 | /// The placeholder text color.
26 | case placeholderText = "PlaceholderTextColor"
27 |
28 | /// The inverted placeholder text color.
29 | case placeholderTextInverted = "PlaceholderTextInvertedColor"
30 |
31 | /// The popover border color.
32 | case popoverBorder = "PopoverBorderColor"
33 |
34 | /// The primary text color.
35 | case primaryText = "PrimaryTextColor"
36 |
37 | /// The inverted primary text color.
38 | case primaryTextInverted = "PrimaryTextInvertedColor"
39 |
40 | /// The background color for the notepad.
41 | case notepadBackground = "NotepadBackgroundColor"
42 |
43 | /// The black/white notepad color.
44 | case notepadBlackWhite = "NotepadBlackWhiteColor"
45 |
46 | /// The blue notepad color.
47 | case notepadBlue = "NotepadBlueColor"
48 |
49 | /// The foreground color for the notepad.
50 | case notepadForeground = "NotepadForegroundColor"
51 |
52 | /// The green notepad color.
53 | case notepadGreen = "NotepadGreenColor"
54 |
55 | /// The indigo notepad color.
56 | case notepadIndigo = "NotepadIndigoColor"
57 |
58 | /// The orange notepad color.
59 | case notepadOrange = "NotepadOrangeColor"
60 |
61 | /// The red notepad color.
62 | case notepadRed = "NotepadRedColor"
63 |
64 | /// The violet notepad color.
65 | case notepadViolet = "NotepadVioletColor"
66 |
67 | /// The yellow notepad color.
68 | case notepadYellow = "NotepadYellowColor"
69 |
70 | /// The secondary background color.
71 | case secondaryBackground = "SecondaryBackgroundColor"
72 |
73 | /// The secondary text color.
74 | case secondaryText = "SecondaryTextColor"
75 |
76 | /// The inverted secondary text color.
77 | case secondaryTextInverted = "SecondaryTextInvertedColor"
78 |
79 | /// The background color for selected cells.
80 | case selectedCellBackground = "SelectedCellBackgroundColor"
81 |
82 | /// The application tint color.
83 | case tint = "TintColor"
84 |
85 | /// The inverted application tint color.
86 | case tintInverted = "TintInvertedColor"
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/TacBoard/Source/Utility/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import UIKit
11 |
12 | /// Struct for namespacing constants.
13 | struct Constants {
14 |
15 | // MARK: Initialization
16 |
17 | /// Struct is not instantiable.
18 | private init() { }
19 |
20 | // MARK: Constants
21 |
22 | /// The default duration for animations.
23 | static let defaultAnimationDuration: TimeInterval = (1.0 / 3.0)
24 |
25 | /// The default brightness to use for media in dark mode.
26 | static let defaultDarkModeBrightness: CGFloat = 0.9
27 |
28 | /// The default row height.
29 | static let defaultRowHeight: CGFloat = 60.0
30 |
31 | /// The URL for the document directory.
32 | static let documentDirectoryURL: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!
33 |
34 | /// A zero-length duration to disable animations.
35 | static let noAnimationDuration: TimeInterval = 0.0
36 |
37 | /// The border width to use for popovers.
38 | static let popoverBorderWidth: CGFloat = 2.0
39 |
40 | /// The corner radius to use for popovers.
41 | static let popoverCornerRadius: CGFloat = 13.0
42 |
43 | /// The default text size.
44 | static let defaultTextSize: CGFloat = 17.0
45 |
46 | /// The large text size.
47 | static let largeTextSize: CGFloat = 21.0
48 |
49 | /// The small text size.
50 | static let smallTextSize: CGFloat = 14.0
51 |
52 | /// The very small text size.
53 | static let verySmallTextSize: CGFloat = 12.0
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/TacBoard/Source/Utility/ErrorHandling.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ErrorHandling.swift
3 | // TacBoard
4 | //
5 | // Created by Vig, Christopher on 8/1/20.
6 | // Copyright © 2020 Christopher Vig. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: - Functions
12 |
13 | /// Fails the application with a `fatalError()`.
14 | /// - note: This is intended to be used when an invalid index path is specified.
15 | func fatalInvalidIndexPath() -> Never {
16 | fatalError("Invalid index path!")
17 | }
18 |
19 | /// Fails the application with a `fatalError()`.
20 | /// - note: This is intended to be called when a required resource is missing or invalid.
21 | func fatalInvalidResource() -> Never {
22 | fatalError("Invalid resource!")
23 | }
24 |
25 | /// Fails the application with a `fatalError()`.
26 | /// - note: This is intended to be called when a segue is invalid.
27 | func fatalInvalidSegue() -> Never {
28 | fatalError("Invalid segue!")
29 | }
30 |
31 | /// Fails the application with a `fatalError()`.
32 | /// - note: This is intended to be called when a non-available function is called.
33 | func fatalNotAvailable() -> Never {
34 | fatalError("Not available!")
35 | }
36 |
37 | /// Fails the application with a `fatalError()`.
38 | /// - note: This is intended to be called when a subclass must implement a function.
39 | func fatalSubclassMustImplement() -> Never {
40 | fatalError("Subclass must implement!")
41 | }
42 |
--------------------------------------------------------------------------------
/TacBoard/Source/Utility/SwiftBridgingHeader.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "AppInfo.h"
6 |
--------------------------------------------------------------------------------
/TacBoard/TacBoard.xcodeproj/xcshareddata/xcschemes/TacBoard - Debug.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/TacBoard/TacBoard.xcodeproj/xcshareddata/xcschemes/TacBoard - Release.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------