// // ThemeOneVM.swift // WOKA // // Created by MacBook Pro on 23/05/24. // import UIKit import AVFoundation import JWPlayerKit import Alamofire import AppTrackingTransparency import AdSupport class ThemeOneVM{ weak var vc : ThemeOneVC! var cloudMovingRight = false // Flag to track the direction of movement var isMovingRight = false // Flag to track the direction of movement // var liveStreamURL = "https://d3volyx7jx7oal.cloudfront.net/master.m3u8" var avPlayer : AVPlayer! var playerItem: AVPlayerItem! var playerLayer: AVPlayerLayer! var shouldAnimate = true let reachability = NetworkReachabilityManager() let monitor = NWPathMonitor() let queue = DispatchQueue.global(qos: .background) var isNetworkMonitored = false var avPlayerStatus = PlayerStatusEnum.none var avPlayerBufferTimestamp : Date? func requestIDFA() { ATTrackingManager.requestTrackingAuthorization { status in switch status { case .authorized: // User granted permission, now you can access IDFA let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString print("IDFA: \(idfa)") case .denied: print("User denied tracking permission") case .restricted: print("Tracking is restricted") case .notDetermined: print("Tracking authorization dialog has not been shown") @unknown default: print("Unknown authorization status") } } } func initView(){ // Make sure to initialize a blank listing if MyListDataTemp.shareInstance.favListingData?.showData == nil { MyListDataTemp.shareInstance.favListingData = FavouriteListingDM.ResultData(totalRecords: nil, showData: FavouriteListingDM.ResultData.ShowData(hindi: [],english: []),videoData: [],gameData: [],singKaraokeData: [],audioData: []) } // Update user Data on UI setUserData() GoogleInterstistialADSetup.shareInstance.setupGoogleIntersitialAD() // requestIDFA() //Initialize the time persiods for handling the time. AuthFunc.shareInstance.initTimePeriods() // start the time for moitoring time change. startInitialTimer() //Handle tap gesutres handleTaps() //Add notification center for app lifecycle and more handleNotificationCenter() //add tap gesutre to the moving live tv addTapGestureToMovingView() // to fit the hello and name in center properly without space vc.nameLabel.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal) vc.nameLabel.setContentCompressionResistancePriority(.fittingSizeLevel, for: .horizontal) // This will monitor the time and update the bakground color and stars as per the timeline. handleBackground() // Set initial small scale in viewDidLoad self.vc.allIconView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) // Set initial scale to 50% of original size // Perform the animation after the view has been laid out UIView.animate(withDuration: 0.7, delay: 0, options: [], animations: { self.vc.allIconView.transform = CGAffineTransform.identity // Reset the transform to original size }, completion: {_ in }) // Check for local ads. if let adsData = AuthFunc.shareInstance.adsData{ // check if ads data contains ad for webseries if let themeOneAd = adsData.result?.filter({$0.slug == AdsEnum.themeOne.rawValue}).first, let bannerImage = themeOneAd.advertisement?.bannerImage{ vc.adBanner.imageURL(bannerImage, color: .textDarkBlue,type: .ads) { [weak self] isDone in guard let self else{return} // if image gives error hide the ad banner if isDone == false{ vc.adBanner.isHidden = true }else{ vc.adBanner.isHidden = false } } } } } private func handleNotificationCenter(){ NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.themeOnePush(notification:)), name: NSNotification.Name(rawValue: K.NotificationCenterReloads.themeOnePush), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.reloadTheme), name: NSNotification.Name(rawValue: K.NotificationCenterReloads.reloadTheme), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleRouteChange(_:)), name: AVAudioSession.routeChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(languageDidChange), name: .languageDidChange, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(showOfflineDeviceUI(notification:)), name: NSNotification.Name.connectivityStatus, object: nil) } @objc private func languageDidChange() { print("Language Change") } @objc func handleRouteChange(_ notification: Notification) { guard let userInfo = notification.userInfo, let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } switch reason { case .oldDeviceUnavailable: // Headphones unplugged, avoid pausing if desired avPlayer?.play() // Resume playing if paused case .newDeviceAvailable: // Headphones plugged in, you may want to take specific actions if needed avPlayer?.play() // Resume playing if paused default: break } } // MARK: - start stop activity Indicator func startStopActivity(isStart : Bool){ DispatchQueue.main.async { [weak self] in guard let self else{return} if isStart{ vc.liveTVActivityIndicator.startAnimating() }else{ vc.liveTVActivityIndicator.hidesWhenStopped = true vc.liveTVActivityIndicator.stopAnimating() } } } func setUserData(){ guard let data = AuthFunc.shareInstance.userData else{return} //set the first name as the name /* Check User Type, Dont show username if the user type is guest */ switch data.userType{ case "1": // child vc.nameLabel.text = data.fullname?.components(separatedBy: " ").first vc.notificationBtnn.isHidden = false case "2" : // adult vc.nameLabel.text = data.fullname?.components(separatedBy: " ").first vc.notificationBtnn.isHidden = false case "3": // Guest vc.nameLabel.text = "" vc.notificationBtnn.isHidden = true break default: break } if let avatar = data.avtarURL{ self.vc.avatarImage.imageURL(avatar, color: .white, type: .homeAvatar) }else{ vc.avatarImage.image = UIImage(named: "DefaultAvatar") } } // MARK: - Notification Center Handlers @objc func reloadTheme(){ self.vc.delegate?.didPressSwitchButton(from: self.vc) } @objc func appDidEnterBackground() { // Code to execute when the app enters the background print("App entered background") if let rootViewController = UIApplication.shared.mainKeyWindow?.rootViewController { if let topVC = topVC(in: rootViewController) { if topVC is HomeVC || topVC is WokaFMVC{ shouldAnimate = false avPlayer.pause() handleBackground() } else { print("The top view controller is not HomeVC") } } else { print("No top view controller found") } } } @objc func appWillEnterForeground() { // Code to execute when the app enters the foreground print("App will enter foreground") self.centerLiveTVViewHorizontally() if let rootViewController = UIApplication.shared.mainKeyWindow?.rootViewController { if let topVC = topVC(in: rootViewController) { if topVC is HomeVC || topVC is WokaFMVC{ shouldAnimate = true if let player = avPlayer{ player.play() } Timer.scheduledTimer(withTimeInterval: 1.3, repeats: false) { [weak self] _ in guard let self else{return} self.moveLiveTVView() self.moveCloudView() } // DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in // guard let self else{return} // moveLiveTVView() // moveCloudView() // } handleBackground() } else { print("The top view controller is not HomeVC") } } else { print("No top view controller found") } } } // MARK: - Handle Tap Gesture func handleTaps(){ vc.bottomArrow.addTapGesture { PersistentStorage.shared.addOthersCount() let sb = UIStoryboard(name: K.StoryBoard.theme, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Theme.moreVC) as! MoreVC vcPush.modalPresentationStyle = .fullScreen vcPush.modalTransitionStyle = .crossDissolve self.vc.present(vcPush, animated: true) } /* 1 = series, 2 = season, 3= episode, 4 = video, 5 = paint, 6 = game, 7 = audio, 8 = kareoke video, 9 = shop product, 10 = parental video, 11 = article, 12 = live TV, 13 = FM, 14 = teaser, 15 others, 16 = Home FM & Live TV - post_id , post_type rest all will be in others post_type = 15 */ //WebSeries vc.webSeriesView.addTapGesture { PersistentStorage.shared.addOthersCount() CommonNwCallTheme.shareInstance.checkUniqueUser() ViewButtonAnimation.sharedInstance.btnTapped(in: self.vc, view: self.vc.webSeriesView) { [weak self] in guard let self else{return} self.checkType(action: .webseries) } } vc.audioBooksView.addTapGesture { PersistentStorage.shared.addOthersCount() CommonNwCallTheme.shareInstance.checkUniqueUser() ViewButtonAnimation.sharedInstance.btnTapped(in: self.vc, view: self.vc.audioBooksView) { [weak self] in guard let self else{return} self.checkType(action: .audioBooks) } } vc.gamesView.addTapGesture { PersistentStorage.shared.addOthersCount() CommonNwCallTheme.shareInstance.checkUniqueUser() ViewButtonAnimation.sharedInstance.btnTapped(in: self.vc, view: self.vc.gamesView) { [weak self] in guard let self else{return} self.checkType(action: .games) } } vc.karaokeView.addTapGesture { [self] in PersistentStorage.shared.addOthersCount() CommonNwCallTheme.shareInstance.checkUniqueUser() ViewButtonAnimation.sharedInstance.btnTapped(in: self.vc, view: self.vc.karaokeView) { [weak self] in guard let self else{return} self.checkType(action: .karaoke) } } vc.shopView.addTapGesture { PersistentStorage.shared.addOthersCount() CommonNwCallTheme.shareInstance.checkUniqueUser() ViewButtonAnimation.sharedInstance.btnTapped(in: self.vc, view: self.vc.shopView) { [weak self] in guard let self else{return} self.checkType(action: .shop) } } vc.liveTVIcon.addTapGesture { PersistentStorage.shared.addLiveTVCount() CommonNwCallTheme.shareInstance.checkUniqueUser() ViewButtonAnimation.sharedInstance.btnTapped(in: self.vc, view: self.vc.liveTVIcon) { [weak self] in guard let self else{return} self.checkType(action: .liveTV) } } /* If ad banner is set then show the banner */ vc.adBanner.addTapGesture { ViewButtonAnimation.sharedInstance.btnTapped(in: self.vc, view: self.vc.adBanner) { //check url from api if let adsData = AuthFunc.shareInstance.adsData{ // check if ads data contains ad for webseries if let themeOneAd = adsData.result?.filter({$0.slug == AdsEnum.themeOne.rawValue}).first, let adLink = themeOneAd.advertisement?.adLink, let adID = themeOneAd.advertisement?.id{ PersistentStorage.shared.addAdsCount(adID: adID,clicks: 1) if let url = URL(string: adLink), UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url) } } } } } } //Get the notification from observer @objc func themeOnePush(notification: Notification){ if let userInfo = notification.userInfo, let action = userInfo["action"] as? TopViewPush { checkType(action: action) } } // Made a common func to check which module to push func checkType(action : TopViewPush){ switch action { case .webseries: let sb = UIStoryboard(name: K.StoryBoard.webSeries, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.WebSeries.webSeriesVC) as! WebSeriesVC vc.navigationController?.pushViewController(vcPush, animated: true) case .audioBooks: let sb = UIStoryboard(name: K.StoryBoard.audioBooks, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.AudioBooks.audioBookHomeVC) as! AudioBookHomeVC vc.navigationController?.pushViewController(vcPush, animated: true) case .games: let sb = UIStoryboard(name: K.StoryBoard.Games, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Games.gamesListVC) as! GamesListVC vc.navigationController?.pushViewController(vcPush, animated: true) case .karaoke: let sb = UIStoryboard(name: K.StoryBoard.Karaoke, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Karaoke.karaokeListingVC) as! KaraokeListingVC vc.navigationController?.pushViewController(vcPush, animated: true) case .shop: let sb = UIStoryboard(name: K.StoryBoard.shop, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Shop.shopListingVC) as! ShopListingVC vc.navigationController?.pushViewController(vcPush, animated: true) case .liveTV: handleTap(UITapGestureRecognizer()) case .blogs: let sb = UIStoryboard(name: K.StoryBoard.theme, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.Theme.blogsVC) as! BlogsVC vc.navigationController?.pushViewController(vcPush, animated: true) case .radio: let sb = UIStoryboard(name: K.StoryBoard.wokaFM, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.WokaFM.wokaFMVC) as! WokaFMVC vcPush.modalPresentationStyle = .overCurrentContext vcPush.modalTransitionStyle = .crossDissolve vc.present(vcPush, animated: true) } } // MARK: - Animate Clouds and LiveTV and setupAv Player func setupAvPlayer(){ /* Av Player Setup */ guard let data = AuthFunc.shareInstance.staticURLs , let liveStreamData = data.liveData?.first else{ AuthFunc.shareInstance.getStaticURLs() self.vc.toast(msg: "Issue with live streaming", time: 2) return } var url = String() // var title = String() if AuthFunc.shareInstance.languageSelected == .english{ url = liveStreamData.liveURL?.hdURLEn ?? "" // title = liveStreamData.name?.titleEn ?? "" }else{ url = liveStreamData.liveURL?.hdURLHin ?? "" // title = liveStreamData.name?.titleHin ?? "" } guard let streamURL = URL(string: url) else{return} playerItem = AVPlayerItem(url: streamURL) // Create AVPlayer with the stream URL avPlayer = AVPlayer(playerItem: playerItem) // avPlayer.isMuted = true // Create AVPlayerLayer playerLayer = AVPlayerLayer(player: avPlayer) playerLayer.videoGravity = .resizeAspectFill // You can set different videoGravity as per your need // Remove any existing sublayers to avoid duplicates self.vc.liveTvPlayer.layer.sublayers?.forEach { $0.removeFromSuperlayer() } self.vc.liveTvPlayer.layer.addSublayer(playerLayer) playerLayer.frame = vc.liveTvPlayer.bounds avPlayer.volume = 0 vc.addObservers() } func centerLiveTVViewHorizontally() { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.vc.liveTVView.center.x = self.vc.view.center.x // Center the liveTVView horizontally self.vc.liveTVView.layoutIfNeeded() } } func centerCloudsHorizontally() { DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.vc.cloud1.center.x = self.vc.view.center.x // Center the liveTVView horizontally self.vc.cloud2.center.x = self.vc.view.center.x // Center the liveTVView horizontally self.vc.cloud1.layoutIfNeeded() self.vc.cloud2.layoutIfNeeded() } } func moveLiveTVView() { guard shouldAnimate else { self.centerLiveTVViewHorizontally() return } // Stop animating if shouldAnimate is false guard let vc = self.vc else { return } // Ensure the view controller is available UIView.animate(withDuration: 8, delay: 0, options: [.allowUserInteraction], animations: { [weak self] in guard let self = self else { return } let margin: CGFloat = 30 let screenWidth = vc.view.frame.width let viewWidth = vc.liveTVView.frame.width let maxX = screenWidth - margin - viewWidth / 2 let minX = margin + viewWidth / 2 if self.isMovingRight { vc.liveTVView.center.x = maxX // Move to the right } else { vc.liveTVView.center.x = minX // Move to the left } }, completion: { [weak self] _ in guard let self = self else { return } self.isMovingRight.toggle() // Toggle the direction for the next iteration // DispatchQueue.main.async { // Ensure the recursive call is made on the main thread // self.moveLiveTVView() if self.shouldAnimate { self.moveLiveTVView() // Recursively call moveLiveTVView to create a continuous animation } // } }) } func moveCloudView() { guard shouldAnimate else { centerCloudsHorizontally() return } UIView.animate(withDuration: 23, delay: 0, options: [], animations: { [weak self] in guard let self else{return} if cloudMovingRight { // print("right") vc.cloud2.center.x += 140 // Move to the right vc.cloud1.center.x -= 140 // Move to the right } else { // print("left") vc.cloud2.center.x -= 140 // Move to the left vc.cloud1.center.x += 140 // Move to the left } }, completion: { [weak self] _ in guard let self else{return} cloudMovingRight.toggle() // Toggle the direction for the next iteration moveCloudView() // Recursively call moveView to create a continuous animation }) } // MARK: - LiveTV Tap Handling private func addTapGestureToMovingView(){ let tap = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) // tap.numberOfTouchesRequired = 1 tap.numberOfTapsRequired = 1 vc.liveTVView.addGestureRecognizer(tap) vc.liveTVView.isUserInteractionEnabled = true } // function which is triggered when handleTap on livetv is called @objc func handleTap(_ sender: UITapGestureRecognizer) { PersistentStorage.shared.addLiveTVCount() CommonNwCallTheme.shareInstance.checkUniqueUser() DispatchQueue.main.async { Utilities.startProgressHUD(msg: "Loading...") } print("tapped") let vc = self.vc.storyboard?.instantiateViewController(withIdentifier: K.StoryBoardID.Theme.playerVC) as! PlayerVC guard let data = AuthFunc.shareInstance.staticURLs , let liveStreamData = data.liveData?.first else{ self.vc.toast(msg: "Issue with live streaming", time: 2) return } var url = String() var title = String() if AuthFunc.shareInstance.languageSelected == .english{ url = liveStreamData.liveURL?.hdURLEn ?? "" title = liveStreamData.name?.titleEn ?? "" }else{ url = liveStreamData.liveURL?.hdURLHin ?? "" title = liveStreamData.name?.titleHin ?? "" } do { // Ensure the liveStreamURL is valid guard let liveStreamURL = URL(string: url) else { print("Invalid live stream URL") Utilities.dismissProgressHUD() return } let videoSourceBuilder = try JWVideoSourceBuilder() .file(liveStreamURL) .label(title) .build() // Create a JWPlayerItem let item = try JWPlayerItemBuilder() .videoSources([videoSourceBuilder]) .title(title) .build() let config = try JWPlayerConfigurationBuilder() .playlist(items: [item]) .autostart(true) .build() vc.config = config vc.dismissTapped = self.tapped vc.contentType = .liveStream if let streamID = AuthFunc.shareInstance.staticURLs?.liveData?.first?.id{ vc.vm.videoIDs = [streamID] } vc.modalPresentationStyle = .fullScreen vc.modalTransitionStyle = .crossDissolve // Present the PlayerVC self.vc.present(vc, animated: true) { [weak self] in guard let self else{return} stopLiveStream() } } catch { print("Error creating JWPlayer configuration: \(error)") Utilities.dismissProgressHUD() } // Dismiss the progress HUD after the view controller presentation Utilities.dismissProgressHUD() } func tapped(){ DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in guard let self else{return} isMovingRight = false self.avPlayer.play() // self.moveLiveTVView() // self.moveCloudView() self.vc.liveTvPlayer.layoutIfNeeded() } print("Player Will Exit") } func startLiveStream(){ avPlayer.play() avPlayer.volume = 0 } func stopLiveStream(){ avPlayer.pause() } // MARK: - Handle Time Change @objc func handleBackground(){ /* check if the player is stuck on buffering and also if its 2 minutes or more, setup av player again. */ if let timeStamp = avPlayerBufferTimestamp, avPlayerStatus == .buffering{ /* check if timestamp difference is equal and greater than 2 minutes i.e 120 seconds */ let duration = DateFormatterLib.dateDifferenceINT(date1: timeStamp, date2: Date()) print("Player paused seconds:- ",duration) // if from 2 minutes its showing buffering then load it again. if duration >= 120{ // setup av player again if CheckReachability.reachability?.isReachable == true{ setupAvPlayer() // make the timestamp nil and status to none avPlayerBufferTimestamp = nil avPlayerStatus = .none } } }else{ print("No Buffer") } let currentTime = Date() for period in AuthFunc.shareInstance.timePeriods { if period.contains(currentTime: currentTime) { let details = period.details // Update view's background color with a gradient or single color // DispatchQueue.main.async { [weak self] in // guard let self else{return} if period.details.dayCycle == .night{ startStarGlowAnimation() }else{ //just hide the moon and star if its not night !vc.moonImage.isHidden ? vc.moonImage.isHidden = true : nil vc.star.forEach { image in !image.isHidden ? image.isHidden = true : nil } } DispatchQueue.main.async { self.vc.gradientView.applyGradient(colors: [details.color1, details.color2], startPoint: CGPoint(x: 0, y: 0), endPoint: CGPoint(x: 0, y: 0.8)) self.vc.nameLabel.textColor = details.textColor self.vc.HelloLabel.textColor = details.textColor self.vc.welcomeLabel.textColor = details.textColor self.vc.homeGrass.image = UIImage(named: details.grass) } // } break } } } func startStarGlowAnimation() { vc.moonImage.isHidden ? vc.moonImage.isHidden = false : nil for (index, imageView) in vc.star.enumerated() { //If star is hidden just unhide it for the night time (vc.star[index].isHidden) ? (vc.star[index].isHidden = false) : nil let animation = CABasicAnimation(keyPath: "opacity") animation.fromValue = 1.0 animation.toValue = 0.07 animation.duration = 3.0 animation.autoreverses = true animation.repeatCount = .infinity // Calculate delay for each animation based on index let delay = Double(index) * 1 // Adjust the delay as needed // Apply the delay to the animation animation.beginTime = CACurrentMediaTime() + delay imageView.layer.add(animation, forKey: "glowAnimation") } } func startInitialTimer() { self.handleBackground() let now = Date() let nextMinute = Calendar.current.nextDate(after: now, matching: DateComponents(second: 0), matchingPolicy: .nextTime)! let timeInterval = nextMinute.timeIntervalSince(now) // Schedule the initial timer to fire at the start of the next minute vc.timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(startRepeatingTimer), userInfo: nil, repeats: false) } @objc func startRepeatingTimer() { // Handle the minute change when the initial timer fires self.handleBackground() // Schedule the repeating timer to fire every minute vc.timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(handleBackground), userInfo: nil, repeats: true) } func updateClicks(){ var totalClicks = [ClicksAnalytics]() totalClicks.append(ClicksAnalytics(postID: 0, postType: 15, numberOfClicks: 12, deviceType: 2, categoryID: 0)) // NetworkManager.shareInstance.nwCallRawJSON(clicksData: totalClicks) } } //class CustomLiveTVView: UIView { // override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { // let hitView = super.hitTest(point, with: event) // // Debug statement to check when hitTest is called // print("hitTest called with point: \(point)") // // // Check if the touch is within the bounds of this view // if hitView == self { // return hitView // } // // If the touch is outside the bounds, return nil // return nil // } //}