mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			213 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			213 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Swift
		
	
// 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<CGFloat?>) {
 | 
						|
        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<String?>) -> 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 hideListRowSeparator() -> some View {
 | 
						|
        if #available(iOS 15.0, *) {
 | 
						|
            return listRowSeparator(.hidden)
 | 
						|
        } else {
 | 
						|
            return onAppear {
 | 
						|
                UITableView.appearance().separatorStyle = .none
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    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<Value> {
 | 
						|
        Binding(
 | 
						|
            get: { self.wrappedValue },
 | 
						|
            set: { newValue in
 | 
						|
                handler(newValue)
 | 
						|
                self.wrappedValue = newValue
 | 
						|
            }
 | 
						|
        )
 | 
						|
    }
 | 
						|
}
 |