<commit_before>//
//  DKAssetGroupDetailVC.swift
//  DKImagePickerController
//
//  Created by ZhangAo on 15/8/10.
//  Copyright (c) 2015年 ZhangAo. All rights reserved.
//

import UIKit
import AVFoundation
import Photos

private extension UICollectionView {
    
    func indexPathsForElements(in rect: CGRect, _ hidesCamera: Bool) -> [IndexPath] {
        let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)!
        
        if hidesCamera {
            return allLayoutAttributes.map { $0.indexPath }
        } else {
            #if swift(>=4.1)
            return allLayoutAttributes.compactMap { $0.indexPath.item == 0 ? nil : IndexPath(item: $0.indexPath.item - 1, section: $0.indexPath.section) }
            #else
            return allLayoutAttributes.flatMap { $0.indexPath.item == 0 ? nil : IndexPath(item: $0.indexPath.item - 1, section: $0.indexPath.section) }
            #endif
        }
    }
    
}

////////////////////////////////////////////////////////////

// Show all images in the asset group
open class DKAssetGroupDetailVC: UIViewController,
    UIGestureRecognizerDelegate,
    UICollectionViewDelegate, UICollectionViewDataSource, DKImageGroupDataManagerObserver, DKImagePickerControllerObserver {
    	
    public lazy var selectGroupButton: UIButton = {
        let button = UIButton()
		
        #if swift(>=4.0)
        let globalTitleColor = UINavigationBar.appearance().titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor
        let globalTitleFont = UINavigationBar.appearance().titleTextAttributes?[NSAttributedString.Key.font] as? UIFont
        #else
        let globalTitleColor = UINavigationBar.appearance().titleTextAttributes?[NSForegroundColorAttributeName] as? UIColor
        let globalTitleFont = UINavigationBar.appearance().titleTextAttributes?[NSFontAttributeName] as? UIFont
        #endif
        
        button.setTitleColor(globalTitleColor ?? UIColor.black, for: .normal)
		button.titleLabel!.font = globalTitleFont ?? UIFont.boldSystemFont(ofSize: 18.0)
		
		button.addTarget(self, action: #selector(DKAssetGroupDetailVC.showGroupSelector), for: .touchUpInside)
        return button
    }()
		
    public var selectedGroupId: String?
    internal var collectionView: UICollectionView!
    internal weak var imagePickerController: DKImagePickerController!
    private var groupListVC: DKAssetGroupListVC!
    private var hidesCamera: Bool = false
    private var footerView: UIView?
    private var headerView: UIView?
    private var currentViewSize: CGSize!
    private var registeredCellIdentifiers = Set<String>()
    private var thumbnailSize = CGSize.zero
    
    override open func viewDidLoad() {
        super.viewDidLoad()
        
        self.imagePickerController.add(observer: self)
		
		let layout = self.imagePickerController.UIDelegate.layoutForImagePickerController(self.imagePickerController).init()
		self.collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
        self.collectionView.backgroundColor = self.imagePickerController.UIDelegate.imagePickerControllerCollectionViewBackgroundColor()
        self.collectionView.allowsMultipleSelection = true
		self.collectionView.delegate = self
		self.collectionView.dataSource = self
		self.view.addSubview(self.collectionView)
		
		self.footerView = self.imagePickerController.UIDelegate.imagePickerControllerFooterView(self.imagePickerController)
		if let footerView = self.footerView {
			self.view.addSubview(footerView)
		}
      
    self.headerView = self.imagePickerController.UIDelegate.imagePickerControllerHeaderView(self.imagePickerController)
    if let headerView = self.headerView {
      self.view.addSubview(headerView)
    }
		
		self.hidesCamera = self.imagePickerController.sourceType == .photo
		self.checkPhotoPermission()
        
        if self.imagePickerController.allowSwipeToSelect && !self.imagePickerController.singleSelect {
            let swipeOutGesture: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.swiping(gesture:)))
            swipeOutGesture.delegate = self
            self.collectionView.panGestureRecognizer.require(toFail: swipeOutGesture)
            self.collectionView.addGestureRecognizer(swipeOutGesture)
        }
    }
    
    override open func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        self.updateCachedAssets()
    }
    
    override open func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
        if let currentViewSize = self.currentViewSize, currentViewSize.equalTo(self.view.bounds.size) {
            return
        } else {
            currentViewSize = self.view.bounds.size
        }
        
        self.collectionView?.collectionViewLayout.invalidateLayout()
    }
	
	override open func viewDidLayoutSubviews() {
		super.viewDidLayoutSubviews()
		
    configureAccessoryViews()
  }
  
  func configureAccessoryViews() {
    var footerViewFrame = CGRect.zero
    var headerViewFrame = CGRect.zero
    if let footerView = self.footerView {
      footerViewFrame = CGRect(x: 0,
                               y: self.view.bounds.height - footerView.bounds.height,
                               width: self.view.bounds.width,
                               height: footerView.bounds.height)
      footerView.frame = footerViewFrame
    }
    if let headerView = self.headerView  {
      if #available(iOS 11.0, *) {
        headerViewFrame = CGRect(x: 0,
                                 y: self.view.safeAreaInsets.top,
                                 width: self.view.bounds.width,
                                 height: headerView.bounds.height)
      } else {
        headerViewFrame = CGRect(x: 0,
                                 y: 0,
                                 width: self.view.bounds.width,
                                 height: headerView.bounds.height)
      }
      headerView.frame = headerViewFrame
    }
    
    if #available(iOS 11.0, *) {
      // Handling Safe Areas for iOS 11
      self.collectionView.frame = CGRect(x: 0,
                                         y: self.view.safeAreaInsets.top + headerViewFrame.height,
                                         width: self.view.bounds.width,
                                         height: self.view.bounds.height - footerViewFrame.height - headerViewFrame.height - self.view.safeAreaInsets.top)
    } else {
      self.collectionView.frame = CGRect(x: 0,
                                         y: headerViewFrame.height,
                                         width: self.view.bounds.width,
                                         height: self.view.bounds.height - footerViewFrame.height - headerViewFrame.height)
    }
  }
	
	internal func checkPhotoPermission() {
		func photoDenied() {
            let permissionColors = self.imagePickerController.permissionViewColors
            self.view.addSubview(DKPermissionView.permissionView(.photo, withColors: permissionColors))
            self.view.backgroundColor = permissionColors.backgroundColor
			self.collectionView?.isHidden = true
		}
        
        func setup() {
            self.resetCachedAssets()
            self.imagePickerController.groupDataManager.add(observer: self)
            self.groupListVC = DKAssetGroupListVC(groupDataManager: self.imagePickerController.groupDataManager,
                                                  defaultAssetGroup: self.imagePickerController.defaultAssetGroup,
                                                  selectedGroupDidChangeBlock: { [unowned self] (groupId) in
                                                    self.selectAssetGroup(groupId)
            })
            self.groupListVC.showsEmptyAlbums = self.imagePickerController.showsEmptyAlbums
            self.groupListVC.loadGroups()
        }
        
		DKImageDataManager.checkPhotoPermission { granted in
			granted ? setup() : photoDenied()
		}
	}
	
    func selectAssetGroup(_ groupId: String?) {
        if self.selectedGroupId == groupId {
            self.updateTitleView()
            return
        }
        
        self.selectedGroupId = groupId
		self.updateTitleView()
		self.collectionView!.reloadData()
    }
    
	open func updateTitleView() {
        guard let selectedGroupId = self.selectedGroupId else { return }
        
        let group = self.imagePickerController.groupDataManager.fetchGroup(with: selectedGroupId)
		self.title = group.groupName
		
		let groupsCount = self.imagePickerController.groupDataManager.groupIds?.count ?? 0
		self.selectGroupButton.setTitle(group.groupName + (groupsCount > 1 ? "  \u{25be}" : "" ), for: .normal)
		self.selectGroupButton.sizeToFit()
		self.selectGroupButton.isEnabled = groupsCount > 1
		
		self.navigationItem.titleView = self.selectGroupButton
	}
    
    @objc func showGroupSelector() {
        DKPopoverViewController.popoverViewController(self.groupListVC, fromView: self.selectGroupButton)
    }
    
    func fetchAsset(for index: Int) -> DKAsset? {
        guard let selectedGroupId = self.selectedGroupId else { return nil }
        
        var assetIndex = index
        
        if !self.hidesCamera && index == 0 {
            return nil
        }
        assetIndex = (index - (self.hidesCamera ? 0 : 1))
        
        let group = self.imagePickerController.groupDataManager.fetchGroup(with: selectedGroupId)
        return self.imagePickerController.groupDataManager.fetchAsset(group, index: assetIndex)
    }
    
    // select an asset at a specific index
    public func selectAsset(atIndex indexPath: IndexPath) {
        guard let cell = self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell
            , let asset = cell.asset else {
            return
        }
        
        if !self.imagePickerController.contains(asset: asset) {
            self.imagePickerController.select(asset: asset)
        }
    }
    
    public func deselectAsset(atIndex indexPath: IndexPath) {
        guard let cell = self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell
            , let asset = cell.asset else {
                return
        }

        self.imagePickerController.deselect(asset: asset)
    }
    
    public func adjustAssetIndex(_ index: Int) -> Int {
        return self.hidesCamera ? index : index + 1
    }
    
    public func scrollIndexPathToVisible(_ indexPath: IndexPath) {
        if let cellFrame = self.collectionView.collectionViewLayout.layoutAttributesForItem(at: indexPath)?.frame {
            self.collectionView.scrollRectToVisible(cellFrame, animated: false)
        }
    }
    
    public func thumbnailImageView(for indexPath: IndexPath) -> UIImageView? {
        if let cell = self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell {
            return cell.thumbnailImageView
        } else {
            self.collectionView.reloadItems(at: [indexPath])
            
            return (self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell)?
                .thumbnailImageView
        }
    }

    func isCameraCell(indexPath: IndexPath) -> Bool {
        return indexPath.row == 0 && !self.hidesCamera
    }

    // MARK: - Swiping
    
    private var fromIndexPath: IndexPath? = nil
    private var swipingIndexPathes = Set<Int>()
    private var swipingToSelect = true
    
    // use the swiping gesture to select the currently swiping cell.
    @objc private func swiping(gesture: UIPanGestureRecognizer) {
        let location = gesture.location(in: self.collectionView)
        
        switch gesture.state {
        case .possible:
            break
        case .began:
            if let indexPath = self.collectionView.indexPathForItem(at: location)
                , let cell = self.collectionView.cellForItem(at: indexPath) {
                self.fromIndexPath = indexPath
                self.swipingToSelect = !cell.isSelected
            }
        case .changed:
            if let toIndexPath = self.collectionView.indexPathForItem(at: location)
                , let fromIndexPath = self.fromIndexPath {
                let begin = min(fromIndexPath.row, toIndexPath.row)
                let end = max(fromIndexPath.row, toIndexPath.row)
                
                var currentSwipingIndexPathes = Set<Int>()
                for i in begin...end {
                    currentSwipingIndexPathes.insert(i)
                    self.swipingIndexPathes.remove(i)
                    
                    if self.swipingToSelect {
                        self.selectAsset(atIndex: IndexPath(row: i, section: 0))
                    } else {
                        self.deselectAsset(atIndex: IndexPath(row: i, section: 0))
                    }
                }
                
                for i in self.swipingIndexPathes {
                    if self.swipingToSelect {
                        self.deselectAsset(atIndex: IndexPath(row: i, section: 0))
                    } else {
                        self.selectAsset(atIndex: IndexPath(row: i, section: 0))
                    }
                }
                self.swipingIndexPathes = currentSwipingIndexPathes
            }
        case .ended:
            self.swipingIndexPathes.removeAll()
            self.fromIndexPath = nil
        case .cancelled:
            self.swipingIndexPathes.removeAll()
            self.fromIndexPath = nil
        case .failed:
            self.swipingIndexPathes.removeAll()
            self.fromIndexPath = nil
        }
    }
    
    // MARK: - UIGestureRecognizerDelegate
    
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
    
    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else { return false }<commit_msg>In swiping selection, users can put their fingers on the top/bottom of the screen to make the view auto-scrolling up/down.<commit_after>//
