-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Password protected entry point for debug menu (#12)
- Loading branch information
1 parent
fbf65ad
commit 9501e2c
Showing
5 changed files
with
218 additions
and
8 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Alejandro Zielinsky on 2021-12-06. | ||
// | ||
|
||
import Foundation | ||
import UIKit | ||
import SwiftUI | ||
|
||
struct DebugPasswordAlertWrapper<Content: View>: UIViewControllerRepresentable { | ||
@Binding var isPresented: Bool | ||
let alert: DebugPasswordAlert | ||
let content: Content | ||
|
||
func makeUIViewController(context: UIViewControllerRepresentableContext<DebugPasswordAlertWrapper>) -> UIHostingController<Content> { | ||
UIHostingController(rootView: content) | ||
} | ||
|
||
final class Coordinator { | ||
var alertController: UIAlertController? | ||
init(_ controller: UIAlertController? = nil) { | ||
self.alertController = controller | ||
} | ||
} | ||
|
||
func makeCoordinator() -> Coordinator { | ||
return Coordinator() | ||
} | ||
|
||
func updateUIViewController(_ uiViewController: UIHostingController<Content>, context: UIViewControllerRepresentableContext<DebugPasswordAlertWrapper>) { | ||
uiViewController.rootView = content | ||
if isPresented && uiViewController.presentedViewController == nil { | ||
var alert = self.alert | ||
alert.action = { | ||
self.isPresented = false | ||
self.alert.action($0) | ||
} | ||
context.coordinator.alertController = UIAlertController(alert: alert) | ||
uiViewController.present(context.coordinator.alertController!, animated: true) | ||
} | ||
if !isPresented && uiViewController.presentedViewController == context.coordinator.alertController { | ||
uiViewController.dismiss(animated: true) | ||
} | ||
} | ||
} | ||
|
||
struct DebugPasswordAlert { | ||
|
||
var title: String | ||
var message: String | ||
var placeholder: String | ||
var accept: String | ||
var cancel: String? | ||
var secondaryActionTitle: String? | ||
var keyboardType: UIKeyboardType | ||
var action: (String?) -> Void | ||
var secondaryAction: (() -> Void)? | ||
|
||
init(title: String = "Debug Settings", | ||
message: String = "Enter Password", | ||
placeholder: String = "", | ||
accept: String = "OK", | ||
cancel: String? = "Cancel", | ||
secondaryActionTitle: String? = nil, | ||
keyboardType: UIKeyboardType = .default, | ||
action: @escaping (String?) -> Void, | ||
secondaryAction: (() -> Void)? = nil) { | ||
self.title = title | ||
self.message = message | ||
self.placeholder = placeholder | ||
self.accept = accept | ||
self.cancel = cancel | ||
self.secondaryActionTitle = secondaryActionTitle | ||
self.keyboardType = keyboardType | ||
self.action = action | ||
self.secondaryAction = secondaryAction | ||
} | ||
} | ||
|
||
extension UIAlertController { | ||
convenience init(alert: DebugPasswordAlert) { | ||
self.init(title: alert.title, message: alert.message, preferredStyle: .alert) | ||
addTextField { | ||
$0.placeholder = alert.placeholder | ||
$0.keyboardType = alert.keyboardType | ||
} | ||
if let cancel = alert.cancel { | ||
addAction(UIAlertAction(title: cancel, style: .cancel) { _ in | ||
alert.action(nil) | ||
}) | ||
} | ||
if let secondaryActionTitle = alert.secondaryActionTitle { | ||
addAction(UIAlertAction(title: secondaryActionTitle, style: .default, handler: { _ in | ||
alert.secondaryAction?() | ||
})) | ||
} | ||
let textField = self.textFields?.first | ||
addAction(UIAlertAction(title: alert.accept, style: .default) { _ in | ||
alert.action(textField?.text) | ||
}) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// | ||
// DebugPasswordEntry.swift | ||
// | ||
// | ||
// Created by Alejandro Zielinsky on 2021-12-06. | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
import CommonCrypto | ||
|
||
struct DebugPasswordEntry: ViewModifier { | ||
|
||
private let debugDataSource: DebugMenuDataSource | ||
private let longPressDuration: CGFloat | ||
private let passwordHash: String | ||
@State private var showDialog = false | ||
@State private var showDebugMenu = false | ||
private let forceShow: Binding<Bool>? | ||
|
||
init(dataSource: DebugMenuDataSource, | ||
passwordSHA256: String, | ||
longPressDuration: CGFloat, | ||
forceShow: Binding<Bool>? = nil) { | ||
self.passwordHash = passwordSHA256 | ||
self.longPressDuration = longPressDuration | ||
self.debugDataSource = dataSource | ||
self.forceShow = forceShow | ||
} | ||
|
||
func body(content: Content) -> some View { | ||
content | ||
.onLongPressGesture(minimumDuration: longPressDuration) { | ||
if let forceShow = forceShow?.wrappedValue, forceShow == true { | ||
showDebugMenu = true | ||
} else { | ||
showDialog = true | ||
} | ||
} | ||
.debugPasswordAlert(isPresented: $showDialog, DebugPasswordAlert(action: self.onPasswordEntered)) | ||
.sheet(isPresented: $showDebugMenu) { | ||
DebugMenuView(dataSource: debugDataSource) | ||
} | ||
} | ||
|
||
private func onPasswordEntered(input: String?) { | ||
guard let input = input else { return } | ||
if input.sha256 == self.passwordHash { | ||
showDialog = false | ||
showDebugMenu = true | ||
forceShow?.wrappedValue = true | ||
} else { | ||
showDialog = false | ||
} | ||
} | ||
} | ||
|
||
public extension View { | ||
func debugMenuNavigation(dataSource: DebugMenuDataSource, | ||
passwordSHA256: String, | ||
longPressDuration: CGFloat = 5.0, | ||
forceShow: Binding<Bool>? = nil) -> some View { | ||
modifier(DebugPasswordEntry(dataSource: dataSource, | ||
passwordSHA256: passwordSHA256, | ||
longPressDuration: longPressDuration, | ||
forceShow: forceShow)) | ||
} | ||
} | ||
|
||
private extension Data { | ||
|
||
var sha256: String { | ||
hexStringFromData(input: digest(input: self as NSData)) | ||
} | ||
|
||
private func digest(input: NSData) -> NSData { | ||
let digestLength = Int(CC_SHA256_DIGEST_LENGTH) | ||
var hash = [UInt8](repeating: 0, count: digestLength) | ||
CC_SHA256(input.bytes, UInt32(input.length), &hash) | ||
return NSData(bytes: hash, length: digestLength) | ||
} | ||
|
||
private func hexStringFromData(input: NSData) -> String { | ||
var bytes = [UInt8](repeating: 0, count: input.length) | ||
input.getBytes(&bytes, length: input.length) | ||
|
||
var hexString = "" | ||
for byte in bytes { | ||
hexString += String(format: "%02x", UInt8(byte)) | ||
} | ||
|
||
return hexString | ||
} | ||
} | ||
|
||
private extension String { | ||
var sha256: String { | ||
self.data(using: String.Encoding.utf8)?.sha256 ?? "" | ||
} | ||
} | ||
|
||
extension View { | ||
func debugPasswordAlert(isPresented: Binding<Bool>, _ alert: DebugPasswordAlert) -> some View { | ||
DebugPasswordAlertWrapper(isPresented: isPresented, alert: alert, content: self) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters