Features: - VOD library with movie grouping and version detection - TV show library with season/episode organization - TMDB integration for trending shows and recently aired episodes - Recent releases section with TMDB release date sorting - Watch history tracking with continue watching - Playlist caching (12-hour TTL) for offline support - M3U playlist parsing with XStream API support - Authentication with credential storage Technical: - SwiftUI for tvOS - Actor-based services for thread safety - Persistent caching for playlists, TMDB data, and watch history - KSPlayer integration for video playback Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
72 lines
3.1 KiB
Swift
72 lines
3.1 KiB
Swift
//
|
|
// PlayerTransitionAnimator.swift
|
|
// KSPlayer
|
|
//
|
|
// Created by kintan on 2021/8/20.
|
|
//
|
|
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
|
|
class PlayerTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
|
|
private let isDismiss: Bool
|
|
private let containerView: UIView
|
|
private let animationView: UIView
|
|
private let fromCenter: CGPoint
|
|
init(containerView: UIView, animationView: UIView, isDismiss: Bool = false) {
|
|
self.containerView = containerView
|
|
self.animationView = animationView
|
|
self.isDismiss = isDismiss
|
|
fromCenter = containerView.superview?.convert(containerView.center, to: nil) ?? .zero
|
|
super.init()
|
|
}
|
|
|
|
func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval {
|
|
0.3
|
|
}
|
|
|
|
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
|
|
let animationSuperView = animationView.superview
|
|
let animationViewIndex = animationSuperView?.subviews.firstIndex(of: animationView) ?? 0
|
|
let initSize = animationView.frame.size
|
|
let animationFrameConstraints = animationView.frameConstraints
|
|
guard let presentedView = transitionContext.view(forKey: isDismiss ? .from : .to) else {
|
|
return
|
|
}
|
|
if isDismiss {
|
|
containerView.layoutIfNeeded()
|
|
presentedView.bounds = containerView.bounds
|
|
presentedView.removeFromSuperview()
|
|
} else {
|
|
if let viewController = transitionContext.viewController(forKey: .to) {
|
|
presentedView.frame = transitionContext.finalFrame(for: viewController)
|
|
}
|
|
}
|
|
presentedView.layoutIfNeeded()
|
|
transitionContext.containerView.addSubview(animationView)
|
|
animationView.translatesAutoresizingMaskIntoConstraints = true
|
|
guard let transform = transitionContext.viewController(forKey: .from)?.view.transform else {
|
|
return
|
|
}
|
|
animationView.transform = CGAffineTransform(scaleX: initSize.width / animationView.frame.size.width, y: initSize.height / animationView.frame.size.height).concatenating(transform)
|
|
let toCenter = transitionContext.containerView.center
|
|
let fromCenter = transform == .identity ? fromCenter : fromCenter.reverse
|
|
animationView.center = isDismiss ? toCenter : fromCenter
|
|
UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveEaseInOut) {
|
|
self.animationView.transform = .identity
|
|
self.animationView.center = self.isDismiss ? fromCenter : toCenter
|
|
} completion: { _ in
|
|
animationSuperView?.insertSubview(self.animationView, at: animationViewIndex)
|
|
if !animationFrameConstraints.isEmpty {
|
|
self.animationView.translatesAutoresizingMaskIntoConstraints = false
|
|
NSLayoutConstraint.activate(animationFrameConstraints)
|
|
}
|
|
if !self.isDismiss {
|
|
transitionContext.containerView.addSubview(presentedView)
|
|
}
|
|
transitionContext.completeTransition(true)
|
|
}
|
|
}
|
|
}
|
|
#endif
|