//  DKAssetGroupDetailVC.swift
//  DKImagePickerController
//
//  Created by ZhangAo on 15/8/10.
//  Copyright (c) 2015年 ZhangAo. All rights reserved.
//

import UIKit
import AVFoundation
import Photos

private extension UICollectionView {
    
    func indexPathsForElements(in rect: CGRect, _ hidesCamera: Bool) -> [IndexPath] {
        let allLayoutAttributes = collectionViewLayout.layoutAttributesForElements(in: rect)!
        
        if hidesCamera {
            return allLayoutAttributes.map { $0.indexPath }
        } else {
            #if swift(>=4.1)
            return allLayoutAttributes.compactMap { $0.indexPath.item == 0 ? nil : IndexPath(item: $0.indexPath.item - 1, section: $0.indexPath.section) }
            #else
            return allLayoutAttributes.flatMap { $0.indexPath.item == 0 ? nil : IndexPath(item: $0.indexPath.item - 1, section: $0.indexPath.section) }
            #endif
        }
    }
    
}

////////////////////////////////////////////////////////////

// Show all images in the asset group
open class DKAssetGroupDetailVC: UIViewController,
    UIGestureRecognizerDelegate,
    UICollectionViewDelegate, UICollectionViewDataSource, DKImageGroupDataManagerObserver, DKImagePickerControllerObserver {
    	
    public lazy var selectGroupButton: UIButton = {
        let button = UIButton()
		
        #if swift(>=4.0)
        let globalTitleColor = UINavigationBar.appearance().titleTextAttributes?[NSAttributedString.Key.foregroundColor] as? UIColor
        let globalTitleFont = UINavigationBar.appearance().titleTextAttributes?[NSAttributedString.Key.font] as? UIFont
        #else
        let globalTitleColor = UINavigationBar.appearance().titleTextAttributes?[NSForegroundColorAttributeName] as? UIColor
        let globalTitleFont = UINavigationBar.appearance().titleTextAttributes?[NSFontAttributeName] as? UIFont
        #endif
        
        button.setTitleColor(globalTitleColor ?? UIColor.black, for: .normal)
		button.titleLabel!.font = globalTitleFont ?? UIFont.boldSystemFont(ofSize: 18.0)
		
		button.addTarget(self, action: #selector(DKAssetGroupDetailVC.showGroupSelector), for: .touchUpInside)
        return button
    }()
		
    public var selectedGroupId: String?
    internal var collectionView: UICollectionView!
    internal weak var imagePickerController: DKImagePickerController!
    private var groupListVC: DKAssetGroupListVC!
    private var hidesCamera: Bool = false
    private var footerView: UIView?
    private var headerView: UIView?
    private var currentViewSize: CGSize!
    private var registeredCellIdentifiers = Set<String>()
    private var thumbnailSize = CGSize.zero
    
    override open func viewDidLoad() {
        super.viewDidLoad()
        
        self.imagePickerController.add(observer: self)
		
		let layout = self.imagePickerController.UIDelegate.layoutForImagePickerController(self.imagePickerController).init()
		self.collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
        self.collectionView.backgroundColor = self.imagePickerController.UIDelegate.imagePickerControllerCollectionViewBackgroundColor()
        self.collectionView.allowsMultipleSelection = true
		self.collectionView.delegate = self
		self.collectionView.dataSource = self
		self.view.addSubview(self.collectionView)
		
		self.footerView = self.imagePickerController.UIDelegate.imagePickerControllerFooterView(self.imagePickerController)
		if let footerView = self.footerView {
			self.view.addSubview(footerView)
		}
      
    self.headerView = self.imagePickerController.UIDelegate.imagePickerControllerHeaderView(self.imagePickerController)
    if let headerView = self.headerView {
      self.view.addSubview(headerView)
    }
		
		self.hidesCamera = self.imagePickerController.sourceType == .photo
		self.checkPhotoPermission()
        
        if self.imagePickerController.allowSwipeToSelect && !self.imagePickerController.singleSelect {
            let swipeOutGesture: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.swiping(gesture:)))
            swipeOutGesture.delegate = self
            self.collectionView.panGestureRecognizer.require(toFail: swipeOutGesture)
            self.collectionView.addGestureRecognizer(swipeOutGesture)
        }
    }
    
    override open func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        self.updateCachedAssets()
    }
    
    override open func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        
        if let currentViewSize = self.currentViewSize, currentViewSize.equalTo(self.view.bounds.size) {
            return
        } else {
            self.currentViewSize = self.view.bounds.size
        }
        
        self.collectionView?.collectionViewLayout.invalidateLayout()
    }
	
	override open func viewDidLayoutSubviews() {
		super.viewDidLayoutSubviews()
		
    configureAccessoryViews()
  }
  
  func configureAccessoryViews() {
    var footerViewFrame = CGRect.zero
    var headerViewFrame = CGRect.zero
    if let footerView = self.footerView {
      footerViewFrame = CGRect(x: 0,
                               y: self.view.bounds.height - footerView.bounds.height,
                               width: self.view.bounds.width,
                               height: footerView.bounds.height)
      footerView.frame = footerViewFrame
    }
    if let headerView = self.headerView  {
      if #available(iOS 11.0, *) {
        headerViewFrame = CGRect(x: 0,
                                 y: self.view.safeAreaInsets.top,
                                 width: self.view.bounds.width,
                                 height: headerView.bounds.height)
      } else {
        headerViewFrame = CGRect(x: 0,
                                 y: 0,
                                 width: self.view.bounds.width,
                                 height: headerView.bounds.height)
      }
      headerView.frame = headerViewFrame
    }
    
    if #available(iOS 11.0, *) {
      // Handling Safe Areas for iOS 11
      self.collectionView.frame = CGRect(x: 0,
                                         y: self.view.safeAreaInsets.top + headerViewFrame.height,
                                         width: self.view.bounds.width,
                                         height: self.view.bounds.height - footerViewFrame.height - headerViewFrame.height - self.view.safeAreaInsets.top)
    } else {
      self.collectionView.frame = CGRect(x: 0,
                                         y: headerViewFrame.height,
                                         width: self.view.bounds.width,
                                         height: self.view.bounds.height - footerViewFrame.height - headerViewFrame.height)
    }
  }
	
	internal func checkPhotoPermission() {
		func photoDenied() {
            let permissionColors = self.imagePickerController.permissionViewColors
            self.view.addSubview(DKPermissionView.permissionView(.photo, withColors: permissionColors))
            self.view.backgroundColor = permissionColors.backgroundColor
			self.collectionView?.isHidden = true
		}
        
        func setup() {
            self.resetCachedAssets()
            self.imagePickerController.groupDataManager.add(observer: self)
            self.groupListVC = DKAssetGroupListVC(groupDataManager: self.imagePickerController.groupDataManager,
                                                  defaultAssetGroup: self.imagePickerController.defaultAssetGroup,
                                                  selectedGroupDidChangeBlock: { [unowned self] (groupId) in
                                                    self.selectAssetGroup(groupId)
            })
            self.groupListVC.showsEmptyAlbums = self.imagePickerController.showsEmptyAlbums
            self.groupListVC.loadGroups()
        }
        
		DKImageDataManager.checkPhotoPermission { granted in
			granted ? setup() : photoDenied()
		}
	}
	
    func selectAssetGroup(_ groupId: String?) {
        if self.selectedGroupId == groupId {
            self.updateTitleView()
            return
        }
        
        self.selectedGroupId = groupId
		self.updateTitleView()
		self.collectionView!.reloadData()
    }
    
	open func updateTitleView() {
        guard let selectedGroupId = self.selectedGroupId else { return }
        
        let group = self.imagePickerController.groupDataManager.fetchGroup(with: selectedGroupId)
		self.title = group.groupName
		
		let groupsCount = self.imagePickerController.groupDataManager.groupIds?.count ?? 0
		self.selectGroupButton.setTitle(group.groupName + (groupsCount > 1 ? "  \u{25be}" : "" ), for: .normal)
		self.selectGroupButton.sizeToFit()
		self.selectGroupButton.isEnabled = groupsCount > 1
		
		self.navigationItem.titleView = self.selectGroupButton
	}
    
    @objc func showGroupSelector() {
        DKPopoverViewController.popoverViewController(self.groupListVC, fromView: self.selectGroupButton)
    }
    
    func fetchAsset(for index: Int) -> DKAsset? {
        guard let selectedGroupId = self.selectedGroupId else { return nil }
        
        var assetIndex = index
        
        if !self.hidesCamera && index == 0 {
            return nil
        }
        assetIndex = (index - (self.hidesCamera ? 0 : 1))
        
        let group = self.imagePickerController.groupDataManager.fetchGroup(with: selectedGroupId)
        return self.imagePickerController.groupDataManager.fetchAsset(group, index: assetIndex)
    }
    
    // select an asset at a specific index
    public func selectAsset(atIndex indexPath: IndexPath) {
        guard let cell = self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell
            , let asset = cell.asset else {
            return
        }
        
        if !self.imagePickerController.contains(asset: asset) {
            self.imagePickerController.select(asset: asset)
        }
    }
    
    public func deselectAsset(atIndex indexPath: IndexPath) {
        guard let cell = self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell
            , let asset = cell.asset else {
                return
        }

        self.imagePickerController.deselect(asset: asset)
    }
    
    public func adjustAssetIndex(_ index: Int) -> Int {
        return self.hidesCamera ? index : index + 1
    }
    
    public func scrollIndexPathToVisible(_ indexPath: IndexPath) {
        if let cellFrame = self.collectionView.collectionViewLayout.layoutAttributesForItem(at: indexPath)?.frame {
            self.collectionView.scrollRectToVisible(cellFrame, animated: false)
        }
    }
    
    public func thumbnailImageView(for indexPath: IndexPath) -> UIImageView? {
        if let cell = self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell {
            return cell.thumbnailImageView
        } else {
            self.collectionView.reloadItems(at: [indexPath])
            
            return (self.collectionView.cellForItem(at: indexPath) as? DKAssetGroupDetailBaseCell)?
                .thumbnailImageView
        }
    }

    func isCameraCell(indexPath: IndexPath) -> Bool {
        return indexPath.row == 0 && !self.hidesCamera
    }

    // MARK: - Swiping
    
    private var fromIndexPath: IndexPath? = nil
    private var swipingIndexPathes = Set<Int>()
    private var swipingToSelect = true
    private var swipingLastLocation = CGPoint.zero
    private var autoScrollingRate: CGFloat = 1.0
    private var autoScrollingDirection: UISwipeGestureRecognizer.Direction = .down
    private lazy var autoScrollingLink: CADisplayLink = {
        let link = CADisplayLink(target: self, selector: #selector(autoScrolling))
        link.isPaused = true
        link.add(to: RunLoop.main, forMode: .default)
        link.frameInterval = 1
        return link
    }()
    
    // use the swiping gesture to select the currently swiping cell.
    @objc private func swiping(gesture: UIPanGestureRecognizer) {
        let location = gesture.location(in: self.collectionView)
        
        switch gesture.state {
        case .possible:
            break
        case .began:
            if let indexPath = self.collectionView.indexPathForItem(at: location)
                , let cell = self.collectionView.cellForItem(at: indexPath) {
                self.fromIndexPath = indexPath
                self.swipingToSelect = !cell.isSelected
            }
        case .changed:
            self.onSwipingChanged(location: location)
            self.startAutoScrollingIfNeeded(location: location)
        case .ended:
            fallthrough
        case .cancelled:
            fallthrough
        case .failed:
            self.swipingIndexPathes.removeAll()
            self.fromIndexPath = nil
            self.endAutoScrolling()
        }
    }
    
    private func onSwipingChanged(location: CGPoint) {
        self.swipingLastLocation = location
        
        if let toIndexPath = self.collectionView.indexPathForItem(at: location)
            , let fromIndexPath = self.fromIndexPath {
            let begin = min(fromIndexPath.row, toIndexPath.row)
            let end = max(fromIndexPath.row, toIndexPath.row)
            
            var currentSwipingIndexPathes = Set<Int>()
            for i in begin...end {
                currentSwipingIndexPathes.insert(i)
                self.swipingIndexPathes.remove(i)
                
                if self.swipingToSelect {
                    self.selectAsset(atIndex: IndexPath(row: i, section: 0))
                } else {
                    self.deselectAsset(atIndex: IndexPath(row: i, section: 0))
                }
            }
            
            for i in self.swipingIndexPathes {
                if self.swipingToSelect {
                    self.deselectAsset(atIndex: IndexPath(row: i, section: 0))
                } else {
                    self.selectAsset(atIndex: IndexPath(row: i, section: 0))
                }
            }
            self.swipingIndexPathes = currentSwipingIndexPathes
        }
    }
    
    private func startAutoScrollingIfNeeded(location: CGPoint) {
        let minLocationY = self.collectionView.contentOffset.y
        let maxLocationY = self.collectionView.bounds.height + self.collectionView.contentOffset.y

        debugPrint("minLocationY:\(minLocationY) maxLocationY:\(maxLocationY) current:\(location.y)")
        
        let locationY = min(max(location.y, minLocationY), maxLocationY)
        
        self.autoScrollingDirection = locationY - minLocationY > (maxLocationY - minLocationY) / 2 ? .down : .up
        var diff: CGFloat = 0
        switch self.autoScrollingDirection {
        case .down:
            diff = maxLocationY - locationY
        case .up:
            diff = locationY - minLocationY
        default:
            debugPrint("Known direction.")
        }
        
        let threshold = self.thumbnailSize.height / UIScreen.main.scale
        if diff < threshold {
            self.autoScrollingRate = threshold / max(diff, threshold / 10)
            self.startAutoScrolling()
        } else {
            self.endAutoScrolling()
        }
    }
    
    private func startAutoScrolling() {
        if self.autoScrollingLink.isPaused {
            self.autoScrollingLink.isPaused = false
        }
    }
    
    private func endAutoScrolling() {
        if !self.autoScrollingLink.isPaused {
            self.autoScrollingLink.isPaused = true
        }
    }
    
    @objc private func autoScrolling() {
        let offsetY = CGFloat(self.autoScrollingDirection == .down ? self.autoScrollingRate : -self.autoScrollingRate)
        
        var targetContentOffset = self.collectionView.contentOffset
        targetContentOffset.y += offsetY
        
        targetContentOffset.y = min(max(targetContentOffset.y, self.collectionView.contentInset.top),
                                    self.collectionView.contentSize.height - self.collectionView.bounds.height)
        
        self.collectionView.contentOffset = targetContentOffset
        
        self.onSwipingChanged(location: CGPoint(x: self.swipingLastLocation.x,
                                                y: self.swipingLastLocation.y + offsetY))
    }
    
    // MARK: - UIGestureRecognizerDelegate
    
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
    
    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else { return false }
{'fraction_non_alphanumeric': 0.0828251540670474, 'fraction_numerical': 0.0037750459781240924, 'mean_word_length': 2.2151452282157678, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 53, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '14652', 'n_tokens_mistral': 8567, 'n_tokens_neox': 8136, 'n_words': 1616}