<commit_before>//
//    MIT License
//
//    Copyright (c) 2017 Touchwonders Commerce B.V.
//
//    Permission is hereby granted, free of charge, to any person obtaining a copy
//    of this software and associated documentation files (the "Software"), to deal
//    in the Software without restriction, including without limitation the rights
//    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//    copies of the Software, and to permit persons to whom the Software is
//    furnished to do so, subject to the following conditions:
//
//    The above copyright notice and this permission notice shall be included in all
//    copies or substantial portions of the Software.
//
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//    SOFTWARE.


import UIKit

/**
 *  The TransitionDriver is something the app developer will not be confronted with.
 *  It is owned by the TransitionController.
 *  It takes a Transition (specification), sets up UIViewPropertyAnimators, ties it
 *  all together with gesture recognizers and makes the magic happen.
 */
internal final class TransitionDriver {
    
    let transition: Transition
    let operationContext: TransitionOperationContext
    let interactionController: TransitionInteractionController?
    
    private var layerAnimators: [AnimationLayerAnimator]!           /// This will be set after init
    private var sharedElementAnimator: UIViewPropertyAnimator? /// This might be set after init, depending on the presence of both an interactive element and a specification
    
    
    var context: UIViewControllerContextTransitioning {
        return operationContext.context
    }
    var operation: TransitionOperation {
        return operationContext.operation
    }
    var canBeInteractive : Bool {
        return interactionController != nil
    }
    
    
    private let effectiveDuration: TimeInterval
    
    private let completionCoordinator = TransitionCompletionCoordinator()
    
    // We add an invisible view with which something is done (an empty animation block would result in the completion being called immediately).
    // It is used in the progressLayerAnimator.
    private let progressAnimationView = UIView(frame: .zero)
    // Using this animator we have a guaranteed full-AnimationRange layer from which at any moment the overall fractionComplete can be derived.
    private var progressLayerAnimator: AnimationLayerAnimator!
    
