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.
		
		
		
		
		
			
		
			
				
	
	
		
			264 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			264 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Swift
		
	
| import PromiseKit
 | |
| import NVActivityIndicatorView
 | |
| import SessionUIKit
 | |
| 
 | |
| final class OpenGroupSuggestionGrid : UIView, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
 | |
|     private let maxWidth: CGFloat
 | |
|     private var rooms: [OpenGroupAPIV2.Info] = [] { didSet { update() } }
 | |
|     private var heightConstraint: NSLayoutConstraint!
 | |
|     var delegate: OpenGroupSuggestionGridDelegate?
 | |
|     
 | |
|     // MARK: UI Components
 | |
|     private lazy var layout: UICollectionViewFlowLayout = {
 | |
|         let result = UICollectionViewFlowLayout()
 | |
|         result.minimumLineSpacing = 0
 | |
|         result.minimumInteritemSpacing = 0
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var collectionView: UICollectionView = {
 | |
|         let result = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
 | |
|         result.register(Cell.self, forCellWithReuseIdentifier: Cell.identifier)
 | |
|         result.backgroundColor = .clear
 | |
|         result.isScrollEnabled = false
 | |
|         result.dataSource = self
 | |
|         result.delegate = self
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var spinner: NVActivityIndicatorView = {
 | |
|         let result = NVActivityIndicatorView(frame: CGRect.zero, type: .circleStrokeSpin, color: Colors.text, padding: nil)
 | |
|         result.set(.width, to: OpenGroupSuggestionGrid.cellHeight)
 | |
|         result.set(.height, to: OpenGroupSuggestionGrid.cellHeight)
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var errorView: UIView = {
 | |
|         let result: UIView = UIView()
 | |
|         result.isHidden = true
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var errorImageView: UIImageView = {
 | |
|         let result: UIImageView = UIImageView(image: #imageLiteral(resourceName: "warning").withRenderingMode(.alwaysTemplate))
 | |
|         result.tintColor = Colors.destructive
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var errorTitleLabel: UILabel = {
 | |
|         let result: UILabel = UILabel()
 | |
|         result.font = UIFont.systemFont(ofSize: Values.mediumFontSize, weight: .medium)
 | |
|         result.text = "DEFAULT_OPEN_GROUP_LOAD_ERROR_TITLE".localized()
 | |
|         result.textColor = Colors.text
 | |
|         result.textAlignment = .center
 | |
|         result.numberOfLines = 0
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var errorSubtitleLabel: UILabel = {
 | |
|         let result: UILabel = UILabel()
 | |
|         result.font = UIFont.systemFont(ofSize: Values.smallFontSize, weight: .medium)
 | |
|         result.text = "DEFAULT_OPEN_GROUP_LOAD_ERROR_SUBTITLE".localized()
 | |
|         result.textColor = Colors.text
 | |
|         result.textAlignment = .center
 | |
|         result.numberOfLines = 0
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     // MARK: Settings
 | |
|     private static let cellHeight: CGFloat = 40
 | |
|     private static let separatorWidth = 1 / UIScreen.main.scale
 | |
|     
 | |
|     // MARK: Initialization
 | |
|     init(maxWidth: CGFloat) {
 | |
|         self.maxWidth = maxWidth
 | |
|         super.init(frame: CGRect.zero)
 | |
|         initialize()
 | |
|     }
 | |
|     
 | |
|     override init(frame: CGRect) {
 | |
|         preconditionFailure("Use init(maxWidth:) instead.")
 | |
|     }
 | |
|     
 | |
|     required init?(coder: NSCoder) {
 | |
|         preconditionFailure("Use init(maxWidth:) instead.")
 | |
|     }
 | |
|     
 | |
|     private func initialize() {
 | |
|         addSubview(collectionView)
 | |
|         collectionView.pin(to: self)
 | |
|         
 | |
|         addSubview(spinner)
 | |
|         spinner.pin(.top, to: .top, of: self)
 | |
|         spinner.center(.horizontal, in: self)
 | |
|         spinner.startAnimating()
 | |
|         
 | |
|         addSubview(errorView)
 | |
|         errorView.pin(.top, to: .top, of: self, withInset: 10)
 | |
|         errorView.pin( [HorizontalEdge.leading, HorizontalEdge.trailing], to: self)
 | |
|         
 | |
|         errorView.addSubview(errorImageView)
 | |
|         errorImageView.pin(.top, to: .top, of: errorView)
 | |
|         errorImageView.center(.horizontal, in: errorView)
 | |
|         errorImageView.set(.width, to: 60)
 | |
|         errorImageView.set(.height, to: 60)
 | |
|         
 | |
|         errorView.addSubview(errorTitleLabel)
 | |
|         errorTitleLabel.pin(.top, to: .bottom, of: errorImageView, withInset: 10)
 | |
|         errorTitleLabel.center(.horizontal, in: errorView)
 | |
|         
 | |
|         errorView.addSubview(errorSubtitleLabel)
 | |
|         errorSubtitleLabel.pin(.top, to: .bottom, of: errorTitleLabel, withInset: 20)
 | |
|         errorSubtitleLabel.center(.horizontal, in: errorView)
 | |
|         
 | |
|         heightConstraint = set(.height, to: OpenGroupSuggestionGrid.cellHeight)
 | |
|         widthAnchor.constraint(greaterThanOrEqualToConstant: OpenGroupSuggestionGrid.cellHeight).isActive = true
 | |
|         
 | |
|         OpenGroupAPIV2.getDefaultRoomsIfNeeded()
 | |
|             .done { [weak self] rooms in
 | |
|                 self?.rooms = rooms
 | |
|             }
 | |
|             .catch { [weak self] _ in
 | |
|                 self?.update()
 | |
|             }
 | |
|     }
 | |
|     
 | |
|     // MARK: Updating
 | |
|     private func update() {
 | |
|         spinner.stopAnimating()
 | |
|         spinner.isHidden = true
 | |
|         let roomCount = min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2)
 | |
|         let height = OpenGroupSuggestionGrid.cellHeight * ceil(CGFloat(roomCount) / 2)
 | |
|         heightConstraint.constant = height
 | |
|         collectionView.reloadData()
 | |
|         errorView.isHidden = (roomCount > 0)
 | |
|     }
 | |
|     
 | |
|     // MARK: Layout
 | |
|     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
 | |
|         let cellWidth = UIDevice.current.isIPad ? maxWidth / 4 : maxWidth / 2
 | |
|         return CGSize(width: cellWidth, height: OpenGroupSuggestionGrid.cellHeight)
 | |
|     }
 | |
|     
 | |
|     // MARK: Data Source
 | |
|     func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
 | |
|         return min(rooms.count, 8) // Cap to a maximum of 8 (4 rows of 2)
 | |
|     }
 | |
|     
 | |
|     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
 | |
|         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.identifier, for: indexPath) as! Cell
 | |
|         cell.room = rooms[indexPath.item]
 | |
|         return cell
 | |
|     }
 | |
|     
 | |
|     // MARK: Interaction
 | |
|     func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
 | |
|         let room = rooms[indexPath.item]
 | |
|         delegate?.join(room)
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: Cell
 | |
| extension OpenGroupSuggestionGrid {
 | |
|     
 | |
|     fileprivate final class Cell : UICollectionViewCell {
 | |
|         var room: OpenGroupAPIV2.Info? { didSet { update() } }
 | |
|         
 | |
|         static let identifier = "OpenGroupSuggestionGridCell"
 | |
|         
 | |
|         private lazy var snContentView: UIView = {
 | |
|             let result = UIView()
 | |
|             result.backgroundColor = Colors.navigationBarBackground
 | |
|             result.set(.height, to: Cell.contentViewHeight)
 | |
|             result.layer.cornerRadius = Cell.contentViewCornerRadius
 | |
|             return result
 | |
|         }()
 | |
|         
 | |
|         private lazy var imageView: UIImageView = {
 | |
|             let result = UIImageView()
 | |
|             let size: CGFloat = 24
 | |
|             result.set(.width, to: size)
 | |
|             result.set(.height, to: size)
 | |
|             result.layer.cornerRadius = size / 2
 | |
|             result.clipsToBounds = true
 | |
|             return result
 | |
|         }()
 | |
|         
 | |
|         private lazy var label: UILabel = {
 | |
|             let result = UILabel()
 | |
|             result.textColor = Colors.text
 | |
|             result.font = .systemFont(ofSize: Values.smallFontSize)
 | |
|             result.lineBreakMode = .byTruncatingTail
 | |
|             return result
 | |
|         }()
 | |
|         
 | |
|         private static let contentViewInset: CGFloat = 4
 | |
|         private static var contentViewHeight: CGFloat { OpenGroupSuggestionGrid.cellHeight - 2 * contentViewInset }
 | |
|         private static var contentViewCornerRadius: CGFloat { contentViewHeight / 2 }
 | |
|         
 | |
|         override init(frame: CGRect) {
 | |
|             super.init(frame: frame)
 | |
|             setUpViewHierarchy()
 | |
|         }
 | |
|         
 | |
|         required init?(coder: NSCoder) {
 | |
|             super.init(coder: coder)
 | |
|             setUpViewHierarchy()
 | |
|         }
 | |
|         
 | |
|         private func setUpViewHierarchy() {
 | |
|             addSubview(snContentView)
 | |
|             let stackView = UIStackView(arrangedSubviews: [ imageView, label ])
 | |
|             stackView.axis = .horizontal
 | |
|             stackView.spacing = Values.smallSpacing
 | |
|             snContentView.addSubview(stackView)
 | |
|             stackView.center(.vertical, in: snContentView)
 | |
|             stackView.pin(.leading, to: .leading, of: snContentView, withInset: 4)
 | |
|             snContentView.trailingAnchor.constraint(greaterThanOrEqualTo: stackView.trailingAnchor, constant: Values.smallSpacing).isActive = true
 | |
|             snContentView.pin(to: self, withInset: Cell.contentViewInset)
 | |
|         }
 | |
|         
 | |
|         override func layoutSubviews() {
 | |
|             super.layoutSubviews()
 | |
|             let newPath = UIBezierPath(roundedRect: snContentView.bounds, cornerRadius: Cell.contentViewCornerRadius).cgPath
 | |
|             snContentView.layer.shadowPath = newPath
 | |
|             snContentView.layer.shadowColor = UIColor.black.cgColor
 | |
|             snContentView.layer.shadowOffset = CGSize.zero
 | |
|             snContentView.layer.shadowOpacity = isLightMode ? 0.2 : 0.6
 | |
|             snContentView.layer.shadowRadius = 2
 | |
|         }
 | |
|         
 | |
|         private func update() {
 | |
|             guard let room = room else { return }
 | |
|             let promise = OpenGroupAPIV2.getGroupImage(for: room.id, on: OpenGroupAPIV2.defaultServer)
 | |
|             
 | |
|             if let imageData: Data = promise.value {
 | |
|                 imageView.image = UIImage(data: imageData)
 | |
|                 imageView.isHidden = (imageView.image == nil)
 | |
|             }
 | |
|             else {
 | |
|                 imageView.isHidden = true
 | |
|                 
 | |
|                 _ = promise.done { [weak self] imageData in
 | |
|                     DispatchQueue.main.async {
 | |
|                         self?.imageView.image = UIImage(data: imageData)
 | |
|                         self?.imageView.isHidden = (self?.imageView.image == nil)
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             
 | |
|             label.text = room.name
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: Delegate
 | |
| protocol OpenGroupSuggestionGridDelegate {
 | |
|     
 | |
|     func join(_ room: OpenGroupAPIV2.Info)
 | |
| }
 |