diff --git a/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift b/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift index 1ffc70ad..28cef304 100644 --- a/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift +++ b/iOS/Projects/App/WeTri/Sources/Application/AppDelegate.swift @@ -5,8 +5,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { func application( _: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil - ) - -> Bool { + ) -> Bool { return true } diff --git a/iOS/Projects/Features/Login/Resources/cycleing.mp4 b/iOS/Projects/Features/Login/Resources/cycleing.mp4 new file mode 100644 index 00000000..b420bb47 Binary files /dev/null and b/iOS/Projects/Features/Login/Resources/cycleing.mp4 differ diff --git a/iOS/Projects/Features/Login/Resources/running.mp4 b/iOS/Projects/Features/Login/Resources/running.mp4 new file mode 100644 index 00000000..37f927e3 Binary files /dev/null and b/iOS/Projects/Features/Login/Resources/running.mp4 differ diff --git a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/LoginViewController.swift b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/LoginViewController.swift index aea6a065..3eab8771 100644 --- a/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/LoginViewController.swift +++ b/iOS/Projects/Features/Login/Sources/Presentation/LoginScene/LoginViewController.swift @@ -7,6 +7,7 @@ // import AuthenticationServices +import AVFoundation import Combine import CombineCocoa import DesignSystem @@ -22,23 +23,38 @@ final class LoginViewController: UIViewController { private let credentialSubject = PassthroughSubject() private let loginSubject = PassthroughSubject() + private var timeObserverToken: Any? + private let playerLayer: AVPlayerLayer? = { + guard + let bundle = Bundle(for: LoginViewController.self).path(forResource: "running", ofType: "mp4") + else { + return nil + } + let videoURL = URL(fileURLWithPath: bundle) + let playerItem = AVPlayerItem(url: videoURL) + let player = AVPlayer(playerItem: playerItem) + let playerLayer = AVPlayerLayer(player: player) + playerLayer.videoGravity = .resizeAspectFill // 영상을 화면에 꽉 차게 표시 + return playerLayer + }() + private lazy var logoImageView: UIImageView = { let imageView = UIImageView() imageView.translatesAutoresizingMaskIntoConstraints = false - imageView.image = .logoImage + imageView.image = .logoImageDark imageView.contentMode = .scaleAspectFit return imageView }() private lazy var appleLoginButton: ASAuthorizationAppleIDButton = { - let button = ASAuthorizationAppleIDButton(type: .signIn, style: .black) + let button = ASAuthorizationAppleIDButton(type: .signIn, style: .white) button.translatesAutoresizingMaskIntoConstraints = false return button }() private let policyTextView: UITextView = { let attributedString = NSMutableAttributedString(string: "가입을 진행할 경우, 서비스 약관 및\n개인정보 처리방침에 동의한것으로 간주합니다.") - attributedString.addAttribute(.link, value: PrivacyLink.link, range: NSRange(location: 12, length: 15)) + attributedString.addAttribute(.link, value: PrivacyLink.link, range: NSRange(location: 12, length: 18)) let textView = UITextView() textView.translatesAutoresizingMaskIntoConstraints = false @@ -50,7 +66,6 @@ final class LoginViewController: UIViewController { textView.font = .systemFont(ofSize: 12, weight: .medium) textView.textColor = DesignSystemColor.primaryText textView.textAlignment = .center - textView.backgroundColor = DesignSystemColor.secondaryBackground return textView }() @@ -64,12 +79,48 @@ final class LoginViewController: UIViewController { fatalError("init(coder:) has not been implemented") } + deinit { + Log.make().debug("\(Self.self) deinitialized") + if let timeObserverToken { + playerLayer?.player?.removeTimeObserver(timeObserverToken) + self.timeObserverToken = nil + } + } + override public func viewDidLoad() { super.viewDidLoad() configureUI() bindViewModel() bindUI() } + + private func setupVideoPlayerDuration() { + // 재생 종료 시점 감지 + guard let duration = playerLayer?.player?.currentItem?.asset.duration + else { + return + } + let endTime = CMTimeSubtract(duration, CMTimeMake(value: 5, timescale: 1)) // 종료 5초 전 + timeObserverToken = playerLayer?.player?.addBoundaryTimeObserver(forTimes: [NSValue(time: endTime)], queue: .main) { + [weak self] in + self?.slowDownPlayback() + } + } + + private func slowDownPlayback() { + // 재생 속도를 점차 감소 + guard var rate = playerLayer?.player?.rate else { return } + let decelerationFactor: Float = 0.98 // 감속 계수: 각 단계마다 속도를 98%로 줄임 + Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in + if rate > 0.1 { + rate *= decelerationFactor + self?.playerLayer?.player?.rate = rate + } else { + timer.invalidate() + self?.playerLayer?.player?.pause() + } + } + } } // MARK: UI @@ -80,6 +131,21 @@ private extension LoginViewController { let safeArea = view.safeAreaLayoutGuide + // 비디오가 있으면 설정 + if let playerLayer { + view.layer.addSublayer(playerLayer) + playerLayer.frame = view.bounds + playerLayer.player?.play() + // 가우시안 블러 효과 추가 + addBlurEffect() + + // 그라디언트 배경 추가 + addGradientLayer() + + // duration 설정 + setupVideoPlayerDuration() + } + view.addSubview(logoImageView) NSLayoutConstraint.activate([ logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), @@ -160,6 +226,21 @@ private extension LoginViewController { authorizationController.presentationContextProvider = self authorizationController.performRequests() } + + func addBlurEffect() { + let blurEffect = UIBlurEffect(style: .dark) + let blurredEffectView = UIVisualEffectView(effect: blurEffect) + blurredEffectView.frame = view.bounds + view.addSubview(blurredEffectView) + } + + func addGradientLayer() { + let gradientLayer = CAGradientLayer() + gradientLayer.frame = view.bounds + gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor] + gradientLayer.locations = [0.0, 1.0] + view.layer.addSublayer(gradientLayer) + } } // MARK: - LoginViewController + ASAuthorizationControllerDelegate diff --git a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift index 48f09af1..a48b36b2 100644 --- a/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift +++ b/iOS/Projects/Features/SignUp/Sources/Presentation/Common/Coordinator/SignUpFeatureCoordinator.swift @@ -96,6 +96,6 @@ public final class SignUpFeatureCoordinator: SignUpFeatureCoordinating { signUpProfileViewController: signUpProfileViewController ) - navigationController.pushViewController(signUpContainerViewController, animated: false) + navigationController.setViewControllers([signUpContainerViewController], animated: true) } } diff --git a/iOS/Projects/Shared/DesignSystem/Resources/Images.xcassets/LogoForDarkMode.imageset/Contents.json b/iOS/Projects/Shared/DesignSystem/Resources/Images.xcassets/LogoForDarkMode.imageset/Contents.json new file mode 100644 index 00000000..021eab72 --- /dev/null +++ b/iOS/Projects/Shared/DesignSystem/Resources/Images.xcassets/LogoForDarkMode.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "LogoForDarkModeWith75.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iOS/Projects/Shared/DesignSystem/Resources/Images.xcassets/LogoForDarkMode.imageset/LogoForDarkModeWith75.png b/iOS/Projects/Shared/DesignSystem/Resources/Images.xcassets/LogoForDarkMode.imageset/LogoForDarkModeWith75.png new file mode 100644 index 00000000..a32753f2 Binary files /dev/null and b/iOS/Projects/Shared/DesignSystem/Resources/Images.xcassets/LogoForDarkMode.imageset/LogoForDarkModeWith75.png differ diff --git a/iOS/Projects/Shared/DesignSystem/Sources/UIImage+assets.swift b/iOS/Projects/Shared/DesignSystem/Sources/UIImage+assets.swift index ccc3bf6f..ba919493 100644 --- a/iOS/Projects/Shared/DesignSystem/Sources/UIImage+assets.swift +++ b/iOS/Projects/Shared/DesignSystem/Sources/UIImage+assets.swift @@ -10,6 +10,7 @@ import UIKit public extension UIImage { static let logoImage: UIImage = .logo + static let logoImageDark: UIImage = .logoForDarkMode static let pencilImage: UIImage = .pencil static let noResultsImage: UIImage = .noResults }