    // Returns the current overall fractionComplete of the transition
    private var totalFractionComplete: AnimationFraction {
        return AnimationFraction(progressLayerAnimator.animator.fractionComplete)
    }
    
    
    init(transition: Transition, operationContext: TransitionOperationContext, interactionController: TransitionInteractionController?) {
        self.transition = transition
        self.operationContext = operationContext
        self.interactionController = interactionController
        
        // This is only computed at the very beginning, since any interaction might influence timingCurveProviders, which could change the effective duration:
        self.effectiveDuration = transition.effectiveDuration
        
        // We add an invisible view with which something is done (an empty animation block would result in the completion being called immediately).
        // Using this animator we have a guaranteed full-AnimationRange layer from which at any moment the overall fractionComplete can be derived.
        operationContext.context.containerView.addSubview(progressAnimationView)
        let progressLayer = AnimationLayer(range: .full, timingParameters: AnimationTimingParameters(animationCurve: .linear), animation: { [weak self] in
            self?.progressAnimationView.transform = CGAffineTransform(translationX: -10, y: 0)
        })
        self.progressLayerAnimator = AnimationLayerAnimator(layer: progressLayer, animator: propertyAnimatorFor(animationLayer: progressLayer))
        completionCoordinator.add(animator: progressLayerAnimator.animator)
        progressLayerAnimator.animator.addCompletion({ [weak self] completionPosition in
            self?.progressLayerAnimator.completionPosition = completionPosition
        })
        // The animation must be set up before any interaction,
        // because any added views (to the context's containerView) should logically be positioned below
        // the interactive element.
        transition.animation.setup(in: operationContext)
        
        // See if there's an interactive transition element:
        var sharedElement: SharedElement? = nil

        if let interactionController = interactionController {
            // Get informed about interaction by registering to the interactionController's GestureRecognizer:
            let gestureRecognizer = interactionController.gestureRecognizer
            gestureRecognizer.addTarget(self, action: #selector(updateInteraction(_:)))
            
            if let sharedElementProvider = interactionController.sharedElementProvider {
                sharedElement = sharedElementProvider.sharedElementForInteractiveTransition(with: interactionController, in: operationContext)
            }
        }
        
        // The transition animation with an interactive element is optional, but if available will lead the base transtion animation, set up right after this.
        if let sharedElement = sharedElement, let sharedElementTransitionSpecification = transition.sharedElement {
            sharedElementTransitionSpecification.sharedElement = sharedElement
            // Now the interaction can be set up, adding the interactive element to the context
            transition.sharedElement?.setup(in: operationContext)
            // The animator will be set up when the interactive part should animate (i.e. it's not actively interactive).
            // Note that it is set up in two parts:
            // one configures the property animator, allowing us to determine the exact duration (see further on in this init the effectiveDuration)
            // two sets the actual animation and completion of that animator, as soon as it is required in animate()
            
            // Add a long-press gesture recognizer to allow interruption of the animation
            sharedElement.transitioningView.addGestureRecognizer(newInterruptionGestureRecognizer())
        } else if interactionController != nil {
            // There is no interactive element to attach the interruption gesture to, so attach it to the entire containerView of the transition:
            context.containerView.addGestureRecognizer(newInterruptionGestureRecognizer())
            // We do need to have an interactionController to continue after interruption
        }
        
        
        // Configure execution and completion of transition.animation
        
        layerAnimators = [progressLayerAnimator]
        
        for layer in transition.animation.layers {
            let animator = propertyAnimatorFor(animationLayer: layer)
            let layerAnimator = AnimationLayerAnimator(layer: layer, animator: animator)
            layerAnimators.append(layerAnimator)
        }
        
        completionCoordinator.completion { [weak self] in
            let completionPosition = self?.progressLayerAnimator.completionPosition ?? .end
            //  execute any specified completion
            transition.animation.completion(position: completionPosition)
            transition.sharedElement?.completion(position: completionPosition)
            
            //  delegate the completion phase
            if completionPosition == .end {
                self?.didTransition(with: sharedElement)
            } else {
                self?.cancelledTransition(with: sharedElement)
            }
            //  cleanup
            self?.removeInterruptionGestureRecognizer()
            self?.progressAnimationView.removeFromSuperview()
            
            //  inform the context we are done
            self?.context.completeTransition(completionPosition == .end)
        }
        
        willTransition(with: sharedElement)
        
        if context.isInteractive {
            // If the transition is initially interactive, make sure the interactive element (if present) is configured appropriately
            startInteraction()
        } else {
            // Begin the animation phase immediately if the transition is not initially interactive
            animate(.end)
        }
    }
    
    // MARK: Property Animators
    
    private func propertyAnimatorFor(animationLayer: AnimationLayer, durationFactor: TimeInterval = 1.0) -> UIViewPropertyAnimator {
        let duration = animationLayer.range.length * effectiveDuration * durationFactor<commit_msg>Properly finish all animators upon deinitializing the TransitionDriver<commit_after>//
//    MIT License
//
//    Copyright (c) 2017 Touchwonders Commerce B.V.
//
//    Permission is hereby granted, free of charge, to any person obtaining a copy
//    of this software and associated documentation files (the "Software"), to deal
//    in the Software without restriction, including without limitation the rights
//    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//    copies of the Software, and to permit persons to whom the Software is
//    furnished to do so, subject to the following conditions:
//
//    The above copyright notice and this permission notice shall be included in all
//    copies or substantial portions of the Software.
//
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//    SOFTWARE.


import UIKit

/**
 *  The TransitionDriver is something the app developer will not be confronted with.
 *  It is owned by the TransitionController.
 *  It takes a Transition (specification), sets up UIViewPropertyAnimators, ties it
 *  all together with gesture recognizers and makes the magic happen.
 */
internal final class TransitionDriver {
    
    let transition: Transition
    let operationContext: TransitionOperationContext
    let interactionController: TransitionInteractionController?
    
    private var layerAnimators: [AnimationLayerAnimator]!           /// This will be set after init
    private var sharedElementAnimator: UIViewPropertyAnimator? /// This might be set after init, depending on the presence of both an interactive element and a specification
    
    
    private var allAnimators: [UIViewPropertyAnimator] {
        var animators: [UIViewPropertyAnimator] = layerAnimators.map { $0.animator }
        if let sharedElementAnimator = sharedElementAnimator {
            animators.append(sharedElementAnimator)
        }
        return animators
    }
    
    
    var context: UIViewControllerContextTransitioning {
        return operationContext.context
    }
    var operation: TransitionOperation {
        return operationContext.operation
    }
    var canBeInteractive : Bool {
        return interactionController != nil
    }
    
    
    private let effectiveDuration: TimeInterval
    
    private let completionCoordinator = TransitionCompletionCoordinator()
    
    // We add an invisible view with which something is done (an empty animation block would result in the completion being called immediately).
    // It is used in the progressLayerAnimator.
    private let progressAnimationView = UIView(frame: .zero)
    // Using this animator we have a guaranteed full-AnimationRange layer from which at any moment the overall fractionComplete can be derived.
    private var progressLayerAnimator: AnimationLayerAnimator!
    
    // Returns the current overall fractionComplete of the transition
    private var totalFractionComplete: AnimationFraction {
        return AnimationFraction(progressLayerAnimator.animator.fractionComplete)
    }
    
    
    init(transition: Transition, operationContext: TransitionOperationContext, interactionController: TransitionInteractionController?) {
        self.transition = transition
        self.operationContext = operationContext
        self.interactionController = interactionController
        
        // This is only computed at the very beginning, since any interaction might influence timingCurveProviders, which could change the effective duration:
        self.effectiveDuration = transition.effectiveDuration
        
        // We add an invisible view with which something is done (an empty animation block would result in the completion being called immediately).
        // Using this animator we have a guaranteed full-AnimationRange layer from which at any moment the overall fractionComplete can be derived.
        operationContext.context.containerView.addSubview(progressAnimationView)
        let progressLayer = AnimationLayer(range: .full, timingParameters: AnimationTimingParameters(animationCurve: .linear), animation: { [weak self] in
            self?.progressAnimationView.transform = CGAffineTransform(translationX: -10, y: 0)
        })
        self.progressLayerAnimator = AnimationLayerAnimator(layer: progressLayer, animator: propertyAnimatorFor(animationLayer: progressLayer))
        completionCoordinator.add(animator: progressLayerAnimator.animator)
        progressLayerAnimator.animator.addCompletion({ [weak self] completionPosition in
            self?.progressLayerAnimator.completionPosition = completionPosition
        })
        // The animation must be set up before any interaction,
        // because any added views (to the context's containerView) should logically be positioned below
        // the interactive element.
        transition.animation.setup(in: operationContext)
        
        // See if there's an interactive transition element:
        var sharedElement: SharedElement? = nil

        if let interactionController = interactionController {
            // Get informed about interaction by registering to the interactionController's GestureRecognizer:
            let gestureRecognizer = interactionController.gestureRecognizer
            gestureRecognizer.addTarget(self, action: #selector(updateInteraction(_:)))
            
            if let sharedElementProvider = interactionController.sharedElementProvider {
                sharedElement = sharedElementProvider.sharedElementForInteractiveTransition(with: interactionController, in: operationContext)
            }
        }
        
        // The transition animation with an interactive element is optional, but if available will lead the base transtion animation, set up right after this.
        if let sharedElement = sharedElement, let sharedElementTransitionSpecification = transition.sharedElement {
            sharedElementTransitionSpecification.sharedElement = sharedElement
            // Now the interaction can be set up, adding the interactive element to the context
            transition.sharedElement?.setup(in: operationContext)
            // The animator will be set up when the interactive part should animate (i.e. it's not actively interactive).
            // Note that it is set up in two parts:
            // one configures the property animator, allowing us to determine the exact duration (see further on in this init the effectiveDuration)
            // two sets the actual animation and completion of that animator, as soon as it is required in animate()
            
            // Add a long-press gesture recognizer to allow interruption of the animation
            sharedElement.transitioningView.addGestureRecognizer(newInterruptionGestureRecognizer())
        } else if interactionController != nil {
            // There is no interactive element to attach the interruption gesture to, so attach it to the entire containerView of the transition:
            context.containerView.addGestureRecognizer(newInterruptionGestureRecognizer())
            // We do need to have an interactionController to continue after interruption
        }
        
        
        // Configure execution and completion of transition.animation
        
        layerAnimators = [progressLayerAnimator]
        
        for layer in transition.animation.layers {
            let animator = propertyAnimatorFor(animationLayer: layer)
            let layerAnimator = AnimationLayerAnimator(layer: layer, animator: animator)
            layerAnimators.append(layerAnimator)
        }
        
        completionCoordinator.completion { [weak self] in
            let completionPosition = self?.progressLayerAnimator.completionPosition ?? .end
            //  execute any specified completion
            transition.animation.completion(position: completionPosition)
            transition.sharedElement?.completion(position: completionPosition)
            
            //  delegate the completion phase
            if completionPosition == .end {
                self?.didTransition(with: sharedElement)
            } else {
                self?.cancelledTransition(with: sharedElement)
            }
            //  cleanup
            self?.removeInterruptionGestureRecognizer()
            self?.progressAnimationView.removeFromSuperview()
            
            //  inform the context we are done
            self?.context.completeTransition(completionPosition == .end)
        }
        
        willTransition(with: sharedElement)
        
        if context.isInteractive {
            // If the transition is initially interactive, make sure the interactive element (if present) is configured appropriately
            startInteraction()
        } else {
            // Begin the animation phase immediately if the transition is not initially interactive
            animate(.end)
        }
    }
    
    
    deinit {
        // Ensure no animator is unfinished (otherwise an iOS system exception is thrown)
        completionCoordinator.invalidate()
        allAnimators.forEach {
            $0.stopAnimation(false)
            $0.finishAnimation(at: .current)
        }
    }
    
    
    // MARK: Property Animators
    
    private func propertyAnimatorFor(animationLayer: AnimationLayer, durationFactor: TimeInterval = 1.0) -> UIViewPropertyAnimator {
        let duration = animationLayer.range.length * effectiveDuration * durationFactor
{'fraction_non_alphanumeric': 0.05478306325143753, 'fraction_numerical': 0.001097752221641401, 'mean_word_length': 2.882102272727273, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5924284', 'n_tokens_mistral': 4239, 'n_tokens_neox': 4084, 'n_words': 1640}