├── .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 | ![Platform: iOS](https://img.shields.io/badge/platform-ios-lightgrey) [![License: GPL v3](https://img.shields.io/badge/license-GPLv3-green.svg)](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 | --------------------------------------------------------------------------------