├── icon.png ├── icon.psd ├── README.md ├── flipflap ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ ├── icon.png │ │ ├── icon-40.png │ │ ├── icon-72.png │ │ ├── icon-76.png │ │ ├── icon@2x.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72@2x.png │ │ ├── icon-76@2x.png │ │ ├── icon-small.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-small-50.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── ios-marketing.png │ │ ├── icon-small-50@2x.png │ │ ├── notification-icon@2x.png │ │ ├── notification-icon@3x.png │ │ ├── notification-icon~ipad.png │ │ ├── notification-icon~ipad@2x.png │ │ └── Contents.json ├── flipflap.xcdatamodeld │ ├── .xccurrentversion │ └── flipflap.xcdatamodel │ │ └── contents ├── UIViewController.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── AppDelegate.swift ├── SceneDelegate.swift ├── FontViewController.swift ├── ViewController.swift └── SettingsViewController.swift └── flipflap.xcodeproj ├── project.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── xcuserdata └── twodayslate.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── project.pbxproj /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/icon.png -------------------------------------------------------------------------------- /icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/icon.psd -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Split Flap 2 | 3 | Special thanks to https://github.com/yannickl/Splitflap -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-72.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-small.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-small-50.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-small@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-small@3x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/ios-marketing.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/icon-small-50@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon@2x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon@3x.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad.png -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/Split-Flap/master/flipflap/Assets.xcassets/AppIcon.appiconset/notification-icon~ipad@2x.png -------------------------------------------------------------------------------- /flipflap.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flipflap/flipflap.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | flipflap.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /flipflap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /flipflap/flipflap.xcdatamodeld/flipflap.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /flipflap.xcodeproj/xcuserdata/twodayslate.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | flipflap.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /flipflap/UIViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | extension UIViewController { 4 | /** 5 | - seealso: https://stackoverflow.com/a/54932223/193772 6 | */ 7 | public func addActionSheetForiPad(actionSheet: UIViewController, sourceView _view: UIView? = nil, sourceRect _rect: CGRect? = nil, permittedArrowDirections _arrowDirections: UIPopoverArrowDirection? = nil) { 8 | let useView = (_view ?? self.view) as UIView 9 | let useRect = (_rect ?? CGRect(x: useView.bounds.midX, y: useView.bounds.midY, width: 0, height: 0)) as CGRect 10 | let useArrows = (_arrowDirections ?? []) as UIPopoverArrowDirection 11 | 12 | actionSheet.popoverPresentationController?.sourceView = useView 13 | actionSheet.popoverPresentationController?.sourceRect = useRect 14 | actionSheet.popoverPresentationController?.permittedArrowDirections = useArrows 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /flipflap/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /flipflap/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | Split Flap 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | LaunchScreen 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIRequiredDeviceCapabilities 45 | 46 | armv7 47 | 48 | UIStatusBarHidden 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | UIInterfaceOrientationPortraitUpsideDown 56 | 57 | UISupportedInterfaceOrientations~ipad 58 | 59 | UIInterfaceOrientationPortrait 60 | UIInterfaceOrientationPortraitUpsideDown 61 | UIInterfaceOrientationLandscapeLeft 62 | UIInterfaceOrientationLandscapeRight 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /flipflap/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-40.png", 5 | "scale" : "1x", 6 | "idiom" : "ipad", 7 | "size" : "40x40" 8 | }, 9 | { 10 | "size" : "40x40", 11 | "scale" : "2x", 12 | "idiom" : "ipad", 13 | "filename" : "icon-40@2x.png" 14 | }, 15 | { 16 | "size" : "60x60", 17 | "filename" : "icon-60@2x.png", 18 | "idiom" : "iphone", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "idiom" : "ipad", 23 | "size" : "72x72", 24 | "scale" : "1x", 25 | "filename" : "icon-72.png" 26 | }, 27 | { 28 | "size" : "72x72", 29 | "idiom" : "ipad", 30 | "scale" : "2x", 31 | "filename" : "icon-72@2x.png" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "scale" : "1x", 36 | "size" : "76x76", 37 | "filename" : "icon-76.png" 38 | }, 39 | { 40 | "filename" : "icon-76@2x.png", 41 | "idiom" : "ipad", 42 | "size" : "76x76", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "idiom" : "ipad", 47 | "filename" : "icon-small-50.png", 48 | "scale" : "1x", 49 | "size" : "50x50" 50 | }, 51 | { 52 | "scale" : "2x", 53 | "idiom" : "ipad", 54 | "filename" : "icon-small-50@2x.png", 55 | "size" : "50x50" 56 | }, 57 | { 58 | "idiom" : "iphone", 59 | "filename" : "icon-small.png", 60 | "scale" : "1x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "filename" : "icon-small@2x.png", 66 | "idiom" : "iphone", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "scale" : "1x", 71 | "size" : "57x57", 72 | "idiom" : "iphone", 73 | "filename" : "icon.png" 74 | }, 75 | { 76 | "idiom" : "iphone", 77 | "filename" : "icon@2x.png", 78 | "scale" : "2x", 79 | "size" : "57x57" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "filename" : "icon-small@3x.png", 84 | "scale" : "3x", 85 | "idiom" : "iphone" 86 | }, 87 | { 88 | "scale" : "3x", 89 | "idiom" : "iphone", 90 | "filename" : "icon-40@3x.png", 91 | "size" : "40x40" 92 | }, 93 | { 94 | "scale" : "3x", 95 | "filename" : "icon-60@3x.png", 96 | "idiom" : "iphone", 97 | "size" : "60x60" 98 | }, 99 | { 100 | "idiom" : "iphone", 101 | "filename" : "icon-40@2x.png", 102 | "size" : "40x40", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "scale" : "1x", 107 | "filename" : "icon-small.png", 108 | "size" : "29x29", 109 | "idiom" : "ipad" 110 | }, 111 | { 112 | "filename" : "icon-small@2x.png", 113 | "idiom" : "ipad", 114 | "size" : "29x29", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "83.5x83.5", 119 | "filename" : "icon-83.5@2x.png", 120 | "idiom" : "ipad", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "size" : "20x20", 125 | "filename" : "notification-icon@2x.png", 126 | "idiom" : "iphone", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "scale" : "3x", 131 | "size" : "20x20", 132 | "filename" : "notification-icon@3x.png", 133 | "idiom" : "iphone" 134 | }, 135 | { 136 | "scale" : "1x", 137 | "size" : "20x20", 138 | "idiom" : "ipad", 139 | "filename" : "notification-icon~ipad.png" 140 | }, 141 | { 142 | "size" : "20x20", 143 | "idiom" : "ipad", 144 | "filename" : "notification-icon~ipad@2x.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "1024x1024", 149 | "scale" : "1x", 150 | "idiom" : "ios-marketing", 151 | "filename" : "ios-marketing.png" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } -------------------------------------------------------------------------------- /flipflap/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // flipflap 4 | // 5 | // Created by Zachary Gorak on 2/7/20. 6 | // Copyright © 2020 twodayslate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | // MARK: UISceneSession Lifecycle 23 | 24 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 25 | // Called when a new scene session is being created. 26 | // Use this method to select a configuration to create the new scene with. 27 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 28 | } 29 | 30 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 31 | // Called when the user discards a scene session. 32 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 33 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 34 | } 35 | 36 | // MARK: - Core Data stack 37 | 38 | lazy var persistentContainer: NSPersistentCloudKitContainer = { 39 | /* 40 | The persistent container for the application. This implementation 41 | creates and returns a container, having loaded the store for the 42 | application to it. This property is optional since there are legitimate 43 | error conditions that could cause the creation of the store to fail. 44 | */ 45 | let container = NSPersistentCloudKitContainer(name: "flipflap") 46 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 47 | if let error = error as NSError? { 48 | // Replace this implementation with code to handle the error appropriately. 49 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 50 | 51 | /* 52 | Typical reasons for an error here include: 53 | * The parent directory does not exist, cannot be created, or disallows writing. 54 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 55 | * The device is out of space. 56 | * The store could not be migrated to the current model version. 57 | Check the error message to determine what the actual problem was. 58 | */ 59 | fatalError("Unresolved error \(error), \(error.userInfo)") 60 | } 61 | }) 62 | return container 63 | }() 64 | 65 | // MARK: - Core Data Saving support 66 | 67 | func saveContext () { 68 | let context = persistentContainer.viewContext 69 | if context.hasChanges { 70 | do { 71 | try context.save() 72 | } catch { 73 | // Replace this implementation with code to handle the error appropriately. 74 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 75 | let nserror = error as NSError 76 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 77 | } 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /flipflap/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // flipflap 4 | // 5 | // Created by Zachary Gorak on 2/7/20. 6 | // Copyright © 2020 twodayslate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { 21 | print("returning") 22 | return 23 | } 24 | 25 | if let windowScene = scene as? UIWindowScene { 26 | print("setting controller") 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = ViewController() 29 | self.window = window 30 | 31 | self.window?.backgroundColor = UIColor.black 32 | 33 | if let data = UserDefaults.standard.object(forKey: "tint") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 34 | 35 | self.window?.tintColor = color 36 | } 37 | 38 | UserDefaults.standard.addObserver(self, forKeyPath: "theme", options: .new, context: nil) 39 | self.setTheme() 40 | 41 | window.makeKeyAndVisible() 42 | } 43 | 44 | 45 | 46 | // don't fall asleep 47 | UIApplication.shared.isIdleTimerDisabled = true 48 | } 49 | 50 | deinit { 51 | UserDefaults.standard.removeObserver(self, forKeyPath: "theme") 52 | } 53 | 54 | func setTheme() { 55 | DispatchQueue.main.async { 56 | switch UserDefaults.standard.integer(forKey: "theme") { 57 | case 1: 58 | self.window?.overrideUserInterfaceStyle = .light 59 | case 2: 60 | self.window?.overrideUserInterfaceStyle = .dark 61 | default: 62 | self.window?.overrideUserInterfaceStyle = .unspecified 63 | } 64 | } 65 | } 66 | 67 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 68 | if keyPath == "theme" { 69 | self.setTheme() 70 | } 71 | } 72 | 73 | func sceneDidDisconnect(_ scene: UIScene) { 74 | // Called as the scene is being released by the system. 75 | // This occurs shortly after the scene enters the background, or when its session is discarded. 76 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 77 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 78 | } 79 | 80 | func sceneDidBecomeActive(_ scene: UIScene) { 81 | // Called when the scene has moved from an inactive state to an active state. 82 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 83 | } 84 | 85 | func sceneWillResignActive(_ scene: UIScene) { 86 | // Called when the scene will move from an active state to an inactive state. 87 | // This may occur due to temporary interruptions (ex. an incoming phone call). 88 | } 89 | 90 | func sceneWillEnterForeground(_ scene: UIScene) { 91 | // Called as the scene transitions from the background to the foreground. 92 | // Use this method to undo the changes made on entering the background. 93 | } 94 | 95 | func sceneDidEnterBackground(_ scene: UIScene) { 96 | // Called as the scene transitions from the foreground to the background. 97 | // Use this method to save data, release shared resources, and store enough scene-specific state information 98 | // to restore the scene back to its current state. 99 | 100 | // Save changes in the application's managed object context when the application transitions to the background. 101 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 102 | } 103 | 104 | 105 | } 106 | 107 | -------------------------------------------------------------------------------- /flipflap/FontViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontViewController.swift 3 | // flipflap 4 | // 5 | // Created by Zachary Gorak on 2/12/20. 6 | // Copyright © 2020 twodayslate. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol FontControllerDelegate { 13 | var currentlySelectedFont: String? { get } 14 | func didChangeFont(name: String) 15 | } 16 | 17 | class FontNavigationController: UINavigationController, FontControllerDelegate { 18 | var fontDelegate: FontControllerDelegate? = nil 19 | func didChangeFont(name: String) { 20 | self.fontDelegate?.didChangeFont(name: name) 21 | } 22 | 23 | var currentlySelectedFont: String? = nil { 24 | didSet { 25 | self.settings.currentlySelectedFont = self.currentlySelectedFont 26 | self.settings.tableView.reloadData() 27 | } 28 | } 29 | 30 | convenience init() { 31 | self.init(nibName: nil, bundle: nil) 32 | } 33 | 34 | let settings = FontTableViewController(style: .plain) 35 | 36 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 37 | super.init(nibName: nil, bundle: nil) 38 | 39 | self.settings.fontDelegate = self 40 | self.viewControllers = [settings] 41 | 42 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(close(_:))) 43 | } 44 | 45 | override func viewDidLoad() { 46 | super.viewDidLoad() 47 | self.view.backgroundColor = .systemBackground 48 | } 49 | 50 | @objc func close(_ sender: Any?) { 51 | self.dismiss(animated: true, completion: {}) 52 | } 53 | 54 | required init?(coder aDecoder: NSCoder) { 55 | fatalError("init(coder:) has not been implemented") 56 | } 57 | } 58 | 59 | class FontTableViewController: UITableViewController, FontControllerDelegate { 60 | var fontDelegate: FontControllerDelegate? = nil 61 | func didChangeFont(name: String) { 62 | self.fontDelegate?.didChangeFont(name: name) 63 | } 64 | 65 | var currentlySelectedFont: String? { 66 | didSet { 67 | self.tableView.reloadData() 68 | if let name = self.currentlySelectedFont { 69 | self.didChangeFont(name: name) 70 | } 71 | 72 | } 73 | } 74 | 75 | let familyNames = UIFont.familyNames.sorted() 76 | 77 | override func viewDidLoad() { 78 | super.viewDidLoad() 79 | self.title = "Settings" 80 | self.view.backgroundColor = .systemBackground 81 | self.tableView.backgroundColor = .systemGroupedBackground 82 | 83 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self.navigationController, action: #selector(FontNavigationController.close(_:))) 84 | 85 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Reset", style: .done, target: self, action: #selector(reset(_:))) 86 | } 87 | 88 | @objc func reset(_ sender: Any?) { 89 | let font = UIFont(name: "Courier", size: 45.0) 90 | guard let data = try? NSKeyedArchiver.archivedData(withRootObject: font, requiringSecureCoding: false) else { 91 | return 92 | } 93 | UserDefaults.standard.set(data, forKey: "font") 94 | 95 | self.currentlySelectedFont = font?.familyName 96 | } 97 | 98 | override func numberOfSections(in tableView: UITableView) -> Int { 99 | return 1 100 | } 101 | 102 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 103 | return self.familyNames.count 104 | } 105 | 106 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 107 | let cell = UITableViewCell(style: .default, reuseIdentifier: "font") 108 | let fontName = self.familyNames[indexPath.row] 109 | cell.textLabel?.text = fontName 110 | cell.textLabel?.font = UIFont(name: fontName, size: cell.textLabel?.font.pointSize ?? 14.0) 111 | 112 | if self.currentlySelectedFont == cell.textLabel?.font.familyName { 113 | cell.accessoryType = .checkmark 114 | } else { 115 | cell.accessoryType = .none 116 | } 117 | return cell 118 | } 119 | 120 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 121 | let fontName = self.familyNames[indexPath.row] 122 | 123 | let font = UIFont(name: fontName, size: 45.0) 124 | guard let data = try? NSKeyedArchiver.archivedData(withRootObject: font, requiringSecureCoding: false) else { 125 | return 126 | } 127 | UserDefaults.standard.set(data, forKey: "font") 128 | 129 | tableView.deselectRow(at: indexPath, animated: true) 130 | 131 | self.currentlySelectedFont = font?.familyName 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /flipflap/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // flipflap 4 | // 5 | // Created by Zachary Gorak on 2/7/20. 6 | // Copyright © 2020 twodayslate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Splitflap 11 | 12 | extension Date { 13 | func convertToLocaleDate(template: String) -> String { 14 | let dateFormatter = DateFormatter() 15 | 16 | let calender = Calendar.current 17 | 18 | dateFormatter.timeZone = calender.timeZone 19 | dateFormatter.locale = calender.locale 20 | dateFormatter.setLocalizedDateFormatFromTemplate(template) 21 | 22 | return dateFormatter.string(from: self) 23 | } 24 | } 25 | 26 | extension Calendar { 27 | var is24Hour: Bool { 28 | guard let locale = self.locale else { 29 | return false 30 | } 31 | guard let c = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: locale) else { 32 | return false 33 | } 34 | // "h a" when not 24 hours 35 | return !c.contains("a") 36 | } 37 | } 38 | 39 | class ViewController: UIViewController, SplitflapDataSource, SplitflapDelegate { 40 | 41 | var flaps = Splitflap() 42 | private var previousFlapText: String? 43 | var flapText = "Hello" { 44 | willSet { 45 | self.previousFlapText = self.flapText 46 | } 47 | didSet { 48 | guard let previousFlapText = self.previousFlapText else { 49 | return 50 | } 51 | if previousFlapText.count != self.flapText.count { 52 | self.flaps.reload() 53 | } 54 | } 55 | } 56 | let settingsButton = UIButton(type: .system) 57 | var timer = Timer() 58 | var shouldSetTime = true 59 | 60 | override func viewDidLoad() { 61 | super.viewDidLoad() 62 | // Do any additional setup after loading the view. 63 | 64 | self.view.backgroundColor = .systemBackground 65 | if let data = UserDefaults.standard.object(forKey: "background_color") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 66 | 67 | self.view.backgroundColor = color 68 | } 69 | 70 | self.flaps.delegate = self 71 | self.flaps.datasource = self 72 | self.flaps.translatesAutoresizingMaskIntoConstraints = false 73 | 74 | let innerView = UIView() 75 | innerView.translatesAutoresizingMaskIntoConstraints = false 76 | self.view.addSubview(innerView) 77 | 78 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": innerView])) 79 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": innerView])) 80 | 81 | innerView.addSubview(self.flaps) 82 | 83 | self.flaps.centerXAnchor.constraint(equalTo: innerView.centerXAnchor).isActive = true 84 | self.flaps.centerYAnchor.constraint(equalTo: innerView.centerYAnchor).isActive = true 85 | 86 | self.flaps.widthAnchor.constraint(equalTo: innerView.widthAnchor).isActive = true 87 | //self.flaps.heightAnchor.constraint(lessThanOrEqualTo: innerView.heightAnchor).isActive = true 88 | self.flaps.heightAnchor.constraint(equalTo: self.flaps.widthAnchor, multiplier: 0.5).isActive = true 89 | self.flaps.heightAnchor.constraint(lessThanOrEqualTo: innerView.heightAnchor, multiplier: 0.5).isActive = true 90 | 91 | 92 | settingsButton.setImage(UIImage.init(systemName: "gear"), for: .normal) 93 | settingsButton.alpha = 0.0 94 | settingsButton.tintColor = .systemFill 95 | settingsButton.translatesAutoresizingMaskIntoConstraints = false 96 | settingsButton.isUserInteractionEnabled = true 97 | settingsButton.isEnabled = true 98 | 99 | innerView.addSubview(settingsButton) 100 | settingsButton.bottomAnchor.constraint(equalTo: innerView.bottomAnchor, constant: -16.0).isActive = true 101 | settingsButton.trailingAnchor.constraint(equalTo: innerView.trailingAnchor, constant: -16.0).isActive = true 102 | //settings.heightAnchor.constraint(equalToConstant: 24).isActive = true 103 | settingsButton.widthAnchor.constraint(equalTo: settingsButton.heightAnchor).isActive = true 104 | settingsButton.addTarget(self, action: #selector(openSettings(_:)), for: .touchUpInside) 105 | 106 | let tapScreenGesture = UITapGestureRecognizer(target: self, action: #selector(tapScreen(_:))) 107 | tapScreenGesture.numberOfTapsRequired = 1 108 | self.view.addGestureRecognizer(tapScreenGesture) 109 | 110 | self.flapText = self.currentTime() 111 | self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.updateTimer(timer:)), userInfo: nil, repeats: true) 112 | RunLoop.main.add(self.timer, forMode: .common) 113 | 114 | UserDefaults.standard.addObserver(self, forKeyPath: "background_color", options: .new, context: nil) 115 | UserDefaults.standard.addObserver(self, forKeyPath: "flap_color", options: .new, context: nil) 116 | UserDefaults.standard.addObserver(self, forKeyPath: "text_color", options: .new, context: nil) 117 | UserDefaults.standard.addObserver(self, forKeyPath: "font", options: .new, context: nil) 118 | } 119 | 120 | deinit { 121 | UserDefaults.standard.removeObserver(self, forKeyPath: "background_color") 122 | UserDefaults.standard.removeObserver(self, forKeyPath: "flap_color") 123 | UserDefaults.standard.removeObserver(self, forKeyPath: "text_color") 124 | UserDefaults.standard.removeObserver(self, forKeyPath: "font") 125 | } 126 | 127 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { 128 | guard let key = keyPath else { 129 | return 130 | } 131 | self.didChangeKey(key) 132 | } 133 | 134 | @objc func tapScreen(_ sender: UITapGestureRecognizer) { 135 | print("did tap") 136 | self.showSettingsIcon() 137 | } 138 | 139 | @objc func showSettingsIcon() { 140 | print("showing icons") 141 | self.settingsButton.isEnabled = true 142 | self.settingsButton.isUserInteractionEnabled = true 143 | UIView.animate(withDuration: 0.5, delay: 0.0, options: [.allowUserInteraction, .beginFromCurrentState, .curveEaseInOut], animations: { 144 | self.settingsButton.alpha = 1.0 145 | }, completion: { didComplete in 146 | self.hideSettingsIcon(didComplete, delay: 5.0) 147 | }) 148 | } 149 | 150 | @objc func openSettings(_ sender: UIButton) { 151 | let settings = SettingsNavigationController() 152 | settings.settingsDelegate = self 153 | self.present(settings, animated: true, completion: nil) 154 | } 155 | 156 | func currentTime() -> String { 157 | if Calendar.current.is24Hour { 158 | if UserDefaults.standard.bool(forKey: "showSeconds") { 159 | return Date().convertToLocaleDate(template: "HH:mm:ss") 160 | } else { 161 | return Date().convertToLocaleDate(template: "HH:mm") 162 | } 163 | } else { 164 | if UserDefaults.standard.bool(forKey: "showSeconds") { 165 | return Date().convertToLocaleDate(template: "h:mm:ss a") 166 | } else { 167 | return Date().convertToLocaleDate(template: "h:mm a") 168 | } 169 | } 170 | } 171 | 172 | @objc 173 | func updateTimer(timer: Timer) { 174 | if shouldSetTime { 175 | DispatchQueue.main.async { 176 | 177 | self.flapText = self.currentTime() 178 | self.flaps.setText(self.flapText, animated: true) 179 | } 180 | } 181 | } 182 | 183 | func numberOfFlapsInSplitflap(_ splitflap: Splitflap) -> Int { 184 | return self.flapText.count 185 | } 186 | 187 | func tokensInSplitflap(_ splitflap: Splitflap, flap: Int) -> [String] { 188 | // so this is the order in which things will actually flip 189 | if shouldSetTime { 190 | if Calendar.current.is24Hour { 191 | return SplitflapTokens.Numeric + [":"] 192 | } else { 193 | let map = SplitflapTokens.Numeric + " AMP:".map { String($0) } 194 | return map 195 | } 196 | } 197 | let map = SplitflapTokens.AlphanumericAndSpace + [":"] 198 | return map 199 | } 200 | 201 | func splitflap(_ splitflap: Splitflap, builderForFlapAtIndex index: Int) -> FlapViewBuilder { 202 | var background: UIColor = .secondarySystemBackground 203 | if let data = UserDefaults.standard.object(forKey: "flap_color") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 204 | 205 | background = color 206 | } 207 | var textColor: UIColor = .label 208 | if let data = UserDefaults.standard.object(forKey: "text_color") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 209 | 210 | textColor = color 211 | } 212 | 213 | let width = splitflap.bounds.width / CGFloat(self.numberOfFlapsInSplitflap(splitflap)) 214 | 215 | var font = UIFont(name: "Courier", size: width) 216 | 217 | if let data = UserDefaults.standard.object(forKey: "font") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [UIFont.self], from: data) as? UIFont { 218 | 219 | font = UIFont(name: color.familyName, size: width) 220 | } 221 | 222 | return FlapViewBuilder { builder in 223 | builder.backgroundColor = background 224 | builder.textColor = textColor 225 | builder.lineColor = .opaqueSeparator 226 | builder.adjustsFontSizeToFitWidth = true 227 | builder.font = font 228 | } 229 | } 230 | 231 | func hideSettingsIcon(_ didComplete: Bool = true, delay: TimeInterval = 0.0) { 232 | print("did complete?", didComplete) 233 | if didComplete { 234 | UIView.animate(withDuration: 2.0, delay: delay, options: [.allowUserInteraction, .beginFromCurrentState], animations: { 235 | self.settingsButton.alpha = 0.011 236 | }, completion: { didComplete2 in 237 | print("did complete2:", didComplete2) 238 | if didComplete2 { 239 | self.settingsButton.isUserInteractionEnabled = false 240 | self.settingsButton.isEnabled = true 241 | } 242 | }) 243 | } 244 | } 245 | 246 | override func viewDidAppear(_ animated: Bool) { 247 | super.viewDidAppear(animated) 248 | self.flaps.reload() // XXX remove once width fix is implemented 249 | UIView.animate(withDuration: 1.0, delay: 0.0, options: [.allowUserInteraction, .beginFromCurrentState], animations: { 250 | self.settingsButton.alpha = 1.0 251 | }, completion: { didComplete in 252 | self.hideSettingsIcon(didComplete, delay: 5.0) 253 | }) 254 | } 255 | 256 | override var prefersStatusBarHidden: Bool { 257 | return true 258 | } 259 | } 260 | 261 | extension ViewController: SettingsControllerDelegate { 262 | func didChangeKey(_ key: String) { 263 | if key == "background_color" { 264 | if let data = UserDefaults.standard.object(forKey: key) as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 265 | self.view.backgroundColor = color 266 | } else { 267 | self.view.backgroundColor = .systemBackground 268 | } 269 | } 270 | 271 | if key == "flap_color" || key == "text_color" || key == "font" { 272 | self.flaps.reload() 273 | } 274 | } 275 | 276 | func didCloseSettings() { 277 | self.showSettingsIcon() 278 | } 279 | 280 | func willCloseSettings() { 281 | self.showSettingsIcon() 282 | self.flaps.reload() 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /flipflap.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F6B15A4D23F478AC001DB252 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B15A4C23F478AC001DB252 /* UIViewController.swift */; }; 11 | F6B15A5023F48797001DB252 /* EFColorPicker in Frameworks */ = {isa = PBXBuildFile; productRef = F6B15A4F23F48797001DB252 /* EFColorPicker */; }; 12 | F6B15A5223F4CD3D001DB252 /* FontViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6B15A5123F4CD3D001DB252 /* FontViewController.swift */; }; 13 | F6DAA11C2944F13F001F4C56 /* Splitflap in Frameworks */ = {isa = PBXBuildFile; productRef = F6DAA11B2944F13F001F4C56 /* Splitflap */; }; 14 | F6F29FBE23EDDF19000AF82B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6F29FBD23EDDF19000AF82B /* AppDelegate.swift */; }; 15 | F6F29FC023EDDF19000AF82B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6F29FBF23EDDF19000AF82B /* SceneDelegate.swift */; }; 16 | F6F29FC223EDDF19000AF82B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6F29FC123EDDF19000AF82B /* ViewController.swift */; }; 17 | F6F29FC823EDDF19000AF82B /* flipflap.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F6F29FC623EDDF19000AF82B /* flipflap.xcdatamodeld */; }; 18 | F6F29FCA23EDDF1C000AF82B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F6F29FC923EDDF1C000AF82B /* Assets.xcassets */; }; 19 | F6F29FCD23EDDF1C000AF82B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F6F29FCB23EDDF1C000AF82B /* LaunchScreen.storyboard */; }; 20 | F6F29FD823EDF77A000AF82B /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6F29FD723EDF77A000AF82B /* SettingsViewController.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | F6B15A4C23F478AC001DB252 /* UIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; 25 | F6B15A5123F4CD3D001DB252 /* FontViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontViewController.swift; sourceTree = ""; }; 26 | F6F29FBA23EDDF19000AF82B /* flipflap.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flipflap.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | F6F29FBD23EDDF19000AF82B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | F6F29FBF23EDDF19000AF82B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 29 | F6F29FC123EDDF19000AF82B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 30 | F6F29FC723EDDF19000AF82B /* flipflap.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = flipflap.xcdatamodel; sourceTree = ""; }; 31 | F6F29FC923EDDF1C000AF82B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | F6F29FCC23EDDF1C000AF82B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | F6F29FCE23EDDF1C000AF82B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | F6F29FD723EDF77A000AF82B /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | F6F29FB723EDDF19000AF82B /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | F6DAA11C2944F13F001F4C56 /* Splitflap in Frameworks */, 43 | F6B15A5023F48797001DB252 /* EFColorPicker in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | F653498D23EE136B004222A1 /* Frameworks */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | ); 54 | name = Frameworks; 55 | sourceTree = ""; 56 | }; 57 | F6F29FB123EDDF18000AF82B = { 58 | isa = PBXGroup; 59 | children = ( 60 | F6F29FBC23EDDF19000AF82B /* flipflap */, 61 | F6F29FBB23EDDF19000AF82B /* Products */, 62 | F653498D23EE136B004222A1 /* Frameworks */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | F6F29FBB23EDDF19000AF82B /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | F6F29FBA23EDDF19000AF82B /* flipflap.app */, 70 | ); 71 | name = Products; 72 | sourceTree = ""; 73 | }; 74 | F6F29FBC23EDDF19000AF82B /* flipflap */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | F6F29FBD23EDDF19000AF82B /* AppDelegate.swift */, 78 | F6F29FBF23EDDF19000AF82B /* SceneDelegate.swift */, 79 | F6B15A4C23F478AC001DB252 /* UIViewController.swift */, 80 | F6F29FC123EDDF19000AF82B /* ViewController.swift */, 81 | F6F29FD723EDF77A000AF82B /* SettingsViewController.swift */, 82 | F6B15A5123F4CD3D001DB252 /* FontViewController.swift */, 83 | F6F29FC923EDDF1C000AF82B /* Assets.xcassets */, 84 | F6F29FCB23EDDF1C000AF82B /* LaunchScreen.storyboard */, 85 | F6F29FCE23EDDF1C000AF82B /* Info.plist */, 86 | F6F29FC623EDDF19000AF82B /* flipflap.xcdatamodeld */, 87 | ); 88 | path = flipflap; 89 | sourceTree = ""; 90 | }; 91 | /* End PBXGroup section */ 92 | 93 | /* Begin PBXNativeTarget section */ 94 | F6F29FB923EDDF19000AF82B /* flipflap */ = { 95 | isa = PBXNativeTarget; 96 | buildConfigurationList = F6F29FD123EDDF1C000AF82B /* Build configuration list for PBXNativeTarget "flipflap" */; 97 | buildPhases = ( 98 | F6F29FB623EDDF19000AF82B /* Sources */, 99 | F6F29FB723EDDF19000AF82B /* Frameworks */, 100 | F6F29FB823EDDF19000AF82B /* Resources */, 101 | ); 102 | buildRules = ( 103 | ); 104 | dependencies = ( 105 | ); 106 | name = flipflap; 107 | packageProductDependencies = ( 108 | F6B15A4F23F48797001DB252 /* EFColorPicker */, 109 | F6DAA11B2944F13F001F4C56 /* Splitflap */, 110 | ); 111 | productName = flipflap; 112 | productReference = F6F29FBA23EDDF19000AF82B /* flipflap.app */; 113 | productType = "com.apple.product-type.application"; 114 | }; 115 | /* End PBXNativeTarget section */ 116 | 117 | /* Begin PBXProject section */ 118 | F6F29FB223EDDF18000AF82B /* Project object */ = { 119 | isa = PBXProject; 120 | attributes = { 121 | LastSwiftUpdateCheck = 1130; 122 | LastUpgradeCheck = 1210; 123 | ORGANIZATIONNAME = twodayslate; 124 | TargetAttributes = { 125 | F6F29FB923EDDF19000AF82B = { 126 | CreatedOnToolsVersion = 11.3; 127 | }; 128 | }; 129 | }; 130 | buildConfigurationList = F6F29FB523EDDF18000AF82B /* Build configuration list for PBXProject "flipflap" */; 131 | compatibilityVersion = "Xcode 9.3"; 132 | developmentRegion = en; 133 | hasScannedForEncodings = 0; 134 | knownRegions = ( 135 | en, 136 | Base, 137 | ); 138 | mainGroup = F6F29FB123EDDF18000AF82B; 139 | packageReferences = ( 140 | F6B15A4E23F48797001DB252 /* XCRemoteSwiftPackageReference "EFColorPicker" */, 141 | F6DAA11A2944F13F001F4C56 /* XCRemoteSwiftPackageReference "Splitflap" */, 142 | ); 143 | productRefGroup = F6F29FBB23EDDF19000AF82B /* Products */; 144 | projectDirPath = ""; 145 | projectRoot = ""; 146 | targets = ( 147 | F6F29FB923EDDF19000AF82B /* flipflap */, 148 | ); 149 | }; 150 | /* End PBXProject section */ 151 | 152 | /* Begin PBXResourcesBuildPhase section */ 153 | F6F29FB823EDDF19000AF82B /* Resources */ = { 154 | isa = PBXResourcesBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | F6F29FCD23EDDF1C000AF82B /* LaunchScreen.storyboard in Resources */, 158 | F6F29FCA23EDDF1C000AF82B /* Assets.xcassets in Resources */, 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXResourcesBuildPhase section */ 163 | 164 | /* Begin PBXSourcesBuildPhase section */ 165 | F6F29FB623EDDF19000AF82B /* Sources */ = { 166 | isa = PBXSourcesBuildPhase; 167 | buildActionMask = 2147483647; 168 | files = ( 169 | F6F29FC223EDDF19000AF82B /* ViewController.swift in Sources */, 170 | F6B15A5223F4CD3D001DB252 /* FontViewController.swift in Sources */, 171 | F6F29FBE23EDDF19000AF82B /* AppDelegate.swift in Sources */, 172 | F6B15A4D23F478AC001DB252 /* UIViewController.swift in Sources */, 173 | F6F29FC023EDDF19000AF82B /* SceneDelegate.swift in Sources */, 174 | F6F29FD823EDF77A000AF82B /* SettingsViewController.swift in Sources */, 175 | F6F29FC823EDDF19000AF82B /* flipflap.xcdatamodeld in Sources */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXSourcesBuildPhase section */ 180 | 181 | /* Begin PBXVariantGroup section */ 182 | F6F29FCB23EDDF1C000AF82B /* LaunchScreen.storyboard */ = { 183 | isa = PBXVariantGroup; 184 | children = ( 185 | F6F29FCC23EDDF1C000AF82B /* Base */, 186 | ); 187 | name = LaunchScreen.storyboard; 188 | sourceTree = ""; 189 | }; 190 | /* End PBXVariantGroup section */ 191 | 192 | /* Begin XCBuildConfiguration section */ 193 | F6F29FCF23EDDF1C000AF82B /* Debug */ = { 194 | isa = XCBuildConfiguration; 195 | buildSettings = { 196 | ALWAYS_SEARCH_USER_PATHS = NO; 197 | CLANG_ANALYZER_NONNULL = YES; 198 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 199 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 200 | CLANG_CXX_LIBRARY = "libc++"; 201 | CLANG_ENABLE_MODULES = YES; 202 | CLANG_ENABLE_OBJC_ARC = YES; 203 | CLANG_ENABLE_OBJC_WEAK = YES; 204 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 205 | CLANG_WARN_BOOL_CONVERSION = YES; 206 | CLANG_WARN_COMMA = YES; 207 | CLANG_WARN_CONSTANT_CONVERSION = YES; 208 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 209 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 210 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 211 | CLANG_WARN_EMPTY_BODY = YES; 212 | CLANG_WARN_ENUM_CONVERSION = YES; 213 | CLANG_WARN_INFINITE_RECURSION = YES; 214 | CLANG_WARN_INT_CONVERSION = YES; 215 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 217 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 219 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 220 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 221 | CLANG_WARN_STRICT_PROTOTYPES = YES; 222 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 223 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | COPY_PHASE_STRIP = NO; 227 | DEBUG_INFORMATION_FORMAT = dwarf; 228 | ENABLE_STRICT_OBJC_MSGSEND = YES; 229 | ENABLE_TESTABILITY = YES; 230 | GCC_C_LANGUAGE_STANDARD = gnu11; 231 | GCC_DYNAMIC_NO_PIC = NO; 232 | GCC_NO_COMMON_BLOCKS = YES; 233 | GCC_OPTIMIZATION_LEVEL = 0; 234 | GCC_PREPROCESSOR_DEFINITIONS = ( 235 | "DEBUG=1", 236 | "$(inherited)", 237 | ); 238 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 239 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 240 | GCC_WARN_UNDECLARED_SELECTOR = YES; 241 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 242 | GCC_WARN_UNUSED_FUNCTION = YES; 243 | GCC_WARN_UNUSED_VARIABLE = YES; 244 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 245 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 246 | MTL_FAST_MATH = YES; 247 | ONLY_ACTIVE_ARCH = YES; 248 | SDKROOT = iphoneos; 249 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 250 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 251 | }; 252 | name = Debug; 253 | }; 254 | F6F29FD023EDDF1C000AF82B /* Release */ = { 255 | isa = XCBuildConfiguration; 256 | buildSettings = { 257 | ALWAYS_SEARCH_USER_PATHS = NO; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 261 | CLANG_CXX_LIBRARY = "libc++"; 262 | CLANG_ENABLE_MODULES = YES; 263 | CLANG_ENABLE_OBJC_ARC = YES; 264 | CLANG_ENABLE_OBJC_WEAK = YES; 265 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 266 | CLANG_WARN_BOOL_CONVERSION = YES; 267 | CLANG_WARN_COMMA = YES; 268 | CLANG_WARN_CONSTANT_CONVERSION = YES; 269 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 270 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 271 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 272 | CLANG_WARN_EMPTY_BODY = YES; 273 | CLANG_WARN_ENUM_CONVERSION = YES; 274 | CLANG_WARN_INFINITE_RECURSION = YES; 275 | CLANG_WARN_INT_CONVERSION = YES; 276 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 277 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 278 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 279 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 280 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 281 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 282 | CLANG_WARN_STRICT_PROTOTYPES = YES; 283 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 284 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 285 | CLANG_WARN_UNREACHABLE_CODE = YES; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | COPY_PHASE_STRIP = NO; 288 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 289 | ENABLE_NS_ASSERTIONS = NO; 290 | ENABLE_STRICT_OBJC_MSGSEND = YES; 291 | GCC_C_LANGUAGE_STANDARD = gnu11; 292 | GCC_NO_COMMON_BLOCKS = YES; 293 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 294 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 295 | GCC_WARN_UNDECLARED_SELECTOR = YES; 296 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 297 | GCC_WARN_UNUSED_FUNCTION = YES; 298 | GCC_WARN_UNUSED_VARIABLE = YES; 299 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 300 | MTL_ENABLE_DEBUG_INFO = NO; 301 | MTL_FAST_MATH = YES; 302 | SDKROOT = iphoneos; 303 | SWIFT_COMPILATION_MODE = wholemodule; 304 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 305 | VALIDATE_PRODUCT = YES; 306 | }; 307 | name = Release; 308 | }; 309 | F6F29FD223EDDF1C000AF82B /* Debug */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 313 | CODE_SIGN_STYLE = Automatic; 314 | CURRENT_PROJECT_VERSION = 2; 315 | DEVELOPMENT_TEAM = C6L3992RFB; 316 | INFOPLIST_FILE = flipflap/Info.plist; 317 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 318 | LD_RUNPATH_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "@executable_path/Frameworks", 321 | ); 322 | MARKETING_VERSION = 1.1.2; 323 | PRODUCT_BUNDLE_IDENTIFIER = com.twodayslate.flipflap; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SWIFT_VERSION = 5.0; 326 | TARGETED_DEVICE_FAMILY = "1,2"; 327 | }; 328 | name = Debug; 329 | }; 330 | F6F29FD323EDDF1C000AF82B /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 334 | CODE_SIGN_STYLE = Automatic; 335 | CURRENT_PROJECT_VERSION = 2; 336 | DEVELOPMENT_TEAM = C6L3992RFB; 337 | INFOPLIST_FILE = flipflap/Info.plist; 338 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 339 | LD_RUNPATH_SEARCH_PATHS = ( 340 | "$(inherited)", 341 | "@executable_path/Frameworks", 342 | ); 343 | MARKETING_VERSION = 1.1.2; 344 | PRODUCT_BUNDLE_IDENTIFIER = com.twodayslate.flipflap; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SWIFT_VERSION = 5.0; 347 | TARGETED_DEVICE_FAMILY = "1,2"; 348 | }; 349 | name = Release; 350 | }; 351 | /* End XCBuildConfiguration section */ 352 | 353 | /* Begin XCConfigurationList section */ 354 | F6F29FB523EDDF18000AF82B /* Build configuration list for PBXProject "flipflap" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | F6F29FCF23EDDF1C000AF82B /* Debug */, 358 | F6F29FD023EDDF1C000AF82B /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | F6F29FD123EDDF1C000AF82B /* Build configuration list for PBXNativeTarget "flipflap" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | F6F29FD223EDDF1C000AF82B /* Debug */, 367 | F6F29FD323EDDF1C000AF82B /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | /* End XCConfigurationList section */ 373 | 374 | /* Begin XCRemoteSwiftPackageReference section */ 375 | F6B15A4E23F48797001DB252 /* XCRemoteSwiftPackageReference "EFColorPicker" */ = { 376 | isa = XCRemoteSwiftPackageReference; 377 | repositoryURL = "https://github.com/EFPrefix/EFColorPicker"; 378 | requirement = { 379 | kind = upToNextMajorVersion; 380 | minimumVersion = 5.2.2; 381 | }; 382 | }; 383 | F6DAA11A2944F13F001F4C56 /* XCRemoteSwiftPackageReference "Splitflap" */ = { 384 | isa = XCRemoteSwiftPackageReference; 385 | repositoryURL = "https://github.com/twodayslate/Splitflap"; 386 | requirement = { 387 | branch = mainline; 388 | kind = branch; 389 | }; 390 | }; 391 | /* End XCRemoteSwiftPackageReference section */ 392 | 393 | /* Begin XCSwiftPackageProductDependency section */ 394 | F6B15A4F23F48797001DB252 /* EFColorPicker */ = { 395 | isa = XCSwiftPackageProductDependency; 396 | package = F6B15A4E23F48797001DB252 /* XCRemoteSwiftPackageReference "EFColorPicker" */; 397 | productName = EFColorPicker; 398 | }; 399 | F6DAA11B2944F13F001F4C56 /* Splitflap */ = { 400 | isa = XCSwiftPackageProductDependency; 401 | package = F6DAA11A2944F13F001F4C56 /* XCRemoteSwiftPackageReference "Splitflap" */; 402 | productName = Splitflap; 403 | }; 404 | /* End XCSwiftPackageProductDependency section */ 405 | 406 | /* Begin XCVersionGroup section */ 407 | F6F29FC623EDDF19000AF82B /* flipflap.xcdatamodeld */ = { 408 | isa = XCVersionGroup; 409 | children = ( 410 | F6F29FC723EDDF19000AF82B /* flipflap.xcdatamodel */, 411 | ); 412 | currentVersion = F6F29FC723EDDF19000AF82B /* flipflap.xcdatamodel */; 413 | path = flipflap.xcdatamodeld; 414 | sourceTree = ""; 415 | versionGroupType = wrapper.xcdatamodel; 416 | }; 417 | /* End XCVersionGroup section */ 418 | }; 419 | rootObject = F6F29FB223EDDF18000AF82B /* Project object */; 420 | } 421 | -------------------------------------------------------------------------------- /flipflap/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // flipflap 4 | // 5 | // Created by Zachary Gorak on 2/7/20. 6 | // Copyright © 2020 twodayslate. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import EFColorPicker 12 | 13 | protocol SettingsControllerDelegate { 14 | func willCloseSettings() 15 | func didCloseSettings() 16 | func didChangeKey(_ key: String) 17 | } 18 | 19 | class SettingsNavigationController: UINavigationController { 20 | var settingsDelegate: SettingsControllerDelegate? = nil { 21 | didSet { 22 | settings.settingsDelegate = self.settingsDelegate 23 | } 24 | } 25 | 26 | convenience init() { 27 | self.init(nibName: nil, bundle: nil) 28 | } 29 | 30 | let settings = SettingsViewController(style: .insetGrouped) 31 | 32 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 33 | super.init(nibName: nil, bundle: nil) 34 | 35 | self.viewControllers = [settings] 36 | 37 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(close(_:))) 38 | } 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | self.view.backgroundColor = .systemBackground 43 | } 44 | 45 | @objc func close(_ sender: Any?) { 46 | self.settingsDelegate?.willCloseSettings() 47 | self.dismiss(animated: true, completion: { 48 | self.settingsDelegate?.didCloseSettings() 49 | }) 50 | } 51 | 52 | required init?(coder aDecoder: NSCoder) { 53 | fatalError("init(coder:) has not been implemented") 54 | } 55 | } 56 | 57 | class SettingsViewController: UITableViewController { 58 | var settingsDelegate: SettingsControllerDelegate? = nil 59 | 60 | override func viewDidLoad() { 61 | super.viewDidLoad() 62 | self.title = "Settings" 63 | self.view.backgroundColor = .systemBackground 64 | self.tableView.backgroundColor = .systemGroupedBackground 65 | 66 | self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self.navigationController, action: #selector(SettingsNavigationController.close(_:))) 67 | } 68 | 69 | let themeSheet = UIAlertController(title: "Theme", message: nil, preferredStyle: .actionSheet) 70 | 71 | override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 72 | switch section { 73 | case 0: 74 | return "Appearance" 75 | default: 76 | return nil 77 | } 78 | } 79 | 80 | override func numberOfSections(in tableView: UITableView) -> Int { 81 | return 2 82 | } 83 | 84 | @objc func showSeconds(_ sender: UISwitch) { 85 | UserDefaults.standard.set(sender.isOn, forKey: "showSeconds") 86 | } 87 | 88 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 89 | var cell = UITableViewCell(style: .default, reuseIdentifier: "cell") 90 | switch indexPath.section { 91 | case 0: 92 | cell = UITableViewCell(style: .value1, reuseIdentifier: "value1") 93 | cell.accessoryType = .disclosureIndicator 94 | switch indexPath.row { 95 | case 0: 96 | cell.textLabel?.text = "Theme" 97 | 98 | switch UserDefaults.standard.integer(forKey: "theme") { 99 | case 1: 100 | cell.detailTextLabel?.text = "Light" 101 | case 2: 102 | cell.detailTextLabel?.text = "Dark" 103 | case 3: 104 | cell.detailTextLabel?.text = "Custom" 105 | default: 106 | cell.detailTextLabel?.text = "System" 107 | } 108 | break 109 | case 1: 110 | if UserDefaults.standard.integer(forKey: "theme") == 3 { 111 | cell.textLabel?.text = "Background Color" 112 | var color = UIColor.systemBackground 113 | 114 | //colorView.backgroundColor = .red 115 | if let data = UserDefaults.standard.object(forKey: "background_color") as? Data, let dcolor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 116 | 117 | color = dcolor 118 | } 119 | 120 | let size = CGSize(width: 24.0, height: 24.0) 121 | let renderer = UIGraphicsImageRenderer(size: size) 122 | let image = renderer.image(actions: { rendererContext in 123 | color.setFill() 124 | rendererContext.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) 125 | }) 126 | cell.imageView?.image = image 127 | cell.imageView?.layer.cornerRadius = 5.0 128 | cell.imageView?.layer.borderColor = UIColor.opaqueSeparator.cgColor 129 | cell.imageView?.layer.borderWidth = 1.0 130 | cell.imageView?.clipsToBounds = true 131 | 132 | } else { 133 | cell.textLabel?.text = "Font" 134 | cell.accessoryType = .disclosureIndicator 135 | if let data = UserDefaults.standard.object(forKey: "font") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [UIFont.self], from: data) as? UIFont { 136 | 137 | cell.detailTextLabel?.text = color.familyName 138 | } else { 139 | cell.detailTextLabel?.text = "Courier" 140 | } 141 | } 142 | case 2: 143 | if UserDefaults.standard.integer(forKey: "theme") == 3 { 144 | cell.textLabel?.text = "Flap Color" 145 | var color = UIColor.secondarySystemBackground 146 | 147 | //colorView.backgroundColor = .red 148 | if let data = UserDefaults.standard.object(forKey: "flap_color") as? Data, let dcolor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 149 | 150 | color = dcolor 151 | } 152 | 153 | let size = CGSize(width: 24.0, height: 24.0) 154 | let renderer = UIGraphicsImageRenderer(size: size) 155 | let image = renderer.image(actions: { rendererContext in 156 | color.setFill() 157 | rendererContext.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) 158 | }) 159 | cell.imageView?.image = image 160 | cell.imageView?.layer.cornerRadius = 5.0 161 | cell.imageView?.layer.borderColor = UIColor.opaqueSeparator.cgColor 162 | cell.imageView?.layer.borderWidth = 1.0 163 | cell.imageView?.clipsToBounds = true 164 | } else { 165 | cell = UITableViewCell(style: .default, reuseIdentifier: "reset") 166 | cell.accessoryType = .none 167 | cell.textLabel?.text = "Reset to Default" 168 | cell.textLabel?.textColor = tableView.tintColor 169 | cell.textLabel?.textAlignment = .center 170 | } 171 | 172 | case 3: 173 | cell.textLabel?.text = "Text Color" 174 | var color = UIColor.label 175 | 176 | //colorView.backgroundColor = .red 177 | if let data = UserDefaults.standard.object(forKey: "text_color") as? Data, let dcolor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 178 | 179 | color = dcolor 180 | } 181 | 182 | let size = CGSize(width: 24.0, height: 24.0) 183 | let renderer = UIGraphicsImageRenderer(size: size) 184 | let image = renderer.image(actions: { rendererContext in 185 | color.setFill() 186 | rendererContext.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) 187 | }) 188 | cell.imageView?.image = image 189 | cell.imageView?.layer.cornerRadius = 5.0 190 | cell.imageView?.layer.borderColor = UIColor.opaqueSeparator.cgColor 191 | cell.imageView?.layer.borderWidth = 1.0 192 | cell.imageView?.clipsToBounds = true 193 | case 4: 194 | cell.textLabel?.text = "Font" 195 | cell.accessoryType = .disclosureIndicator 196 | if let data = UserDefaults.standard.object(forKey: "font") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [UIFont.self], from: data) as? UIFont { 197 | 198 | cell.detailTextLabel?.text = color.familyName 199 | } else { 200 | cell.detailTextLabel?.text = "Courier" 201 | } 202 | case 5: 203 | cell.textLabel?.text = "Reset to Default" 204 | cell.accessoryType = .none 205 | cell.textLabel?.textColor = tableView.tintColor 206 | cell.textLabel?.textAlignment = .center 207 | default: 208 | break 209 | } 210 | 211 | break 212 | case 1: 213 | let secSwitch = UISwitch() 214 | cell.textLabel?.text = "Show seconds" 215 | secSwitch.setOn(UserDefaults.standard.bool(forKey: "showSeconds"), animated: false) 216 | secSwitch.addTarget(self, action: #selector(showSeconds(_:)), for: .valueChanged) 217 | cell.accessoryView = secSwitch 218 | cell.selectionStyle = .none 219 | default: 220 | break 221 | } 222 | 223 | return cell 224 | } 225 | 226 | var currentColorPicker: EFColorSelectionViewController? = nil 227 | var currentColorPickerKey: String? = nil 228 | 229 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 230 | switch indexPath.section { 231 | case 0: 232 | switch indexPath.row { 233 | case 0: 234 | if themeSheet.actions.count == 0 { 235 | let inappSafariAction = UIAlertAction(title: "System", style: .default, handler: { _ in 236 | print("Auto") 237 | UserDefaults.standard.set(0, forKey: "theme") 238 | UserDefaults.standard.synchronize() 239 | let cell = self.tableView.cellForRow(at: indexPath) 240 | cell?.detailTextLabel?.text = "System" 241 | self.clearCustomColors() 242 | self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) 243 | 244 | }) 245 | themeSheet.addAction(inappSafariAction) 246 | if #available(iOS 13.0, *) { 247 | let safariAction = UIAlertAction(title: "Light", style: .default, handler: { _ in 248 | print("Auto") 249 | UserDefaults.standard.set(1, forKey: "theme") 250 | UserDefaults.standard.synchronize() 251 | let cell = self.tableView.cellForRow(at: indexPath) 252 | cell?.detailTextLabel?.text = "Light" 253 | self.clearCustomColors() 254 | self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) 255 | }) 256 | themeSheet.addAction(safariAction) 257 | let darkAction = UIAlertAction(title: "Dark", style: .default, handler: { _ in 258 | print("Dark") 259 | UserDefaults.standard.set(2, forKey: "theme") 260 | UserDefaults.standard.synchronize() 261 | let cell = self.tableView.cellForRow(at: indexPath) 262 | cell?.detailTextLabel?.text = "Dark" 263 | self.clearCustomColors() 264 | self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) 265 | }) 266 | themeSheet.addAction(darkAction) 267 | let customAction = UIAlertAction(title: "Custom", style: .default, handler: { _ in 268 | UserDefaults.standard.set(3, forKey: "theme") 269 | UserDefaults.standard.synchronize() 270 | let cell = self.tableView.cellForRow(at: indexPath) 271 | cell?.detailTextLabel?.text = "Custom" 272 | self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) 273 | }) 274 | themeSheet.addAction(customAction) 275 | } 276 | themeSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) 277 | } 278 | 279 | for (index, action) in themeSheet.actions.enumerated() { 280 | if UserDefaults.standard.integer(forKey: "theme") == index { 281 | action.setValue("true", forKey: "checked") 282 | } else { 283 | action.setValue("false", forKey: "checked") 284 | } 285 | } 286 | // swiftlint:disable:next line_length 287 | self.addActionSheetForiPad(actionSheet: themeSheet) 288 | present(themeSheet, animated: true) { 289 | tableView.deselectRow(at: indexPath, animated: true) 290 | } 291 | case 4: 292 | print("selected 4") 293 | let c = FontNavigationController() 294 | c.fontDelegate = self 295 | c.currentlySelectedFont = self.currentlySelectedFont 296 | self.present(c, animated: true, completion: { 297 | tableView.deselectRow(at: indexPath, animated: true) 298 | }) 299 | break 300 | case 5: 301 | print("selected 5") 302 | tableView.deselectRow(at: indexPath, animated: true) 303 | self.resetToDefault() 304 | break 305 | default: 306 | if UserDefaults.standard.integer(forKey: "theme") == 3 { 307 | 308 | let colorSelectionController = EFColorSelectionViewController() 309 | colorSelectionController.isColorTextFieldHidden = false 310 | let navCtrl = UINavigationController(rootViewController: colorSelectionController) 311 | navCtrl.navigationBar.backgroundColor = UIColor.systemBackground 312 | navCtrl.navigationBar.isTranslucent = false 313 | navCtrl.modalPresentationStyle = UIModalPresentationStyle.popover 314 | 315 | navCtrl.popoverPresentationController?.sourceView = tableView.cellForRow(at: indexPath) 316 | navCtrl.popoverPresentationController?.sourceRect = tableView.cellForRow(at: indexPath)!.bounds 317 | navCtrl.preferredContentSize = colorSelectionController.view.systemLayoutSizeFitting( 318 | UIView.layoutFittingCompressedSize 319 | ) 320 | if UIUserInterfaceSizeClass.compact == self.traitCollection.horizontalSizeClass { 321 | 322 | colorSelectionController.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .close, target: self, action: #selector(dismissColorPicker(_:))) 323 | 324 | colorSelectionController.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Reset", style: .done, target: self, action: #selector(resetsColorPicker(_:))) 325 | } 326 | 327 | //colorSelectionController.delegate = self 328 | 329 | colorSelectionController.setMode(mode: .all) 330 | self.currentColorPicker = colorSelectionController 331 | 332 | switch indexPath.row { 333 | case 1: 334 | self.currentColorPickerKey = "background_color" 335 | if let data = UserDefaults.standard.object(forKey: "background_color") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 336 | 337 | colorSelectionController.color = color 338 | } else { 339 | colorSelectionController.color = .systemBackground 340 | } 341 | break 342 | case 2: 343 | self.currentColorPickerKey = "flap_color" 344 | if let data = UserDefaults.standard.object(forKey: "flap_color") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 345 | 346 | colorSelectionController.color = color 347 | } else { 348 | colorSelectionController.color = .secondarySystemBackground 349 | } 350 | break 351 | case 3: 352 | self.currentColorPickerKey = "text_color" 353 | if let data = UserDefaults.standard.object(forKey: "text_color") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: data) { 354 | 355 | colorSelectionController.color = color 356 | } else { 357 | colorSelectionController.color = .label 358 | } 359 | break 360 | 361 | default: 362 | break 363 | } 364 | 365 | present(navCtrl, animated: true) { 366 | tableView.deselectRow(at: indexPath, animated: true) 367 | } 368 | } else { 369 | switch indexPath.row { 370 | case 1: 371 | let c = FontNavigationController() 372 | c.fontDelegate = self 373 | c.currentlySelectedFont = self.currentlySelectedFont 374 | self.present(c, animated: true, completion: { 375 | tableView.deselectRow(at: indexPath, animated: true) 376 | }) 377 | case 2: 378 | tableView.deselectRow(at: indexPath, animated: true) 379 | self.resetToDefault() 380 | default: 381 | break 382 | } 383 | } 384 | break 385 | } 386 | break 387 | default: 388 | break 389 | } 390 | } 391 | 392 | @objc func dismissColorPicker(_ sender: UIBarButtonItem?) { 393 | print(self.currentColorPicker?.color) 394 | guard let color = self.currentColorPicker?.color, let data = try? NSKeyedArchiver.archivedData(withRootObject: color, requiringSecureCoding: false), let key = currentColorPickerKey else { 395 | return 396 | } 397 | UserDefaults.standard.set(data, forKey: key) 398 | 399 | self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) 400 | 401 | self.currentColorPicker?.dismiss(animated: true, completion: nil) 402 | } 403 | 404 | func resetToDefault() { 405 | UserDefaults.standard.set(0, forKey: "theme") 406 | self.clearCustomColors() 407 | let font = UIFont(name: "Courier", size: 45.0) 408 | if let data = try? NSKeyedArchiver.archivedData(withRootObject: font, requiringSecureCoding: false) { 409 | UserDefaults.standard.set(data, forKey: "font") 410 | } 411 | 412 | self.tableView.reloadSections(IndexSet(integer: 0), with: .automatic) 413 | } 414 | 415 | func clearCustomColors() { 416 | UserDefaults.standard.removeObject(forKey: "background_color") 417 | UserDefaults.standard.removeObject(forKey: "text_color") 418 | UserDefaults.standard.removeObject(forKey: "flap_color") 419 | self.settingsDelegate?.didChangeKey("background_color") 420 | self.settingsDelegate?.didChangeKey("text_color") 421 | self.settingsDelegate?.didChangeKey("flap_color") 422 | } 423 | 424 | @objc func resetsColorPicker(_ sender: UIBarButtonItem?) { 425 | guard let key = currentColorPickerKey else { 426 | return 427 | } 428 | 429 | var color = UIColor.systemBackground 430 | if key == "text_color" { 431 | color = .label 432 | } else if key == "flap_color" { 433 | color = .secondarySystemBackground 434 | } 435 | self.currentColorPicker?.color = color 436 | 437 | self.dismissColorPicker(sender) 438 | } 439 | 440 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 441 | switch section { 442 | case 0: 443 | if UserDefaults.standard.integer(forKey: "theme") == 3 { 444 | return 6 445 | } 446 | return 3 447 | default: 448 | return 1 449 | } 450 | } 451 | } 452 | 453 | extension SettingsViewController: FontControllerDelegate { 454 | var currentlySelectedFont: String? { 455 | if let data = UserDefaults.standard.object(forKey: "font") as? Data, let color = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [UIFont.self], from: data) as? UIFont { 456 | 457 | return color.familyName 458 | } else { 459 | return "Courier" 460 | } 461 | } 462 | 463 | func didChangeFont(name: String) { 464 | self.tableView.reloadData() 465 | } 466 | 467 | 468 | } 469 | --------------------------------------------------------------------------------