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.
		
		
		
		
		
			
		
			
				
	
	
		
			182 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			182 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import UIKit
 | 
						|
import SessionUtilitiesKit
 | 
						|
 | 
						|
public extension UIContextualAction {
 | 
						|
    private static var lookupMap: Atomic<[Int: [String: [Int: ThemeValue]]]> = Atomic([:])
 | 
						|
    
 | 
						|
    enum Side: Int {
 | 
						|
        case leading
 | 
						|
        case trailing
 | 
						|
        
 | 
						|
        func key(for indexPath: IndexPath) -> String {
 | 
						|
            return "\(indexPath.section)-\(indexPath.row)-\(rawValue)"
 | 
						|
        }
 | 
						|
        
 | 
						|
        init?(for view: UIView) {
 | 
						|
            guard view.frame.minX == 0 else {
 | 
						|
                self = .trailing
 | 
						|
                return
 | 
						|
            }
 | 
						|
            
 | 
						|
            self = .leading
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    convenience init(
 | 
						|
        title: String? = nil,
 | 
						|
        icon: UIImage? = nil,
 | 
						|
        iconHeight: CGFloat = Values.mediumFontSize,
 | 
						|
        themeTintColor: ThemeValue = .white,
 | 
						|
        themeBackgroundColor: ThemeValue,
 | 
						|
        side: Side,
 | 
						|
        actionIndex: Int,
 | 
						|
        indexPath: IndexPath,
 | 
						|
        tableView: UITableView,
 | 
						|
        handler: @escaping UIContextualAction.Handler
 | 
						|
    ) {
 | 
						|
        self.init(style: .normal, title: title, handler: handler)
 | 
						|
        self.image = UIContextualAction
 | 
						|
            .imageWith(
 | 
						|
                title: title,
 | 
						|
                icon: icon,
 | 
						|
                iconHeight: iconHeight,
 | 
						|
                themeTintColor: themeTintColor
 | 
						|
            )?
 | 
						|
            .withRenderingMode(.alwaysTemplate)
 | 
						|
        self.themeBackgroundColor = themeBackgroundColor
 | 
						|
        
 | 
						|
        UIContextualAction.lookupMap.mutate {
 | 
						|
            $0[tableView.hashValue] = ($0[tableView.hashValue] ?? [:])
 | 
						|
                .setting(
 | 
						|
                    side.key(for: indexPath),
 | 
						|
                    (($0[tableView.hashValue] ?? [:])[side.key(for: indexPath)] ?? [:])
 | 
						|
                        .setting(actionIndex, themeTintColor)
 | 
						|
                )
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    private static func imageWith(
 | 
						|
        title: String?,
 | 
						|
        icon: UIImage?,
 | 
						|
        iconHeight: CGFloat,
 | 
						|
        themeTintColor: ThemeValue
 | 
						|
    ) -> UIImage? {
 | 
						|
        let stackView: UIStackView = UIStackView()
 | 
						|
        stackView.axis = .vertical
 | 
						|
        stackView.alignment = .center
 | 
						|
        stackView.spacing = 4
 | 
						|
        
 | 
						|
        if let icon: UIImage = icon {
 | 
						|
            let aspectRatio: CGFloat = (icon.size.width / icon.size.height)
 | 
						|
            let imageView: UIImageView = UIImageView(image: icon)
 | 
						|
            imageView.frame = CGRect(x: 0, y: 0, width: (iconHeight * aspectRatio), height: iconHeight)
 | 
						|
            imageView.contentMode = .scaleAspectFit
 | 
						|
            imageView.themeTintColor = themeTintColor
 | 
						|
            stackView.addArrangedSubview(imageView)
 | 
						|
        }
 | 
						|
        
 | 
						|
        if let title: String = title {
 | 
						|
            let label: UILabel = UILabel()
 | 
						|
            label.font = .systemFont(ofSize: Values.smallFontSize)
 | 
						|
            label.text = title
 | 
						|
            label.textAlignment = .center
 | 
						|
            label.themeTextColor = themeTintColor
 | 
						|
            label.minimumScaleFactor = 0.75
 | 
						|
            label.numberOfLines = (title.components(separatedBy: " ").count > 1 ? 2 : 1)
 | 
						|
            label.frame = CGRect(
 | 
						|
                origin: .zero,
 | 
						|
                // Note: It looks like there is a semi-max width of 68px for images in the swipe actions
 | 
						|
                // if the image ends up larger then there an odd behaviour can occur where 8/10 times the
 | 
						|
                // image is scaled down to fit, but ocassionally (primarily if you hide the action and
 | 
						|
                // immediately swipe to show it again once the cell hits the edge of the screen) the image
 | 
						|
                // won't be scaled down but will be full size - appearing as if two different images are used
 | 
						|
                size: label.sizeThatFits(CGSize(width: 68, height: 999))
 | 
						|
            )
 | 
						|
            label.set(.width, to: label.frame.width)
 | 
						|
            
 | 
						|
            stackView.addArrangedSubview(label)
 | 
						|
        }
 | 
						|
        
 | 
						|
        stackView.frame = CGRect(
 | 
						|
            origin: .zero,
 | 
						|
            size: stackView.systemLayoutSizeFitting(CGSize(width: 999, height: 999))
 | 
						|
        )
 | 
						|
        
 | 
						|
        // Based on https://stackoverflow.com/a/41288197/1118398
 | 
						|
        let renderFormat: UIGraphicsImageRendererFormat = UIGraphicsImageRendererFormat()
 | 
						|
        renderFormat.scale = UIScreen.main.scale
 | 
						|
        
 | 
						|
        let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(
 | 
						|
            size: stackView.bounds.size,
 | 
						|
            format: renderFormat
 | 
						|
        )
 | 
						|
        return renderer.image { rendererContext in
 | 
						|
            stackView.layer.render(in: rendererContext.cgContext)
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    private static func firstSubviewOfType<T>(in superview: UIView) -> T? {
 | 
						|
        guard !(superview is T) else { return superview as? T }
 | 
						|
        guard !superview.subviews.isEmpty else { return nil }
 | 
						|
        
 | 
						|
        for subview in superview.subviews {
 | 
						|
            if let result: T = firstSubviewOfType(in: subview) {
 | 
						|
                return result
 | 
						|
            }
 | 
						|
        }
 | 
						|
        
 | 
						|
        return nil
 | 
						|
    }
 | 
						|
    
 | 
						|
    static func willBeginEditing(indexPath: IndexPath, tableView: UITableView) {
 | 
						|
        guard
 | 
						|
            let targetCell: UITableViewCell = tableView.cellForRow(at: indexPath),
 | 
						|
            targetCell.superview != tableView,
 | 
						|
            let targetSuperview: UIView = targetCell.superview?
 | 
						|
                .subviews
 | 
						|
                .filter({ $0 != targetCell })
 | 
						|
                .first,
 | 
						|
            let side: Side = Side(for: targetSuperview),
 | 
						|
            let themeMap: [Int: ThemeValue] = UIContextualAction.lookupMap.wrappedValue
 | 
						|
                .getting(tableView.hashValue)?
 | 
						|
                .getting(side.key(for: indexPath)),
 | 
						|
            targetSuperview.subviews.count == themeMap.count
 | 
						|
        else { return }
 | 
						|
        
 | 
						|
        let targetViews: [UIImageView] = targetSuperview.subviews
 | 
						|
            .compactMap { subview in firstSubviewOfType(in: subview) }
 | 
						|
        
 | 
						|
        guard targetViews.count == themeMap.count else { return }
 | 
						|
        
 | 
						|
        // Set the imageView and background colours (so they change correctly when the theme changes)
 | 
						|
        targetViews.enumerated().forEach { index, targetView in
 | 
						|
            guard let themeTintColor: ThemeValue = themeMap[index] else { return }
 | 
						|
            
 | 
						|
            targetView.themeTintColor = themeTintColor
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    static func didEndEditing(indexPath: IndexPath?, tableView: UITableView) {
 | 
						|
        guard let indexPath: IndexPath = indexPath else { return }
 | 
						|
        
 | 
						|
        let leadingKey: String = Side.leading.key(for: indexPath)
 | 
						|
        let trailingKey: String = Side.trailing.key(for: indexPath)
 | 
						|
        
 | 
						|
        guard
 | 
						|
            UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[leadingKey] != nil ||
 | 
						|
            UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[trailingKey] != nil
 | 
						|
        else { return }
 | 
						|
        
 | 
						|
        UIContextualAction.lookupMap.mutate {
 | 
						|
            $0[tableView.hashValue]?[leadingKey] = nil
 | 
						|
            $0[tableView.hashValue]?[trailingKey] = nil
 | 
						|
            
 | 
						|
            if $0[tableView.hashValue]?.isEmpty == true {
 | 
						|
                $0[tableView.hashValue] = nil
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |