// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import SwiftUI import UIKit import SessionUtilitiesKit struct ViewControllerHolder { weak var value: UIViewController? } struct ViewControllerKey: EnvironmentKey { static var defaultValue: ViewControllerHolder { return ViewControllerHolder(value: Singleton.appContext.mainWindow?.rootViewController) } } extension EnvironmentValues { public var viewController: UIViewController? { get { return self[ViewControllerKey.self].value } set { self[ViewControllerKey.self].value = newValue } } } public struct UIView_SwiftUI: UIViewRepresentable { public typealias UIViewType = UIView private let view: UIView public init(view: UIView) { self.view = view } public func makeUIView(context: Context) -> UIView { return self.view } public func updateUIView(_ uiView: UIView, context: Context) { uiView.layoutIfNeeded() } } // MARK: MaxWidthEqualizer /// PreferenceKey to report the max width of the view. struct MaxWidthPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0.0 // We `reduce` to just take the max value from all values reported. static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) } } /// Convenience view modifier that observe its size, and notify the value back to parent view via `MaxWidthPreferenceKey`. public struct MaxWidthNotify: ViewModifier { /// We embed a transparent background view, to the current view to get the size via `GeometryReader`. /// The `MaxWidthPreferenceKey` will be reported, when the frame of this view is updated. private var sizeView: some View { GeometryReader { geometry in Color.clear.preference(key: MaxWidthPreferenceKey.self, value: geometry.frame(in: .global).size.width) } } public func body(content: Content) -> some View { content.background(sizeView) } } /// Convenience modifier to use in the parent view to observe `MaxWidthPreferenceKey` from children, and bind the value to `$width`. public struct MaxWidthEqualizer: ViewModifier { @Binding var width: CGFloat? public static var notify: MaxWidthNotify { MaxWidthNotify() } public init(width: Binding) { self._width = width } public func body(content: Content) -> some View { content.onPreferenceChange(MaxWidthPreferenceKey.self) { value in let oldWidth: CGFloat = width ?? 0 if value > oldWidth { width = value } } } } public struct Line: View { let color: ThemeValue public init(color: ThemeValue) { self.color = color } public var body: some View { Rectangle() .fill(themeColor: color) .frame(height: 1) } } struct EdgeBorder: Shape { var width: CGFloat var edges: [Edge] func path(in rect: CGRect) -> Path { var path = Path() for edge in edges { var x: CGFloat { switch edge { case .top, .bottom, .leading: return rect.minX case .trailing: return rect.maxX - width } } var y: CGFloat { switch edge { case .top, .leading, .trailing: return rect.minY case .bottom: return rect.maxY - width } } var w: CGFloat { switch edge { case .top, .bottom: return rect.width case .leading, .trailing: return self.width } } var h: CGFloat { switch edge { case .top, .bottom: return self.width case .leading, .trailing: return rect.height } } path.addPath(Path(CGRect(x: x, y: y, width: w, height: h))) } return path } } extension View { public func border(width: CGFloat, edges: [Edge], color: ThemeValue) -> some View { overlay( EdgeBorder(width: width, edges: edges) .foregroundColor(themeColor: color) ) } public func toastView(message: Binding) -> some View { self.modifier(ToastModifier(message: message)) } public func textViewTransparentScrolling() -> some View { if #available(iOS 16.0, *) { return scrollContentBackground(.hidden) } else { return onAppear { UITextView.appearance().backgroundColor = .clear } } } public func transparentListBackground() -> some View { if #available(iOS 16.0, *) { return scrollContentBackground(.hidden) } else { return onAppear { UITableView.appearance().backgroundColor = .clear } } } public func accessibility(_ accessibility: Accessibility) -> some View { if #available(iOSApplicationExtension 14.0, *) { guard let identifier = accessibility.identifier else { return self } return accessibilityIdentifier(identifier).accessibilityLabel(accessibility.label ?? "") } else { return self } } } extension Binding { public func onChange(_ handler: @escaping (Value) -> Void) -> Binding { Binding( get: { self.wrappedValue }, set: { newValue in handler(newValue) self.wrappedValue = newValue } ) } }