Skip to content

Commit

Permalink
Merge pull request #152 from cocoatype/151-improve-debug-overlay
Browse files Browse the repository at this point in the history
Add debug overlay preferences view
  • Loading branch information
Arclite authored Jun 17, 2024
2 parents a162585 + c0b8d30 commit ef0811b
Show file tree
Hide file tree
Showing 14 changed files with 300 additions and 82 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Created by Geoff Pado on 6/17/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import SwiftUI
import UIKit

@available(iOS 15.0, *)
public class OverlayPreferencesHostingController: UIHostingController<OverlayPreferencesView> {
public init() {
super.init(rootView: OverlayPreferencesView())

sheetPresentationController?.detents = [.medium()]
}

@available(*, unavailable)
required init(coder: NSCoder) {
let typeName = NSStringFromClass(type(of: self))
fatalError("\(typeName) does not implement init(coder:)")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Created by Geoff Pado on 6/17/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import Defaults
import DesignSystem
import SwiftUI

@available(iOS 15.0, *)
public struct OverlayPreferencesView: View {
@Defaults.Binding(key: .showDetectedTextOverlay) private var isDetectedTextOverlayEnabled: Bool
@Defaults.Binding(key: .showDetectedCharactersOverlay) private var isDetectedCharactersOverlayEnabled: Bool
@Defaults.Binding(key: .showRecognizedTextOverlay) private var isRecognizedTextOverlayEnabled: Bool
@Defaults.Binding(key: .showCalculatedOverlay) private var isCalculatedOverlayEnabled: Bool

public var body: some View {
List {
PreferencesCell(isOn: $isDetectedTextOverlayEnabled, title: "Detected Text", color: .red)
PreferencesCell(isOn: $isDetectedCharactersOverlayEnabled, title: "Detected Characters", color: .blue)
PreferencesCell(isOn: $isRecognizedTextOverlayEnabled, title: "Recognized Text", color: .yellow)
PreferencesCell(isOn: $isCalculatedOverlayEnabled, title: "Calculated Area", color: .green)
}
}

private struct PreferencesCell: View {
@Binding var isOn: Bool
let title: String
let color: Color

var body: some View {
Toggle(isOn: $isOn) {
Text(title)
}.tint(color)
}
}
}

@available(iOS 15.0, *)
enum OverlayPreferencesViewPreviews: PreviewProvider {
static var previews: some View {
OverlayPreferencesView()
}
}
76 changes: 76 additions & 0 deletions Modules/Capabilities/Defaults/Sources/DefaultsBinding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Created by Geoff Pado on 6/17/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

import Foundation
import SwiftUI

extension Defaults {
@propertyWrapper public struct Binding<ValueType> {
public var wrappedValue: ValueType {
get {
Self.object(for: key, fallback: fallback)
}
nonmutating set {
Self.userDefaults.set(newValue, forKey: key.rawValue)
NotificationCenter.default.post(name: valueDidChange, object: nil)
}
}

public init(key: Defaults.Key, fallback: ValueType) {
self.key = key
self.fallback = fallback
let valueDidChange = Notification.Name("Defaults.valueDidChange.\(key.rawValue)")
self.binding = SwiftUI.Binding(get: {
Self.object(for: key, fallback: fallback)
}, set: {
Self.userDefaults.set($0, forKey: key.rawValue)
NotificationCenter.default.post(name: valueDidChange, object: nil)
})
self.valueDidChange = valueDidChange
}

public init(key: Defaults.Key) where ValueType == Bool {
self.init(key: key, fallback: false)
}

public init(key: Defaults.Key) where ValueType == Int {
self.init(key: key, fallback: 0)
}

public init<ElementType>(key: Defaults.Key) where ValueType == [ElementType] {
self.init(key: key, fallback: [])
}

public init<DictKey, DictValue>(key: Defaults.Key) where ValueType == [DictKey: DictValue] {
self.init(key: key, fallback: [:])
}

private let key: Defaults.Key
private let fallback: ValueType

// MARK: Projected Value

public var projectedValue: SwiftUI.Binding<ValueType> {
return binding
}

private let binding: SwiftUI.Binding<ValueType>

// MARK: Boilerplate

private static func object(for key: Key, fallback: ValueType) -> ValueType {
guard let object = Self.userDefaults.object(forKey: key.rawValue) as? ValueType else { return fallback }
return object
}

public let valueDidChange: Notification.Name

static var userDefaults: UserDefaults {
guard ProcessInfo.processInfo.environment["IS_TEST"] == nil else {
return UserDefaults.test
}

return UserDefaults.standard
}
}
}
7 changes: 6 additions & 1 deletion Modules/Capabilities/Defaults/Sources/DefaultsKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import Foundation
extension Defaults {
public enum Key: String {
case numberOfSaves = "Defaults.Keys.numberOfSaves2"
// case autoRedactionsWordList = "Defaults.Keys.autoRedactionsWordList"
case autoRedactionsCategoryNames = "Defaults.Keys.autoRedactionsCategoryNames"
case autoRedactionsCategoryAddresses = "Defaults.Keys.autoRedactionsCategoryAddresses"
case autoRedactionsCategoryPhoneNumbers = "Defaults.Keys.autoRedactionsCategoryPhoneNumbers"
case autoRedactionsSet = "Defaults.Keys.autoRedactionsSet"
case recentBookmarks = "Defaults.Keys.recentBookmarks"
case hideDocumentScanner = "Defaults.Keys.hideDocumentScanner"
case hideAutoRedactions = "Defaults.Keys.hideAutoRedactions"

// Debug Overlay
case showDetectedTextOverlay = "Defaults.Keys.showDetectedTextOverlay"
case showDetectedCharactersOverlay = "Defaults.Keys.showDetectedCharactersOverlay"
case showRecognizedTextOverlay = "Defaults.Keys.showRecognizedTextOverlay"
case showCalculatedOverlay = "Defaults.Keys.showCalculatedOverlay"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Created by Geoff Pado on 6/17/24.
// Copyright © 2024 Cocoatype, LLC. All rights reserved.

class PhotoEditingObservationDebugLayer: CAShapeLayer {
init(fillColor: UIColor, frame: CGRect, path: CGPath) {
super.init()
self.fillColor = fillColor.withAlphaComponent(0.3).cgColor
self.frame = frame
self.path = path
}

override init(layer: Any) {
super.init(layer: layer)
}

@available(*, unavailable)
required init(coder: NSCoder) {
let typeName = NSStringFromClass(type(of: self))
fatalError("\(typeName) does not implement init(coder:)")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Created by Geoff Pado on 7/8/22.
// Copyright © 2022 Cocoatype, LLC. All rights reserved.

@_implementationOnly import ClippingBezier
import Combine
import Defaults
import Observations
import UIKit

class PhotoEditingObservationDebugView: PhotoEditingRedactionView {
override init() {
super.init()
isUserInteractionEnabled = false
subscribeToUpdates()
}

deinit {
_ = cancellables.map(NotificationCenter.default.removeObserver(_:))
}

// MARK: Text Observations

var textObservations: [TextRectangleObservation]? {
didSet {
updateDebugLayers()
setNeedsDisplay()
}
}

var recognizedTextObservations: [RecognizedTextObservation]? {
didSet {
updateDebugLayers()
setNeedsDisplay()
}
}

// MARK: Preferences

@Defaults.Value(key: .showDetectedTextOverlay) private var isDetectedTextOverlayEnabled: Bool
@Defaults.Value(key: .showDetectedCharactersOverlay) private var isDetectedCharactersOverlayEnabled: Bool
@Defaults.Value(key: .showRecognizedTextOverlay) private var isRecognizedTextOverlayEnabled: Bool
@Defaults.Value(key: .showCalculatedOverlay) private var isCalculatedOverlayEnabled: Bool
private var cancellables = [any NSObjectProtocol]()

private func subscribeToUpdates() {
let update: @MainActor @Sendable () -> Void = { [weak self] in self?.updateDebugLayers() }
cancellables.append(NotificationCenter.default.addObserver(for: _isDetectedTextOverlayEnabled, block: update))
cancellables.append(NotificationCenter.default.addObserver(for: _isDetectedCharactersOverlayEnabled, block: update))
cancellables.append(NotificationCenter.default.addObserver(for: _isRecognizedTextOverlayEnabled, block: update))
cancellables.append(NotificationCenter.default.addObserver(for: _isCalculatedOverlayEnabled, block: update))
}

private func updateDebugLayers() {
Task {
layer.sublayers = await debugLayers
}
}

// MARK: Debug Layers

private var debugLayers: [CAShapeLayer] {
get async {
guard FeatureFlag.shouldShowDebugOverlay, let textObservations, let recognizedTextObservations else { return [] }

// find words (new system)
let wordLayers: [PhotoEditingObservationDebugLayer]
if isRecognizedTextOverlayEnabled {
wordLayers = recognizedTextObservations.map { wordObservation in
PhotoEditingObservationDebugLayer(fillColor: .systemYellow, frame: bounds, path: wordObservation.path)
}
} else { wordLayers = [] }

// find text (old system)
let textLayers = textObservations.flatMap { textObservation -> [CAShapeLayer] in
let characterObservations = textObservation.characterObservations
let characterLayers: [PhotoEditingObservationDebugLayer]
if isDetectedCharactersOverlayEnabled {
characterLayers = characterObservations.map { observation -> PhotoEditingObservationDebugLayer in
PhotoEditingObservationDebugLayer(fillColor: .systemBlue, frame: bounds, path: observation.bounds.path)
}
} else { characterLayers = [] }

if Defaults.Value(key: .showDetectedTextOverlay).wrappedValue {
let textLayer = PhotoEditingObservationDebugLayer(fillColor: .systemRed, frame: bounds, path: CGPath(rect: textObservation.bounds.boundingBox, transform: nil))

return characterLayers + [textLayer]
} else { return characterLayers }
}

let calculator = PhotoEditingObservationCalculator(detectedTextObservations: textObservations, recognizedTextObservations: recognizedTextObservations)
let calculatedObservations = await calculator.calculatedObservations
let wordCharacterLayers: [PhotoEditingObservationDebugLayer]
if isCalculatedOverlayEnabled {
wordCharacterLayers = calculatedObservations.map { (calculatedObservation: CharacterObservation) -> PhotoEditingObservationDebugLayer in
PhotoEditingObservationDebugLayer(fillColor: .systemGreen, frame: bounds, path: calculatedObservation.bounds.path)
}
} else { wordCharacterLayers = [] }

return textLayers + wordLayers + wordCharacterLayers
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright © 2019 Cocoatype, LLC. All rights reserved.

import AutoRedactionsUI
import DebugOverlay
import Defaults
import Detections
import Exporting
Expand Down Expand Up @@ -500,6 +501,11 @@ public class PhotoEditingViewController: UIViewController, UIScrollViewDelegate,
}
}

@objc @MainActor public func showDebugPreferences(_ sender: Any) {
guard #available(iOS 15, *) else { return }
present(OverlayPreferencesHostingController(), animated: true)
}

// MARK: Boilerplate

// tuBrute by @AdamWulf on 2024-04-29
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct ActionSet {
// 🧑‍💻👋👋👋🧑‍💻🏠🕖🤣💜😍💜😍🕙😒😒😒🕙👋😍🤣 by @Eskeminha on 2024-05-03
// the standard set of trailing navigation items
@ToolbarBuilder private var 🧑‍💻👋👋👋🧑‍💻🏠🕖🤣💜😍💜😍🕙😒😒😒🕙👋😍🤣: [UIBarButtonItem] {
if FeatureFlag.shouldShowDebugOverlay { DebugPreferencesBarButtonItem(target: target) }

ShareBarButtonItem(target: target)

SeekBarButtonItem(target: target)
Expand Down
Loading

0 comments on commit ef0811b

Please sign in to comment.