diff --git a/Random/App/Resources/de.lproj/Localizable.strings b/Random/App/Resources/de.lproj/Localizable.strings index fcb841b4..c44a0f55 100644 --- a/Random/App/Resources/de.lproj/Localizable.strings +++ b/Random/App/Resources/de.lproj/Localizable.strings @@ -25,6 +25,7 @@ "can_generate_random_contact" = "Es ist möglich, einen zufälligen Kontakt aus einer Liste zu generieren"; "can_generate_random_lottery_ticket" = "Es ist möglich, ein zufälliges Los für die Teilnahme am Gewinnspiel zu generieren"; "can_generate_random_movie" = "Es ist möglich, einen zufälligen Film zu erzeugen"; +"can_generate_random_series" = "Es ist möglich, eine zufällige Serie zu erzeugen"; "can_generate_random_password" = "Es ist möglich, ein zufälliges Passwort für die Registrierung zu generieren"; "can_generate_random_photo_filter" = "Es ist möglich, einen zufälligen Filter für ein Foto zu erzeugen"; "can_participate_in_weekly_prize_draw" = "Sie können an der wöchentlichen Verlosung teilnehmen"; @@ -216,6 +217,7 @@ "select_coin_style" = "Münzstil auswählen"; "select_icon" = "Symbol auswählen"; "selen" = "Selen"; +"series" = "Serien"; "september" = "September"; "settings" = "Einstellungen"; "seven_days_free" = "7 Tage kostenlos"; @@ -258,6 +260,7 @@ "vendor_identifier" = "Anbieter-ID"; "violet_lemon" = "Violett-Zitrone"; "walk" = "Spaziergang"; +"watchTrailer" = "Trailer ansehen"; "wednesday" = "Mittwoch"; "yes" = "Ja"; "yes_or_no" = "Ja oder Nein"; diff --git a/Random/App/Resources/en.lproj/Localizable.strings b/Random/App/Resources/en.lproj/Localizable.strings index df823723..9fa28730 100644 --- a/Random/App/Resources/en.lproj/Localizable.strings +++ b/Random/App/Resources/en.lproj/Localizable.strings @@ -25,6 +25,7 @@ "can_generate_random_contact" = "You can generate a random contact from the list"; "can_generate_random_lottery_ticket" = "You can generate a random lot to participate in the lottery"; "can_generate_random_movie" = "You can generate a random movie"; +"can_generate_random_series" = "You can generate a random series"; "can_generate_random_password" = "You can generate a random password for registration"; "can_generate_random_photo_filter" = "You can generate a random filter for photos"; "can_participate_in_weekly_prize_draw" = "You can participate in the weekly prize draw"; @@ -216,6 +217,7 @@ "select_coin_style" = "Select coin style"; "select_icon" = "Select icon"; "selen" = "Selenium"; +"series" = "Series"; "september" = "September"; "settings" = "Settings"; "seven_days_free" = "7 days free"; @@ -258,6 +260,7 @@ "vendor_identifier" = "Vendor ID"; "violet_lemon" = "Violet-lemon"; "walk" = "Walk"; +"watchTrailer" = "Watch trailer"; "wednesday" = "Wednesday"; "yes" = "Yes"; "yes_or_no" = "Yes or No"; diff --git a/Random/App/Resources/es.lproj/Localizable.strings b/Random/App/Resources/es.lproj/Localizable.strings index b1961cf3..cda01f01 100644 --- a/Random/App/Resources/es.lproj/Localizable.strings +++ b/Random/App/Resources/es.lproj/Localizable.strings @@ -25,6 +25,7 @@ "can_generate_random_contact" = "se puede generar un contacto aleatorio de una lista"; "can_generate_random_lottery_ticket" = "se puede generar un lote aleatorio para participar en la lotería"; "can_generate_random_movie" = "se puede generar una película aleatoria"; +"can_generate_random_series" = "Se puede generar una serie aleatoria"; "can_generate_random_password" = "se puede generar una contraseña aleatoria para el registro"; "can_generate_random_photo_filter" = "se puede generar un filtro aleatorio para la foto"; "can_participate_in_weekly_prize_draw" = "se Puede participar en el sorteo semanal"; @@ -216,6 +217,7 @@ "select_coin_style" = "Seleccionar estilo de moneda"; "select_icon" = "Seleccione un icono"; "selen" = "Selenio"; +"series" = "Seriales"; "september" = "Septiembre"; "settings" = "Configuraciones"; "seven_days_free" = "7 días gratis"; @@ -258,6 +260,7 @@ "vendor_identifier" = "id del proveedor"; "violet_lemon" = "violeta-limón"; "walk" = "caminar"; +"watchTrailer" = "Ver tráiler"; "wednesday" = "Miércoles"; "yes" = "Sí"; "yes_or_no" = "Sí o No"; diff --git a/Random/App/Resources/it.lproj/Localizable.strings b/Random/App/Resources/it.lproj/Localizable.strings index 4d873986..62214ce7 100644 --- a/Random/App/Resources/it.lproj/Localizable.strings +++ b/Random/App/Resources/it.lproj/Localizable.strings @@ -25,6 +25,7 @@ "can_generate_random_contact" = "è possibile generare un contatto casuale dall'elenco"; "can_generate_random_lottery_ticket" = "è possibile generare un lotto casuale per partecipare alla lotteria"; "can_generate_random_movie" = "è possibile generare un filmato casuale"; +"can_generate_random_series" = "È possibile generare una serie casuale"; "can_generate_random_password" = "è possibile generare una password casuale per la registrazione"; "can_generate_random_photo_filter" = "è possibile generare un filtro casuale per le foto"; "can_participate_in_weekly_prize_draw" = "puoi partecipare all'estrazione settimanale dei Premi"; @@ -216,6 +217,7 @@ "select_coin_style" = "Seleziona stile moneta"; "select_icon" = "Scegli un'icona"; "selen" = "selenio"; +"series" = "Serials"; "september" = "settembre"; "settings" = "Impostazioni"; "seven_days_free" = "7 giorni gratis"; @@ -258,6 +260,7 @@ "vendor_identifier" = "ID fornitore"; "violet_lemon" = "Viola-limone"; "walk" = "Passeggiata"; +"watchTrailer" = "Guarda il trailer"; "wednesday" = "Mercoledì"; "yes" = "Sì"; "yes_or_no" = "Sì o no"; diff --git a/Random/App/Resources/ru.lproj/Localizable.strings b/Random/App/Resources/ru.lproj/Localizable.strings index 548549ee..11aec35e 100644 --- a/Random/App/Resources/ru.lproj/Localizable.strings +++ b/Random/App/Resources/ru.lproj/Localizable.strings @@ -25,6 +25,7 @@ "can_generate_random_contact" = "Можно генерировать рандомный контакт из списка"; "can_generate_random_lottery_ticket" = "Можно генерировать рандомный лот для участия в лотереи"; "can_generate_random_movie" = "Можно генерировать рандомный фильм"; +"can_generate_random_series" = "Можно генерировать рандомный сериал"; "can_generate_random_password" = "Можно генерировать рандомный пароль для регистрации"; "can_generate_random_photo_filter" = "Можно генерировать рандомный фильтр для фото"; "can_participate_in_weekly_prize_draw" = "Можно участвовать в еженедельном розыгрыше призов"; @@ -216,6 +217,7 @@ "select_coin_style" = "Выбрать стиль монеты"; "select_icon" = "Выберите иконку"; "selen" = "Селен"; +"series" = "Сериалы"; "september" = "Сентябрь"; "settings" = "Настройки"; "seven_days_free" = "7 дней бесплатно"; @@ -258,6 +260,7 @@ "vendor_identifier" = "Идентийфикатор поставщика"; "violet_lemon" = "Фиолотово-лимонный"; "walk" = "Прогулка"; +"watchTrailer" = "Смотреть трейлер"; "wednesday" = "Среда"; "yes" = "Да"; "yes_or_no" = "Да или Нет"; diff --git a/Random/App/Sources/ApplicationServices/DeepLinkService/DeepLinkType.swift b/Random/App/Sources/ApplicationServices/DeepLinkService/DeepLinkType.swift index 11577e14..08cc3848 100644 --- a/Random/App/Sources/ApplicationServices/DeepLinkService/DeepLinkType.swift +++ b/Random/App/Sources/ApplicationServices/DeepLinkService/DeepLinkType.swift @@ -81,6 +81,8 @@ enum DeepLinkType: CaseIterable, UserDefaultsCodable { return "settings_share_app" case .settingsfeedBackButton: return "settings_feed_back_button" + case .series: + return "series_screen" } } @@ -187,4 +189,7 @@ enum DeepLinkType: CaseIterable, UserDefaultsCodable { /// Раздел мемов case memes + + /// Раздел `Сериалы` + case series } diff --git a/Random/App/Sources/ApplicationServices/MetricsService/MetricsnSections.swift b/Random/App/Sources/ApplicationServices/MetricsService/MetricsnSections.swift index 83b3d02c..77df54bc 100644 --- a/Random/App/Sources/ApplicationServices/MetricsService/MetricsnSections.swift +++ b/Random/App/Sources/ApplicationServices/MetricsService/MetricsnSections.swift @@ -49,6 +49,7 @@ enum MetricsSections: String { case memes = "Раздел Мемов" case onboarding = "Раздел Onboarding" case sales = "Sales" + case seriesScreen = "Series" // Настройки главного экрана case mainSettingsScreen = "Настройки главного экрана" diff --git a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Abstractions/MainScreenAbstractions.swift b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Abstractions/MainScreenAbstractions.swift index 53d69962..0ca80c2e 100644 --- a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Abstractions/MainScreenAbstractions.swift +++ b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Abstractions/MainScreenAbstractions.swift @@ -108,6 +108,9 @@ protocol MainScreenModuleOutput: AnyObject { /// Открыть раздел `Мемы` func openMemes() + + /// Открыть раздел `Сериалы` + func openSeries() /// Была нажата кнопка (настройки) func settingButtonAction() diff --git a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Factory/MainScreenFactory.swift b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Factory/MainScreenFactory.swift index 92a1bec6..21a6e355 100644 --- a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Factory/MainScreenFactory.swift +++ b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Factory/MainScreenFactory.swift @@ -292,6 +292,14 @@ extension MainScreenFactory { isPremium: true, advLabel: .none )) + case .series: + allSections.append(MainScreenModel.Section( + type: section, + isEnabled: true, + isHidden: false, + isPremium: true, + advLabel: .none + )) } } @@ -543,6 +551,14 @@ extension MainScreenFactory { isPremium: model.isPremium, advLabel: self.setLabelFrom(featureToggleRawValue: labelString, oldLabel: model.advLabel) )) + case .series: + cardSections.append(MainScreenModel.Section( + type: model.type, + isEnabled: model.isEnabled, + isHidden: self.ifDebugFeatureSectionIsHidden(isHidenSection), + isPremium: model.isPremium, + advLabel: self.setLabelFrom(featureToggleRawValue: labelString, oldLabel: model.advLabel) + )) } } DispatchQueue.main.async { diff --git a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+Appearance.swift b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+Appearance.swift index e0ba9ebb..0db1d8e5 100644 --- a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+Appearance.swift +++ b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+Appearance.swift @@ -31,7 +31,8 @@ extension MainScreenModel { let imageFiltersDescriptionForNoPremiumAccess = RandomStrings.Localizable.canGenerateRandomPhotoFilter let filmsDescriptionForNoPremiumAccess = RandomStrings.Localizable.canGenerateRandomMovie let nickNameDescriptionForNoPremiumAccess = RandomStrings.Localizable.generateRandomNickname - + let seriesDescriptionForNoPremiumAccess = RandomStrings.Localizable.canGenerateRandomSeries + let imageCardTeam = "person.circle" let titleCardTeam = RandomStrings.Localizable.teams @@ -79,6 +80,9 @@ extension MainScreenModel { let imageFilms = "film" let titleFilms = RandomStrings.Localizable.movies + + let imageSeries = "film.stack" + let titleSeries = RandomStrings.Localizable.series let imageNickName = "square.and.pencil" let titleNickName = RandomStrings.Localizable.nickname diff --git a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+SectionType.swift b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+SectionType.swift index d7999c08..08582443 100644 --- a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+SectionType.swift +++ b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Models/MainScreenModel+SectionType.swift @@ -86,6 +86,8 @@ extension MainScreenModel { return appearance.titleTruthOrDareOther case .memes: return appearance.titleMemes + case .series: + return appearance.titleSeries } } @@ -149,6 +151,8 @@ extension MainScreenModel { return appearance.imageTruthOrDare case .memes: return appearance.imageMemes + case .series: + return appearance.imageSeries } } @@ -212,6 +216,8 @@ extension MainScreenModel { return appearance.fortuneWheelDescriptionForNoPremiumAccess case .memes: return appearance.memesDescriptionForNoPremiumAccess + case .series: + return appearance.seriesDescriptionForNoPremiumAccess } } @@ -264,6 +270,9 @@ extension MainScreenModel { /// Раздел `Фильмы` case films + + /// Раздел `Сериалы` + case series /// Раздел `Никнейм` case nickName diff --git a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Presenter/MainScreenViewController.swift b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Presenter/MainScreenViewController.swift index 9708e176..b0033d77 100644 --- a/Random/App/Sources/Modules/MainScreen/MainScreenModule/Presenter/MainScreenViewController.swift +++ b/Random/App/Sources/Modules/MainScreen/MainScreenModule/Presenter/MainScreenViewController.swift @@ -119,6 +119,10 @@ final class MainScreenViewController: MainScreenModule { // MARK: - MainScreenViewOutput extension MainScreenViewController: MainScreenViewOutput { + func openSeries() { + moduleOutput?.openSeries() + } + func openMemes() { moduleOutput?.openMemes() } diff --git a/Random/App/Sources/Modules/MainScreen/MainScreenModule/View/MainScreenView.swift b/Random/App/Sources/Modules/MainScreen/MainScreenModule/View/MainScreenView.swift index 1eb5c553..bc3a88e4 100644 --- a/Random/App/Sources/Modules/MainScreen/MainScreenModule/View/MainScreenView.swift +++ b/Random/App/Sources/Modules/MainScreen/MainScreenModule/View/MainScreenView.swift @@ -95,6 +95,9 @@ protocol MainScreenViewOutput: AnyObject { /// Открыть раздел `Мемы` func openMemes() + + /// Открыть раздел `Сериалы` + func openSeries() /// Нет премиум доступа /// - Parameter section: Секция на главном экране @@ -257,6 +260,8 @@ extension MainScreenView: UICollectionViewDelegate { output?.openTruthOrDare() case .memes: output?.openMemes() + case .series: + output?.openSeries() } } } diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Abstractions/SeriesScreenAbstractions.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Abstractions/SeriesScreenAbstractions.swift new file mode 100644 index 00000000..3593bb81 --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Abstractions/SeriesScreenAbstractions.swift @@ -0,0 +1,32 @@ +// +// SeriesScreenAbstractions.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// + +import FancyUIKit + +/// События которые отправляем из `SeriesScreenModule` в `Coordinator` +public protocol SeriesScreenModuleOutput: AnyObject { + + /// Что-то пошло не так + func somethingWentWrong() + + /// Проиграть трайлер по ссылке + /// - Parameter url: Ссылка на трейлер + func playTrailerActionWith(url: String) + + /// Модуль был закрыт + func moduleClosed() +} + +/// События которые отправляем из `Coordinator` в `SeriesScreenModule` +public protocol SeriesScreenModuleInput { + + /// События которые отправляем из `SeriesScreenModule` в `Coordinator` + var moduleOutput: SeriesScreenModuleOutput? { get set } +} + +/// Готовый модуль `SeriesScreenModule` +public typealias SeriesScreenModule = ViewController & SeriesScreenModuleInput diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Assembly/SeriesScreenAssembly.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Assembly/SeriesScreenAssembly.swift new file mode 100644 index 00000000..b600f8a8 --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Assembly/SeriesScreenAssembly.swift @@ -0,0 +1,31 @@ +// +// SeriesScreenAssembly.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// + +import Foundation + +/// Сборщик `SeriesScreen` +final class SeriesScreenAssembly { + + /// Собирает модуль `SeriesScreen` + /// - Parameter services: Сервисы приложения + /// - Returns: Cобранный модуль `SeriesScreen` + func createModule(services: ApplicationServices) -> SeriesScreenModule { + let view = SeriesScreenView() + let factory = SeriesScreenFactory() + let interactor = SeriesScreenInteractor(services: services, factory: factory) + let presenter = SeriesScreenViewController( + moduleView: view, + interactor: interactor, + factory: factory + ) + + view.output = presenter + interactor.output = presenter + factory.output = presenter + return presenter + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Factory/SeriesScreenFactory.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Factory/SeriesScreenFactory.swift new file mode 100644 index 00000000..27aac66b --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Factory/SeriesScreenFactory.swift @@ -0,0 +1,102 @@ +// +// SeriesScreenFactory.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// + +import Foundation + +/// Cобытия которые отправляем из Factory в Presenter +protocol SeriesScreenFactoryOutput: AnyObject {} + +/// Cобытия которые отправляем от Presenter к Factory +protocol SeriesScreenFactoryInput { + + /// Создать модель данных с Русскими сериалами + /// - Parameters: + /// - modelsDTO: ДТО Русских сериалов + /// - image: Изображение + func createRusSeriesModelFrom(_ modelsDTO: SeriesScreenRusModelDTO.Series, + image: Data?) -> SeriesScreenModel + + /// Создать модель данных с Иностранных сериалов + /// - Parameters: + /// - modelsDTO: ДТО Иностранных сериалов + /// - image: Изображение + func createEngSeriesModelFrom(_ modelsDTO: SeriesScreenEngModelDTO, + image: Data?) -> SeriesScreenModel + + /// Создать ссылку для Яндекс поиска + /// - Parameter text: Текст для поиска + func createYandexLinkWith(text: String) -> String + + /// Создать ссылку для Google поиска + /// - Parameter text: Текст для поиска + func createGoogleLinkWith(text: String) -> String +} + +/// Фабрика +final class SeriesScreenFactory: SeriesScreenFactoryInput { + + // MARK: - Internal properties + + weak var output: SeriesScreenFactoryOutput? + + // MARK: - Internal func + + func createRusSeriesModelFrom(_ modelsDTO: SeriesScreenRusModelDTO.Series, image: Data?) -> SeriesScreenModel { + SeriesScreenModel(name: modelsDTO.nameRu, + description: "\(modelsDTO.genres.first?.genre ?? "") \(modelsDTO.year)", + image: image) + } + + func createEngSeriesModelFrom(_ modelsDTO: SeriesScreenEngModelDTO, image: Data?) -> SeriesScreenModel { + let date = formatDate(from: modelsDTO.premiered ?? "") + return SeriesScreenModel(name: modelsDTO.name ?? "", + description: "\(modelsDTO.genres?.first ?? "") \(String(describing: date))", + image: image) + } + + func createYandexLinkWith(text: String) -> String { + let appearance = Appearance() + let request = "\(appearance.yandexRequest + appearance.watchTrailer) \(text)" + let formatRequest = request.replacingOccurrences(of: " ", with: "+") + return formatRequest + } + + func createGoogleLinkWith(text: String) -> String { + let appearance = Appearance() + let request = "\(appearance.googleRequest + appearance.watchTrailer) \(text)" + let formatRequest = request.replacingOccurrences(of: " ", with: "+") + return formatRequest + } +} + +// MARK: - Private + +private extension SeriesScreenFactory { + func formatDate(from date: String) -> String { + let formattorToDate = DateFormatter() + formattorToDate.dateFormat = "yyyy-MM-dd" + + guard let date = formattorToDate.date(from: date) else { + return "" + } + + let formattorToString = DateFormatter() + formattorToString.dateFormat = "yyyy" + let newStringDate = formattorToString.string(from: date) + return newStringDate + } +} + +// MARK: - Appearance + +private extension SeriesScreenFactory { + struct Appearance { + let yandexRequest = "http://yandex.ru/search/?text=" + let googleRequest = "https://www.google.com/search?q=" + let watchTrailer = RandomStrings.Localizable.watchTrailer + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Interactor/SeriesScreenInteractor.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Interactor/SeriesScreenInteractor.swift new file mode 100644 index 00000000..f4657ab2 --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Interactor/SeriesScreenInteractor.swift @@ -0,0 +1,313 @@ +// +// SeriesScreenInteractor.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// + +import UIKit +import FancyNetwork +import FancyUIKit + +/// События которые отправляем из Interactor в Presenter +protocol SeriesScreenInteractorOutput: AnyObject { + + /// Был получен контент с сериалами + /// - Parameter model: Модель данных + func didReceiveSeries(model: SeriesScreenModel) + + /// Что-то пошло не так + func somethingWentWrong() + + /// Запустить лоадер + func startLoader() + + /// Остановить лоадер + func stopLoader() +} + +/// События которые отправляем от Presenter к Interactor +protocol SeriesScreenInteractorInput { + + /// Загрузить сериал + func loadSeries() + + /// Сгенерировать сериал + func generateSeries() + + /// Локация Россия + func isRuslocale() -> Bool +} + +/// Интерактор +final class SeriesScreenInteractor: SeriesScreenInteractorInput { + + // MARK: - Internal properties + + weak var output: SeriesScreenInteractorOutput? + + // MARK: - Private property + + private let factory: SeriesScreenFactoryInput + private let services: ApplicationServices + private var storageService: StorageService + private var seriesScreenModel: [SeriesScreenModel] = [] + + // MARK: - Initialization + + /// - Parameters: + /// - services: Сервисы приложения + /// - factory: фабрика + init(services: ApplicationServices, + factory: SeriesScreenFactoryInput) { + self.services = services + self.factory = factory + storageService = services.storageService + } + + // MARK: - Internal func + + func isRuslocale() -> Bool { + guard let localeType = CountryType.getCurrentCountryType() else { + return false + } + + switch localeType { + case .ru: + return true + default: + return false + } + } + + func generateSeries() { + getSeriesByLocale(isRussian: isRuslocale()) + services.buttonCounterService.onButtonClick() + } + + func loadSeries() { + seriesScreenModel = getSeriesData() + + if !seriesScreenModel.isEmpty, let seriesModel = getGenerateSeries() { + output?.didReceiveSeries(model: seriesModel) + return + } + if isRuslocale() { + loadRusSeries { _ in } + } else { + loadEngSeries { _ in } + } + } +} + +// MARK: - Private + +private extension SeriesScreenInteractor { + + func loadEngSeries(completion: @escaping (_ seriesModels: [SeriesScreenModel]) -> Void) { + output?.startLoader() + loadListEngSeriesDTO { [weak self] seriesModelsDTO in + self?.loadImageWith(seriesDTO: seriesModelsDTO, + isRussian: self?.isRuslocale() ?? false) { [weak self] seriesModels in + self?.seriesScreenModel = seriesModels + completion(seriesModels) + self?.output?.stopLoader() + } + } + } + + func loadRusSeries(completion: @escaping (_ seriesModels: [SeriesScreenModel]) -> Void) { + output?.startLoader() + loadListRusSeriesDTO { [weak self] seriesModelsDTO in + self?.loadImageWith(seriesDTO: seriesModelsDTO, + isRussian: self?.isRuslocale() ?? false) { [weak self] seriesModels in + self?.seriesScreenModel = seriesModels + completion(seriesModels) + self?.output?.stopLoader() + } + } + } + + func getSeriesByLocale(isRussian: Bool) { + if !seriesScreenModel.isEmpty, + let seriesModel = getGenerateSeries() { + output?.didReceiveSeries(model: seriesModel) + } else if isRussian { + loadRusSeries { [weak self] seriesModels in + guard let seriesModel = seriesModels.first else { + self?.output?.somethingWentWrong() + return + } + self?.output?.didReceiveSeries(model: seriesModel) + } + } else { + loadEngSeries { [weak self] seriesModels in + guard let seriesModel = seriesModels.first else { + self?.output?.somethingWentWrong() + return + } + self?.output?.didReceiveSeries(model: seriesModel) + } + } + } + + func loadListEngSeriesDTO(completion: @escaping ([SeriesScreenEngModelDTO]) -> Void) { + let appearance = Appearance() + let urlString = "\(appearance.engDomenSeriesUrl)\(appearance.engSeriesEndPoint)" + let engSeriesType = SeriesScreenEngType.allCases.shuffled().first ?? .allSeries + + services.networkService.performRequestWith( + urlString: urlString, + queryItems: [ + URLQueryItem(name: "page", + value: "\(Int.random(in: 0...engSeriesType.pageMaxCount))"), + ], + httpMethod: .get, + headers: [] + ) { [weak self] result in + switch result { + case let .success(data): + DispatchQueue.main.async { + guard let model = self?.services.networkService.map(data, to: [SeriesScreenEngModelDTO].self) else { + self?.output?.somethingWentWrong() + return + } + completion(model) + } + case .failure: + DispatchQueue.main.async { + completion([]) + self?.output?.somethingWentWrong() + } + } + } + } + + func loadListRusSeriesDTO(completion: @escaping ([SeriesScreenRusModelDTO.Series]) -> Void) { + let appearance = Appearance() + let urlString = "\(appearance.rusDomenKinopoisk)\(appearance.rusEndPoint)" + let randomSeriesType = SeriesScreenRusType.allCases.shuffled().first ?? .tvSeries + + services.networkService.performRequestWith( + urlString: urlString, + queryItems: [ + URLQueryItem(name: "type", + value: randomSeriesType.rawValue), + URLQueryItem(name: "ratingFrom", + value: appearance.rusMinRating), + URLQueryItem(name: "page", + value: "\(Int.random(in: 1...randomSeriesType.pageMaxCount))") + ], + httpMethod: .get, + headers: [ + .additionalHeaders(set: [ + (key: appearance.rusHeaderAPIKey, value: appearance.rusAPIKey) + ]), + .contentTypeJson + ] + ) { [weak self] result in + switch result { + case let .success(data): + DispatchQueue.main.async { + guard let model = self?.services.networkService.map(data, to: SeriesScreenRusModelDTO.self) else { + self?.output?.somethingWentWrong() + return + } + completion(model.items) + } + case .failure: + DispatchQueue.main.async { + completion([]) + self?.output?.somethingWentWrong() + } + } + } + } + + func loadImageWith(seriesDTO: [Any], + isRussian: Bool, + completion: @escaping (_ seriesModels: [SeriesScreenModel]) -> Void) { + var seriesModels: [SeriesScreenModel] = [] + let dispatchGroup = DispatchGroup() + seriesDTO.forEach { _ in + dispatchGroup.enter() + } + + seriesDTO.forEach { modelDTO in + var url = "" + + if isRussian, let model = modelDTO as? SeriesScreenRusModelDTO.Series { + url = model.posterUrl + } + + if !isRussian, let model = modelDTO as? SeriesScreenEngModelDTO { + url = model.image?.original ?? "" + } + + services.networkService.performRequestWith(urlString: url, + queryItems: [], + httpMethod: .get, + headers: []) { [weak self] result in + guard let self = self else { + return + } + switch result { + case let .success(imageData): + if isRussian, let model = modelDTO as? SeriesScreenRusModelDTO.Series { + let seriesScreenModel = self.factory.createRusSeriesModelFrom(model, image: imageData) + seriesModels.append(seriesScreenModel) + } + + if !isRussian, let model = modelDTO as? SeriesScreenEngModelDTO { + let seriesScreenModel = self.factory.createEngSeriesModelFrom(model, image: imageData) + seriesModels.append(seriesScreenModel) + } + dispatchGroup.leave() + case .failure: + dispatchGroup.leave() + DispatchQueue.main.async { + self.output?.somethingWentWrong() + } + } + } + } + dispatchGroup.notify(queue: .main) { + completion(seriesModels) + } + } + + func getGenerateSeries() -> SeriesScreenModel? { + seriesScreenModel.shuffle() + guard let seriesModel = seriesScreenModel.first else { + return nil + } + seriesScreenModel.removeFirst() + saveSeriesData(seriesScreenModel) + return seriesModel + } + + func getSeriesData() -> [SeriesScreenModel] { + storageService.getData(from: [SeriesScreenModel].self) ?? [] + } + + func saveSeriesData(_ data: [SeriesScreenModel]?) { + DispatchQueue.global(qos: .userInteractive).async { [weak self] in + self?.storageService.saveData(data) + } + } +} + +// MARK: - Appearance + +private extension SeriesScreenInteractor { + struct Appearance { + let rusDomenKinopoisk = "https://kinopoiskapiunofficial.tech" + let rusAPIKey = SecretsAPI.apiKeyKinopoisk + let rusHeaderAPIKey = "X-API-KEY" + let rusEndPoint = "/api/v2.2/films" + let rusMinRating = "4" + + let engDomenSeriesUrl = "https://api.tvmaze.com" + let engSeriesEndPoint = "/shows" + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenEngModelDTO.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenEngModelDTO.swift new file mode 100644 index 00000000..0cdc0ce8 --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenEngModelDTO.swift @@ -0,0 +1,32 @@ +// +// SeriesScreenEngModelDTO.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// Copyright © 2023 SosinVitalii.com. All rights reserved. +// + +import Foundation + +struct SeriesScreenEngModelDTO: Codable { + + // MARK: - Series + + /// Название сериала + let name: String? + + /// Год выпуска сериала + let premiered: String? + + /// URL Картинки + let image: SeriesImage? + + /// Жанры сериала + let genres: [String]? + + // MARK: - SeriesImage + + struct SeriesImage: Codable { + let original: String? + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenEngType.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenEngType.swift new file mode 100644 index 00000000..bedab763 --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenEngType.swift @@ -0,0 +1,23 @@ +// +// SeriesScreenEngType.swift +// Random +// +// Created by Artem Pavlov on 21.11.2023. +// Copyright © 2023 SosinVitalii.com. All rights reserved. +// + +import Foundation + +enum SeriesScreenEngType: CaseIterable { + + /// Все сериалы + case allSeries + + /// Максимальное количество страниц для каждого кейса + var pageMaxCount: Int { + switch self { + case .allSeries: + return 3 + } + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenRusModelDTO.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenRusModelDTO.swift new file mode 100644 index 00000000..bbafdd7f --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenRusModelDTO.swift @@ -0,0 +1,40 @@ +// +// SeriesScreenRusModelDTO.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// Copyright © 2023 SosinVitalii.com. All rights reserved. +// + +import Foundation + +struct SeriesScreenRusModelDTO: Codable { + + /// Список сериалов + let items: [Series] + + // MARK: - Series + + struct Series: Codable { + + /// Название сериала + let nameRu: String + + /// Жанры сериала + let genres: [Genre] + + /// Год выпуска сериала + let year: Int + + /// URL Картинки + let posterUrl: String + } + + // MARK: - Genre + + struct Genre: Codable { + + /// Жанр сериала + let genre: String + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenRusType.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenRusType.swift new file mode 100644 index 00000000..93e633e3 --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/DTO/SeriesScreenRusType.swift @@ -0,0 +1,38 @@ +// +// SeriesScreenRusType.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// Copyright © 2023 SosinVitalii.com. All rights reserved. +// + +import Foundation + +enum SeriesScreenRusType: CaseIterable { + + /// Сериалы + case tvSeries + + /// Мини-сериалы + case miniSeries + + /// Название каждого кейса + var rawValue: String { + switch self { + case .tvSeries: + return "TV_SERIES" + case .miniSeries: + return "MINI_SERIES" + } + } + + /// Максимальное количество страниц для каждого кейса + var pageMaxCount: Int { + switch self { + case .tvSeries: + return 5 + case .miniSeries: + return 5 + } + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/SeriesScreenModel.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/SeriesScreenModel.swift new file mode 100644 index 00000000..97304c9e --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Models/SeriesScreenModel.swift @@ -0,0 +1,24 @@ +// +// SeriesScreenModel.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// Copyright © 2023 SosinVitalii.com. All rights reserved. +// + +import Foundation + +struct SeriesScreenModel: UserDefaultsCodable { + + /// Название сериала + let name: String + + /// Описание сериала + let description: String + + /// Изображение + let image: Data? + + /// Ссылка на трейлер сериалов + var trailerUrl: String? +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Presenter/SeriesScreenViewController.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Presenter/SeriesScreenViewController.swift new file mode 100644 index 00000000..0ca6dd58 --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/Presenter/SeriesScreenViewController.swift @@ -0,0 +1,162 @@ +// +// SeriesScreenViewController.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// + +import UIKit +import FancyUIKit + +/// Презентер +final class SeriesScreenViewController: SeriesScreenModule { + + // MARK: - Internal properties + + weak var moduleOutput: SeriesScreenModuleOutput? + + // MARK: - Private properties + + private let interactor: SeriesScreenInteractorInput + private let moduleView: SeriesScreenViewProtocol + private let factory: SeriesScreenFactoryInput + private let impactFeedback = UIImpactFeedbackGenerator(style: .light) + + // MARK: - Initialization + + /// - Parameters: + /// - moduleView: вью + /// - interactor: интерактор + /// - factory: фабрика + init(moduleView: SeriesScreenViewProtocol, + interactor: SeriesScreenInteractorInput, + factory: SeriesScreenFactoryInput) { + self.moduleView = moduleView + self.interactor = interactor + self.factory = factory + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Life cycle + + override func loadView() { + view = moduleView + } + + override func viewDidLoad() { + super.viewDidLoad() + + interactor.loadSeries() + } + + override func finishFlow() { + moduleOutput?.moduleClosed() + } +} + +// MARK: - SeriesScreenViewOutput + +extension SeriesScreenViewController: SeriesScreenViewOutput { + + func generateSeriesAction() { + interactor.generateSeries() + } +} + +// MARK: - SeriesScreenInteractorOutput + +extension SeriesScreenViewController: SeriesScreenInteractorOutput { + func startLoader() { + moduleView.startLoader() + } + + func didReceiveSeries(model: SeriesScreenModel) { + moduleView.updateContentWith(model: model) + + if moduleView.getSeriesName() == nil { + setNavigationBar(isPlayTrailerEnabled: false) + } else { + setNavigationBar(isPlayTrailerEnabled: true) + } + } + + func somethingWentWrong() { + moduleOutput?.somethingWentWrong() + } + + func stopLoader() { + moduleView.stopLoader() + } +} + +// MARK: - SeriesScreenFactoryOutput + +extension SeriesScreenViewController: SeriesScreenFactoryOutput {} + +// MARK: - Private + +private extension SeriesScreenViewController { + func setNavigationBar(isPlayTrailerEnabled: Bool) { + let appearance = Appearance() + + navigationItem.largeTitleDisplayMode = .never + + let playTrailerImageName = isPlayTrailerEnabled + ? appearance.playTrailerImageEnabledName + : appearance.playTrailerImageDisabledName + + let playTrailerButton = UIBarButtonItem.menuButton(self, + action: #selector(playTrailerAction), + imageName: playTrailerImageName, + size: CGSize(width: 33, + height: 28)) + playTrailerButton.isEnabled = isPlayTrailerEnabled + navigationItem.rightBarButtonItems = [playTrailerButton] + } + + @objc + func playTrailerAction() { + guard let seriesName = moduleView.getSeriesName() else { + moduleOutput?.somethingWentWrong() + return + } + + let url = interactor.isRuslocale() + ? factory.createYandexLinkWith(text: seriesName) + : factory.createGoogleLinkWith(text: seriesName) + moduleOutput?.playTrailerActionWith(url: url) + impactFeedback.impactOccurred() + } +} + +// MARK: - UIBarButtonItem + +private extension UIBarButtonItem { + static func menuButton(_ target: Any?, + action: Selector, + imageName: String, + size: CGSize) -> UIBarButtonItem { + let button = UIButton(type: .system) + button.setImage(UIImage(named: imageName)?.withRenderingMode(.alwaysOriginal), for: .normal) + button.addTarget(target, action: action, for: .touchUpInside) + + let menuBarItem = UIBarButtonItem(customView: button) + menuBarItem.customView?.translatesAutoresizingMaskIntoConstraints = false + menuBarItem.customView?.heightAnchor.constraint(equalToConstant: size.height).isActive = true + menuBarItem.customView?.widthAnchor.constraint(equalToConstant: size.width).isActive = true + return menuBarItem + } +} + +// MARK: - Appearance + +private extension SeriesScreenViewController { + struct Appearance { + let playTrailerImageEnabledName = RandomAsset.playTrailerEnabled.name + let playTrailerImageDisabledName = RandomAsset.playTrailerDisabled.name + } +} diff --git a/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/View/SeriesScreenView.swift b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/View/SeriesScreenView.swift new file mode 100644 index 00000000..96d13d8b --- /dev/null +++ b/Random/App/Sources/Modules/MainScreen/SeriesScreenModule/View/SeriesScreenView.swift @@ -0,0 +1,149 @@ +// +// SeriesScreenView.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// + +import UIKit +import FancyUIKit +import FancyStyle +import Lottie + +/// События которые отправляем из View в Presenter +protocol SeriesScreenViewOutput: AnyObject { + + /// Сгенерировать сериал + func generateSeriesAction() +} + +/// События которые отправляем от Presenter ко View +protocol SeriesScreenViewInput { + + /// Обновить контент + /// - Parameter model: Модель + func updateContentWith(model: SeriesScreenModel) + + /// Запустить лоадер + func startLoader() + + /// Остановить лоадер + func stopLoader() + + /// Получить название сериала + func getSeriesName() -> String? +} + +/// Псевдоним протокола UIView & SeriesScreenViewInput +typealias SeriesScreenViewProtocol = UIView & SeriesScreenViewInput + +/// View для экрана +final class SeriesScreenView: SeriesScreenViewProtocol { + + // MARK: - Internal properties + + weak var output: SeriesScreenViewOutput? + + // MARK: - Private properties + + private let seriesView = FilmView() + private let activityIndicator = LottieAnimationView(name: Appearance().loaderImage) + private var cacheSeriesName: String? + + // MARK: - Initialization + + override init(frame: CGRect) { + super.init(frame: frame) + + configureLayout() + applyDefaultBehavior() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Internal func + + func updateContentWith(model: SeriesScreenModel) { + cacheSeriesName = model.name + var backgroundImage: UIImage? + if let imageData = model.image { + backgroundImage = UIImage(data: imageData) + } + + seriesView.configureWith(backgroundImage: backgroundImage, + title: model.name, + description: model.description, + buttonTitle: Appearance().buttonTitle, + buttonAction: { [weak self] in + self?.output?.generateSeriesAction() + }) + } + + func startLoader() { + activityIndicator.isHidden = false + activityIndicator.play() + seriesView.setButtonIsEnabled(false) + } + + func stopLoader() { + activityIndicator.isHidden = true + activityIndicator.stop() + seriesView.setButtonIsEnabled(true) + } + + func getSeriesName() -> String? { + cacheSeriesName + } +} + +// MARK: - Private + +private extension SeriesScreenView { + func configureLayout() { + [seriesView, activityIndicator].forEach { + $0.translatesAutoresizingMaskIntoConstraints = false + addSubview($0) + } + + NSLayoutConstraint.activate([ + seriesView.leadingAnchor.constraint(equalTo: leadingAnchor), + seriesView.topAnchor.constraint(equalTo: topAnchor), + seriesView.trailingAnchor.constraint(equalTo: trailingAnchor), + seriesView.bottomAnchor.constraint(equalTo: bottomAnchor), + + activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor), + activityIndicator.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height * 0.5) + ]) + } + + func applyDefaultBehavior() { + backgroundColor = fancyColor.darkAndLightTheme.primaryWhite + seriesView.backgroundColor = fancyColor.darkAndLightTheme.primaryWhite + + activityIndicator.isHidden = true + activityIndicator.contentMode = .scaleAspectFit + activityIndicator.loopMode = .loop + activityIndicator.animationSpeed = Appearance().animationSpeed + + seriesView.configureWith(backgroundImage: nil, + title: nil, + description: nil, + buttonTitle: Appearance().buttonTitle, + buttonAction: { [weak self] in + self?.output?.generateSeriesAction() + }) + } +} + +// MARK: - Appearance + +private extension SeriesScreenView { + struct Appearance { + let buttonTitle = RandomStrings.Localizable.generate + let loaderImage = RandomAsset.filmsLoader.name + let animationSpeed: CGFloat = 0.5 + } +} diff --git a/Random/App/Sources/Navigation/MainScreenCoordinator/MainScreenCoordinator/MainScreenCoordinator.swift b/Random/App/Sources/Navigation/MainScreenCoordinator/MainScreenCoordinator/MainScreenCoordinator.swift index cdee3661..e8c40643 100644 --- a/Random/App/Sources/Navigation/MainScreenCoordinator/MainScreenCoordinator/MainScreenCoordinator.swift +++ b/Random/App/Sources/Navigation/MainScreenCoordinator/MainScreenCoordinator/MainScreenCoordinator.swift @@ -75,6 +75,7 @@ final class MainScreenCoordinator: MainScreenCoordinatorProtocol { private var passwordScreenCoordinator: PasswordScreenCoordinator? private var numberScreenCoordinator: NumberScreenCoordinator? private var settingsScreenCoordinator: MainSettingsScreenCoordinator? + private var seriesScreenCoordinator: SeriesScreenCoordinator? // MARK: - Initialization @@ -327,11 +328,24 @@ extension MainScreenCoordinator: MainScreenModuleOutput { filmsScreenCoordinator.finishFlow = { [weak self] in self?.filmsScreenCoordinator = nil } - + mainScreenModule?.removeLabelFromSection(type: .films) services.metricsService.track(event: .filmScreen) } - + + func openSeries() { + let seriesScreenCoordinator = SeriesScreenCoordinator(navigationController, + services) + self.seriesScreenCoordinator = seriesScreenCoordinator + seriesScreenCoordinator.start() + seriesScreenCoordinator.finishFlow = { [weak self] in + self?.seriesScreenCoordinator = nil + } + + mainScreenModule?.removeLabelFromSection(type: .series) + services.metricsService.track(event: .seriesScreen) + } + func openImageFilters() { let imageFiltersScreenCoordinator = ImageFiltersScreenCoordinator(navigationController, services) @@ -857,6 +871,8 @@ private extension MainScreenCoordinator { openImageFilters() case .films: openFilms() + case .series: + openSeries() case .nickName: openNickName() case .names: diff --git a/Random/App/Sources/Navigation/MainScreenCoordinator/SeriesScreenCoordinator/SeriesScreenCoordinator.swift b/Random/App/Sources/Navigation/MainScreenCoordinator/SeriesScreenCoordinator/SeriesScreenCoordinator.swift new file mode 100644 index 00000000..e529f9f5 --- /dev/null +++ b/Random/App/Sources/Navigation/MainScreenCoordinator/SeriesScreenCoordinator/SeriesScreenCoordinator.swift @@ -0,0 +1,104 @@ +// +// SeriesScreenCoordinator.swift +// Random +// +// Created by Artem Pavlov on 13.11.2023. +// + +import UIKit +import SafariServices + +/// Псевдоним протокола Coordinator & SeriesScreenCoordinatorInput +typealias SeriesScreenCoordinatorProtocol = Coordinator & SeriesScreenCoordinatorInput + +/// События которые отправляем из `текущего координатора` в `другой координатор` +protocol SeriesScreenCoordinatorOutput: AnyObject {} + +/// События которые отправляем из `другого координатора` в `текущий координатор` +protocol SeriesScreenCoordinatorInput { + + /// События которые отправляем из `текущего координатора` в `другой координатор` + var output: SeriesScreenCoordinatorOutput? { get set } +} + +// MARK: - SeriesScreenCoordinator + +/// Координатор `SeriesScreen` +final class SeriesScreenCoordinator: SeriesScreenCoordinatorProtocol { + + // MARK: - Internal variables + + var finishFlow: (() -> Void)? + weak var output: SeriesScreenCoordinatorOutput? + + // MARK: - Private property + + private var seriesScreenModule: SeriesScreenModule? + private var navigationController: UINavigationController + private let services: ApplicationServices + + // MARK: - Initialisation + + /// Ининциализатор + /// - Parameters: + /// - navigationController: Навигейшн контроллер + /// - services: Сервисы приложения + init(_ navigationController: UINavigationController, + _ services: ApplicationServices) { + self.navigationController = navigationController + self.services = services + } + + // MARK: - Life cycle + + func start() { + let seriesScreenModule = SeriesScreenAssembly().createModule(services: services) + self.seriesScreenModule = seriesScreenModule + self.seriesScreenModule?.moduleOutput = self + navigationController.pushViewController(seriesScreenModule, animated: true) + } +} + +// MARK: - SeriesScreenModuleOutput + +extension SeriesScreenCoordinator: SeriesScreenModuleOutput { + func moduleClosed() { + finishFlow?() + } + + func playTrailerActionWith(url: String) { + guard let encodedTexts = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), + let seriesUrl = URL(string: encodedTexts) else { + somethingWentWrong() + return + } + + let safariViewController = SFSafariViewController(url: seriesUrl) + navigationController.present(safariViewController, animated: true, completion: nil) + } + + func somethingWentWrong() { + services.notificationService.showNegativeAlertWith(title: Appearance().somethingWentWrong, + glyph: false, + timeout: nil, + active: {}) + } + + func resultLabelAction(text: String?) { + UIPasteboard.general.string = text + UIImpactFeedbackGenerator(style: .light).impactOccurred() + services.notificationService.showPositiveAlertWith(title: Appearance().copiedToClipboard, + glyph: true, + timeout: nil, + active: {}) + } +} + +// MARK: - Appearance + +private extension SeriesScreenCoordinator { + struct Appearance { + let somethingWentWrong = RandomStrings.Localizable.somethingWentWrong + let copiedToClipboard = RandomStrings.Localizable.copyToClipboard + } +} diff --git a/Random/App/Sources/System/SecretsAPI.swift b/Random/App/Sources/System/SecretsAPI.swift index a67e20a1..e5807287 100644 --- a/Random/App/Sources/System/SecretsAPI.swift +++ b/Random/App/Sources/System/SecretsAPI.swift @@ -11,7 +11,7 @@ import Foundation struct SecretsAPI { static let apiKeyYandexMetrica = "b4921e71-faf2-4bd3-8bea-e033a76457ae" static let apiKeyApphud = "API_KEY_HERE" - static let apiKeyKinopoisk = "API_KEY_HERE" - static let apiKeyMostPopularMovies = "API_KEY_HERE" + static let apiKeyKinopoisk = "f835989c-b489-4624-9209-6d93bfead535" + static let apiKeyMostPopularMovies = "f7321a82d8msh8cd3d6fb5db3824p1bef63jsn40adb1a66d30" static let fancyBackend = "API_KEY_HERE" }