From 0839b83f6fef0182dd84d4911e8661088df3284f Mon Sep 17 00:00:00 2001 From: Denis Shilovich Date: Tue, 31 Jan 2023 07:25:23 +0000 Subject: [PATCH] "Nicegram plus" release 1.1.9 --- .../Sources/Assistant/AssistantBuilder.swift | 1 - .../Assistant/AssistantInteractor.swift | 31 ++--- .../Assistant/AssistantViewController.swift | 1 - Nicegram/NGCopyProtectedContent/BUILD | 18 +++ .../Sources/CopyProtectedContent.swift | 56 ++++++++ .../Sources/LoadingView/LoadingView.swift | 89 +++++++++++-- .../PlaceholderState+Factory.swift | 22 +++ .../Placeholder/PlaceholderState.swift | 34 +++++ .../Placeholder/PlaceholderableView.swift | 25 ++++ .../Sources/DefaultPlaceholderView.swift | 19 ++- .../Sources/PlaceholderableView.swift | 14 +- .../CreateTicket/CreateTicketView.swift | 8 +- .../Presentation/Splash/SplashView.swift | 86 +++++------- .../Presentation/Splash/SplashViewModel.swift | 126 +++++++++++------- .../Presentation/Splash/SplashViewState.swift | 59 ++++++++ Nicegram/NGRemoteConfig/Sources/Helpers.swift | 6 + Nicegram/NGSecretMenu/BUILD | 1 + .../Sources/SecretMenuController.swift | 1 + .../Sources/ChatListController.swift | 10 -- submodules/GalleryUI/BUILD | 5 +- .../ChatItemGalleryFooterContentNode.swift | 14 ++ .../SyncCore_CacheStorageSettings.swift | 8 +- .../Sources/Utils/MessageUtils.swift | 5 - .../TelegramCore/Sources/Utils/NGUtils.swift | 16 --- .../Sources/Utils/PeerUtils.swift | 5 - submodules/TelegramUI/BUILD | 1 + .../Sources/StorageKeepSizeComponent.swift | 5 + .../Sources/StorageUsageScreen.swift | 3 +- .../ChatInterfaceStateContextMenus.swift | 29 +++- versions.json | 2 +- 30 files changed, 502 insertions(+), 198 deletions(-) create mode 100644 Nicegram/NGCopyProtectedContent/BUILD create mode 100644 Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift create mode 100644 Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState+Factory.swift create mode 100644 Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState.swift create mode 100644 Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderableView.swift create mode 100644 Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewState.swift diff --git a/Nicegram/NGAssistant/Sources/Assistant/AssistantBuilder.swift b/Nicegram/NGAssistant/Sources/Assistant/AssistantBuilder.swift index 5806591a549..1171b44035e 100644 --- a/Nicegram/NGAssistant/Sources/Assistant/AssistantBuilder.swift +++ b/Nicegram/NGAssistant/Sources/Assistant/AssistantBuilder.swift @@ -84,7 +84,6 @@ public class AssistantBuilderImpl: AssistantBuilder { ), getReferralLinkUseCase: GetReferralLinkUseCaseImpl(), initiateLoginWithTelegramUseCase: appContext.resolveInitiateLoginWithTelegramUseCase(), - getLotteryDataUseCase: appContext.resolveGetLotteryDataUseCase(), eventsLogger: LoggersFactory().createDefaultEventsLogger() ) interactor.output = presenter diff --git a/Nicegram/NGAssistant/Sources/Assistant/AssistantInteractor.swift b/Nicegram/NGAssistant/Sources/Assistant/AssistantInteractor.swift index 51e71fa31b9..6bf9be91bc7 100644 --- a/Nicegram/NGAssistant/Sources/Assistant/AssistantInteractor.swift +++ b/Nicegram/NGAssistant/Sources/Assistant/AssistantInteractor.swift @@ -28,6 +28,11 @@ protocol AssistantInteractorOutput { @available(iOS 13.0, *) class AssistantInteractor: AssistantInteractorInput { + + private struct Constants { + static let lotteryJackpot = Money(amount: 250000000, currency: .usd) + } + var output: AssistantInteractorOutput! var router: AssistantRouterInput! @@ -37,7 +42,6 @@ class AssistantInteractor: AssistantInteractorInput { private let getSpecialOfferUseCase: GetSpecialOfferUseCase private let getReferralLinkUseCase: GetReferralLinkUseCase private let initiateLoginWithTelegramUseCase: InitiateLoginWithTelegramUseCase - private let getLotteryDataUseCase: GetLotteryDataUseCase private let eventsLogger: EventsLogger private var deeplink: Deeplink? @@ -46,7 +50,7 @@ class AssistantInteractor: AssistantInteractorInput { private var cancellables = Set() - init(deeplink: Deeplink?, esimAuth: EsimAuth, userEsimsRepository: UserEsimsRepository, getCurrentUserUseCase: GetCurrentUserUseCase, getSpecialOfferUseCase: GetSpecialOfferUseCase, getReferralLinkUseCase: GetReferralLinkUseCase, initiateLoginWithTelegramUseCase: InitiateLoginWithTelegramUseCase, getLotteryDataUseCase: GetLotteryDataUseCase, eventsLogger: EventsLogger) { + init(deeplink: Deeplink?, esimAuth: EsimAuth, userEsimsRepository: UserEsimsRepository, getCurrentUserUseCase: GetCurrentUserUseCase, getSpecialOfferUseCase: GetSpecialOfferUseCase, getReferralLinkUseCase: GetReferralLinkUseCase, initiateLoginWithTelegramUseCase: InitiateLoginWithTelegramUseCase, eventsLogger: EventsLogger) { self.deeplink = deeplink self.esimAuth = esimAuth self.userEsimsRepository = userEsimsRepository @@ -54,7 +58,6 @@ class AssistantInteractor: AssistantInteractorInput { self.getSpecialOfferUseCase = getSpecialOfferUseCase self.getReferralLinkUseCase = getReferralLinkUseCase self.initiateLoginWithTelegramUseCase = initiateLoginWithTelegramUseCase - self.getLotteryDataUseCase = getLotteryDataUseCase self.eventsLogger = eventsLogger } @@ -62,7 +65,7 @@ class AssistantInteractor: AssistantInteractorInput { output.handleViewDidLoad() trySignInWithTelegram() fetchSpecialOffer() - subscribeToLotteryChange() + displayLotteryIfNeeded() } func onViewDidAppear() { @@ -195,23 +198,9 @@ private extension AssistantInteractor { } } - func subscribeToLotteryChange() { - guard !hideLottery else { return } - getLotteryDataUseCase.lotteryDataPublisher() - .receive(on: DispatchQueue.main) - .sink { [weak self] lotteryData in - guard let self else { return } - - if let lotteryData { - self.output.presentLottery(jackpot: lotteryData.currentDraw.jackpot) - self.output.presentLottery(true) - - AppCache.wasLotteryShown = true - } else { - self.output.presentLottery(false) - } - } - .store(in: &cancellables) + func displayLotteryIfNeeded() { + self.output.presentLottery(jackpot: Constants.lotteryJackpot) + self.output.presentLottery(!hideLottery) } func tryHandleDeeplink() { diff --git a/Nicegram/NGAssistant/Sources/Assistant/AssistantViewController.swift b/Nicegram/NGAssistant/Sources/Assistant/AssistantViewController.swift index efc3b7cc429..1e171ba7d32 100644 --- a/Nicegram/NGAssistant/Sources/Assistant/AssistantViewController.swift +++ b/Nicegram/NGAssistant/Sources/Assistant/AssistantViewController.swift @@ -149,7 +149,6 @@ final class AssistantViewController: UIViewController { make.height.equalTo(0.66) } - lotteryContainerView.isHidden = true lotteryContainerView.addSubview(lotteryStack) lotteryStack.snp.makeConstraints { make in make.edges.equalToSuperview() diff --git a/Nicegram/NGCopyProtectedContent/BUILD b/Nicegram/NGCopyProtectedContent/BUILD new file mode 100644 index 00000000000..4a56dce147a --- /dev/null +++ b/Nicegram/NGCopyProtectedContent/BUILD @@ -0,0 +1,18 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "NGCopyProtectedContent", + module_name = "NGCopyProtectedContent", + srcs = glob([ + "Sources/**/*.swift", + ]), + deps = [ + "//Nicegram/NGCore:NGCore", + "//Nicegram/NGData:NGData", + "//Nicegram/NGRemoteConfig:NGRemoteConfig", + "//Nicegram/NGSubscription:NGSubscription", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift b/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift new file mode 100644 index 00000000000..4d8c691e5e8 --- /dev/null +++ b/Nicegram/NGCopyProtectedContent/Sources/CopyProtectedContent.swift @@ -0,0 +1,56 @@ +import Foundation +import NGCore +import NGData +import NGRemoteConfig +import NGSubscription +import Postbox +import TelegramPresentationData +import UIKit + +// MARK: - Logic + +public func shouldShowInterfaceForCopyContent(message: Message) -> Bool { + let isCopyProtectionEnabled = message.isCopyProtected() + return !isCopyProtectionEnabled || allowCopyProtectedContent +} + +public func shouldShowInterfaceForForwardAsCopy(message: Message) -> Bool { + let isCopyProtectionEnabled = message.isCopyProtected() + + if isCopyProtectionEnabled { + let hasMedia = !message.media.isEmpty + return !hasMedia && shouldShowInterfaceForCopyContent(message: message) + } else { + return true + } +} + +public func shouldSubscribeToCopyContent(message: Message) -> Bool { + let isCopyProtectionEnabled = message.isCopyProtected() + + if isCopyProtectionEnabled { + return !isPremium() && !getBypassCopyProtection() + } else { + return false + } +} + +public func routeToNicegramPremiumForCopyContent(presentationData: PresentationData) { + let c = SubscriptionBuilderImpl(presentationData: presentationData).build() + c.modalPresentationStyle = .fullScreen + if let topViewController = UIApplication.topViewController { + topViewController.present(c, animated: true) + } +} + +// MARK: - Bypass setting (Secret Menu) + +private let bypassCopyProtectionKey = "ng:bypassCopyProtection" + +public func getBypassCopyProtection() -> Bool { + return UserDefaults.standard.bool(forKey: bypassCopyProtectionKey) +} + +public func setBypassCopyProtection(_ value: Bool) { + UserDefaults.standard.set(value, forKey: bypassCopyProtectionKey) +} diff --git a/Nicegram/NGCoreUI/Sources/LoadingView/LoadingView.swift b/Nicegram/NGCoreUI/Sources/LoadingView/LoadingView.swift index 04cca7cd65c..81fcee95bce 100644 --- a/Nicegram/NGCoreUI/Sources/LoadingView/LoadingView.swift +++ b/Nicegram/NGCoreUI/Sources/LoadingView/LoadingView.swift @@ -1,37 +1,100 @@ -import NGLoadingIndicator +import EsimUI +import SnapKit import UIKit +import Lottie public class LoadingView: UIView { + // MARK: - UI Elements + + private let activityIndicator: AnimationView + private let dimmView = UIView() + // MARK: - Public Properties public var isLoading: Bool = false { didSet { - if isLoading, !isLoadingAnimationInFlight { - loadingInicator.startAnimating(on: containerView) - isLoadingAnimationInFlight = true - } else if !isLoading, isLoadingAnimationInFlight { - loadingInicator.stopAnimating() - isLoadingAnimationInFlight = false + if isLoading { + start() + } else { + stop() } } } + public var hidesWhenStopped: Bool = true + public var dimmBackground: Bool = false + // MARK: - Logic - private let loadingInicator = NGLoadingIndicator() - private var isLoadingAnimationInFlight: Bool = false - private weak var containerView: UIView? + private var isAnimationInFlight: Bool = false // MARK: - Lifecycle - public init(containerView: UIView?) { - self.containerView = containerView + public override init(frame: CGRect) { + self.activityIndicator = AnimationView(name: "NicegramLoader") + + super.init(frame: frame) + + isHidden = true + + activityIndicator.loopMode = .loop - super.init(frame: .zero) + dimmView.backgroundColor = .black.withAlphaComponent(0.5) + + addSubview(dimmView) + dimmView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + addSubview(activityIndicator) + activityIndicator.snp.makeConstraints { make in + make.center.equalTo(safeAreaLayoutGuide) + make.leading.top.greaterThanOrEqualToSuperview() + make.height.equalTo(activityIndicator.snp.width) + make.width.equalTo(80).priority(999) + } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + // MARK: - Privtae Functions + + private func start() { + guard !isAnimationInFlight else { return } + isAnimationInFlight = true + + isHidden = false + dimmView.isHidden = !dimmBackground + UIView.animate(withDuration: 0.2) { [weak self] in + guard let self else { return } + self.activityIndicator.alpha = 1 + } completion: { [weak self] _ in + guard let self else { return } + self.activityIndicator.play() + } + } + + private func stop() { + activityIndicator.stop() + if hidesWhenStopped { + isHidden = true + } + + isAnimationInFlight = false + } +} + +public extension PlaceholderableView { + func showLoading() { + let view = LoadingView() + view.isLoading = true + self.showPlaceholder(view) + } + + func hideLoading() { + self.hidePlaceholder() + } } diff --git a/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState+Factory.swift b/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState+Factory.swift new file mode 100644 index 00000000000..8d90508504f --- /dev/null +++ b/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState+Factory.swift @@ -0,0 +1,22 @@ +import NGLocalization +import UIKit + +public extension PlaceholderState { + static func retry(error: Error, onTap: @escaping () -> Void) -> PlaceholderState { + return retry(message: error.localizedDescription, onTap: onTap) + } + + static func retry(message: String?, onTap: @escaping () -> Void) -> PlaceholderState { + return PlaceholderState( + title: nil, + image: nil, + description: mapErrorDescription(message), + buttonState: .init( + title: ngLocalized("Nicegram.Alert.TryAgain").uppercased(), + image: UIImage(named: "ng.refresh"), + style: .small, + onTap: onTap + ) + ) + } +} diff --git a/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState.swift b/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState.swift new file mode 100644 index 00000000000..3577093f241 --- /dev/null +++ b/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderState.swift @@ -0,0 +1,34 @@ +import UIKit + +public struct PlaceholderState { + public let title: String? + public let image: UIImage? + public let description: String + public let buttonState: ButtonState? + + public init(title: String?, image: UIImage?, description: String, buttonState: ButtonState?) { + self.title = title + self.image = image + self.description = description + self.buttonState = buttonState + } + + public struct ButtonState { + public let title: String + public let image: UIImage? + public let style: Style + public let onTap: () -> Void + + public init(title: String, image: UIImage? = nil, style: Style = .normal, onTap: @escaping () -> Void) { + self.title = title + self.image = image + self.style = style + self.onTap = onTap + } + + public enum Style { + case normal + case small + } + } +} diff --git a/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderableView.swift b/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderableView.swift new file mode 100644 index 00000000000..ab9e5721711 --- /dev/null +++ b/Nicegram/NGCoreUI/Sources/Placeholder/PlaceholderableView.swift @@ -0,0 +1,25 @@ +import NGCustomViews +import SnapKit +import UIKit + +public typealias PlaceholderableView = NGCustomViews.PlaceholderableView + +public extension PlaceholderableView { + func showPlaceholder(_ state: PlaceholderState) { + let view = DefaultPlaceholderView() + + switch state.buttonState?.style { + case .small: + view.configureWithSmallButton() + case .normal, .none: + break + } + + view.display(image: state.image, description: state.description, buttonTitle: state.buttonState?.title, buttonImage: state.buttonState?.image) + + view.onButtonClick = state.buttonState?.onTap + + self.showPlaceholder(view) + } +} + diff --git a/Nicegram/NGCustomViews/Sources/DefaultPlaceholderView.swift b/Nicegram/NGCustomViews/Sources/DefaultPlaceholderView.swift index d02da30f502..eae6fef59e6 100644 --- a/Nicegram/NGCustomViews/Sources/DefaultPlaceholderView.swift +++ b/Nicegram/NGCustomViews/Sources/DefaultPlaceholderView.swift @@ -24,7 +24,7 @@ open class DefaultPlaceholderView: UIView { set { stack.spacing = newValue } } - var onButtonClick: (() -> ())? { + public var onButtonClick: (() -> ())? { get { button.touchUpInside } set { button.touchUpInside = newValue } } @@ -83,3 +83,20 @@ open class DefaultPlaceholderView: UIView { c(button) } } + +public extension DefaultPlaceholderView { + func configureWithSmallButton() { + self.alignment = .center + self.spacing = 24 + self.configureButton { button in + button.backgroundColor = .ngLightOrange + button.layer.cornerRadius = 8 + button.insets = UIEdgeInsets(top: 5, left: 8, bottom: 5, right: 8) + button.spacing = 6 + + button.configureTitleLabel { label in + label.font = .systemFont(ofSize: 12, weight: .semibold) + } + } + } +} diff --git a/Nicegram/NGCustomViews/Sources/PlaceholderableView.swift b/Nicegram/NGCustomViews/Sources/PlaceholderableView.swift index 126fff427a8..89a9313de0f 100644 --- a/Nicegram/NGCustomViews/Sources/PlaceholderableView.swift +++ b/Nicegram/NGCustomViews/Sources/PlaceholderableView.swift @@ -66,19 +66,7 @@ public extension PlaceholderableView { buttonTitle: ngLocalized("Nicegram.Alert.TryAgain") .uppercased(), buttonImage: UIImage(named: "ng.refresh"), configureView: { view in - view.alignment = .center - view.spacing = 24 - view.configureButton { button in - button.backgroundColor = .ngLightOrange - button.layer.cornerRadius = 8 - button.insets = UIEdgeInsets(top: 5, left: 8, bottom: 5, right: 8) - button.spacing = 6 - - button.configureTitleLabel { label in - label.font = .systemFont(ofSize: 12, weight: .semibold) - } - } - + view.configureWithSmallButton() }, onButtonClick: onButtonClick ) diff --git a/Nicegram/NGLotteryUI/Sources/Presentation/CreateTicket/CreateTicketView.swift b/Nicegram/NGLotteryUI/Sources/Presentation/CreateTicket/CreateTicketView.swift index 5d232de4bf4..ab5422743d6 100644 --- a/Nicegram/NGLotteryUI/Sources/Presentation/CreateTicket/CreateTicketView.swift +++ b/Nicegram/NGLotteryUI/Sources/Presentation/CreateTicket/CreateTicketView.swift @@ -34,7 +34,7 @@ class CreateTicketViewController: MVVMViewController: MVVMViewController { // MARK: - UI Elements private let containerView = UIView() + private let contentView = UIView() + private lazy var contentViewWrapper = PlaceholderableView(wrappedView: contentView) private let scrollView = UIScrollView() private let headerView = HeaderView() private let nextDrawView = NextDrawView() @@ -78,11 +33,11 @@ class SplashViewController: MVVMViewController { private let infoView = LotteryInfoView() private let myTicketsView = MyTicketsView() private let closeButton = CustomButton() - private lazy var loadingView = LoadingView(containerView: self.view) + private lazy var loadingView = LoadingView() // MARK: - Logic - private var nextDrawId: SplashViewState.NextDraw.ID? + private var nextDrawId: SplashViewLoadedState.NextDraw.ID? private var timerSubscription: AnyCancellable? // MARK: - Lifecycle @@ -122,7 +77,7 @@ class SplashViewController: MVVMViewController { } segmentControl.onSegmentSelected = { [weak self] index in - guard let tab = SplashViewState.Tab(rawValue: index) else { return } + guard let tab = SplashViewLoadedState.Tab(rawValue: index) else { return } self?.viewModel.requestTab(tab) } @@ -142,6 +97,18 @@ class SplashViewController: MVVMViewController { // MARK: - Private Functions override func updateState(_ state: SplashViewState) { + switch state { + case .loading: + contentViewWrapper.showLoading() + case .placeholder(let placeholderState): + contentViewWrapper.showPlaceholder(placeholderState) + case .loaded(let splashViewLoadedState): + displayLoadedState(splashViewLoadedState) + contentViewWrapper.hidePlaceholder() + } + } + + private func displayLoadedState(_ state: SplashViewLoadedState) { headerView.display(jackpot: state.nextDraw.jackpot) if state.nextDraw.id != self.nextDrawId { @@ -330,14 +297,25 @@ private extension SplashViewController { make.height.equalToSuperview().priority(1) } - scrollContent.addSubview(closeButton) + contentView.addSubview(scrollView) + scrollView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + containerView.addSubview(contentViewWrapper) + contentViewWrapper.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + containerView.addSubview(closeButton) closeButton.snp.makeConstraints { make in make.top.trailing.equalToSuperview().inset(16) make.size.equalTo(24) } - containerView.addSubview(scrollView) - scrollView.snp.makeConstraints { make in + loadingView.dimmBackground = true + containerView.addSubview(loadingView) + loadingView.snp.makeConstraints { make in make.edges.equalToSuperview() } } diff --git a/Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewModel.swift b/Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewModel.swift index 3b32451ec11..f5b117e3a58 100644 --- a/Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewModel.swift +++ b/Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewModel.swift @@ -57,7 +57,7 @@ class SplashViewModelImpl: BaseViewModel Date() { - state.premiumSection = .alreadyReceived(nextDate: nextDate) - } else { - state.premiumSection = .getTicket - } - } else { - state.premiumSection = .subscribe - } - - state.userPastTickets = lotteryData.userPastTickets.compactMap { ticketWithDraw in - return .init( - date: ticketWithDraw.draw.date, - ticket: ticketWithDraw.ticket.numbers, - winningNumbers: ticketWithDraw.draw.winningNumbers - ) - } + state = .loaded(self.map(lotteryData: lotteryData, hasPremium: hasPremium)) } } .store(in: &cancellables) @@ -118,11 +88,11 @@ private extension SplashViewModelImpl { return } - updateViewState { $0.isLoading = true } + updateLoadedState { $0.isLoading = true } getTicketForPremiumUseCase.getTicket { [weak self] error in guard let self else { return } - self.updateViewState { $0.isLoading = false } + self.updateLoadedState { $0.isLoading = false } if let error { Alerts.show(.error(error)) @@ -133,11 +103,11 @@ private extension SplashViewModelImpl { } func initiateLoginWithTelegram() { - updateViewState { $0.isLoading = true } + updateLoadedState { $0.isLoading = true } initiateLoginWithTelegramUseCase.initiateLoginWithTelegram { [weak self] result in guard let self else { return } - self.updateViewState { $0.isLoading = false } + self.updateLoadedState { $0.isLoading = false } switch result { case .success(let url): @@ -150,8 +120,19 @@ private extension SplashViewModelImpl { } } - func refreshData() { - loadLotteryDataUseCase.loadLotteryData { _ in } + func loadLotteryData() { + updateViewState { $0 = .loading } + loadLotteryDataUseCase.loadLotteryData { [weak self] error in + guard let self else { return } + + if let error { + self.updateViewState { state in + state = .placeholder(.retry(error: error, onTap: { + self.loadLotteryData() + })) + } + } + } } } @@ -160,16 +141,16 @@ private extension SplashViewModelImpl { @available(iOS 13.0, *) extension SplashViewModelImpl: SplashViewModel { - func requestTab(_ tab: SplashViewState.Tab) { - updateViewState { $0.tab = tab } + func requestTab(_ tab: SplashViewLoadedState.Tab) { + updateLoadedState { $0.tab = tab } } func requestGetTicket() { - updateViewState { state in + updateLoadedState { state in state.tab = .myTickets state.forceShowHowToGetTicket = true } - updateViewState { $0.forceShowHowToGetTicket = false } + updateLoadedState { $0.forceShowHowToGetTicket = false } } func requestCreateTicket() { @@ -194,10 +175,6 @@ extension SplashViewModelImpl: SplashViewModel { UIApplication.shared.open(Constants.moreInfoUrl) } - func requestPullToRefresh() { - refreshData() - } - func requestClose() { handlers.close() } @@ -207,9 +184,64 @@ extension SplashViewModelImpl: SplashViewModel { @available(iOS 13.0, *) private extension SplashViewModelImpl { - func mapToPastDraw(_ draw: PastDraw?) -> SplashViewState.PastDraw? { + func map(lotteryData: LotteryData, hasPremium: Bool) -> SplashViewLoadedState { + var state = SplashViewLoadedState() + + state.nextDraw = .init( + id: lotteryData.currentDraw.date, + jackpot: lotteryData.currentDraw.jackpot, + date: lotteryData.currentDraw.date + ) + state.lastDraw = self.mapToPastDraw(lotteryData.lastDraw) + state.pastDraws = lotteryData.pastDraws.compactMap { self.mapToPastDraw($0) } + state.userActiveTickets = lotteryData.userActiveTickets.map { ticket in + return .init(numbers: ticket.numbers, date: ticket.drawDate) + } + state.availableUserTicketsCount = lotteryData.userAvailableTicketsCount + + if hasPremium { + if let nextDate = lotteryData.nextTicketForPremiumDate, + nextDate > Date() { + state.premiumSection = .alreadyReceived(nextDate: nextDate) + } else { + state.premiumSection = .getTicket + } + } else { + state.premiumSection = .subscribe + } + + state.userPastTickets = lotteryData.userPastTickets.compactMap { ticketWithDraw in + return .init( + date: ticketWithDraw.draw.date, + ticket: ticketWithDraw.ticket.numbers, + winningNumbers: ticketWithDraw.draw.winningNumbers + ) + } + + return state + } + + func mapToPastDraw(_ draw: PastDraw?) -> SplashViewLoadedState.PastDraw? { guard let draw else { return nil } return .init(date: draw.date, winningNumbers: draw.winningNumbers) } } +// MARK: - Helpers + +@available(iOS 13.0, *) +private extension SplashViewModelImpl { + func updateLoadedState(_ block: @escaping (inout SplashViewLoadedState) -> Void) { + updateViewState { state in + switch state { + case .loaded(let loadedState): + var loadedState = loadedState + block(&loadedState) + state = .loaded(loadedState) + case .loading, .placeholder: + break + } + } + } +} + diff --git a/Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewState.swift b/Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewState.swift new file mode 100644 index 00000000000..83f184f6bd0 --- /dev/null +++ b/Nicegram/NGLotteryUI/Sources/Presentation/Splash/SplashViewState.swift @@ -0,0 +1,59 @@ +import NGCore +import NGCoreUI +import Foundation + +enum SplashViewState: ViewState { + case loading + case loaded(SplashViewLoadedState) + case placeholder(PlaceholderState) + + init() { + self = .loading + } +} + +struct SplashViewLoadedState { + var tab: Tab = .info + var nextDraw: NextDraw = NextDraw() + var lastDraw: PastDraw? = nil + var pastDraws: [PastDraw] = [] + var userActiveTickets: [UserActiveTicket] = [] + var availableUserTicketsCount: Int = 0 + var premiumSection: PremiumSectionViewState = .subscribe + var userPastTickets: [MyTicketsViewState.PastTicket] = [] + var isLoading: Bool = false + + var forceShowHowToGetTicket: Bool = false + + enum Tab: Int { + case info + case myTickets + } + + struct NextDraw: Identifiable { + let id: Date + let jackpot: Money + let date: Date + + init(id: Date = Date(), jackpot: Money = Money(amount: 0, currency: .usd), date: Date = .distantFuture) { + self.id = id + self.jackpot = jackpot + self.date = date + } + } + + struct PastDraw { + let date: Date + let winningNumbers: [Int] + + init(date: Date = Date(), winningNumbers: [Int] = []) { + self.date = date + self.winningNumbers = winningNumbers + } + } + + struct UserActiveTicket { + let numbers: [Int] + let date: Date + } +} diff --git a/Nicegram/NGRemoteConfig/Sources/Helpers.swift b/Nicegram/NGRemoteConfig/Sources/Helpers.swift index cddc9fa59f3..402f6454cf5 100644 --- a/Nicegram/NGRemoteConfig/Sources/Helpers.swift +++ b/Nicegram/NGRemoteConfig/Sources/Helpers.swift @@ -9,3 +9,9 @@ public var hideLottery: Bool { let defaultValue = false return remoteValue ?? defaultValue } + +public var allowCopyProtectedContent: Bool { + let remoteValue = RemoteConfigServiceImpl.shared.get(Bool.self, byKey: "allowCopyProtectedContent") + let defaultValue = false + return remoteValue ?? defaultValue +} diff --git a/Nicegram/NGSecretMenu/BUILD b/Nicegram/NGSecretMenu/BUILD index 2e3d2c74dd4..fe68cd4ee33 100644 --- a/Nicegram/NGSecretMenu/BUILD +++ b/Nicegram/NGSecretMenu/BUILD @@ -8,6 +8,7 @@ swift_library( ]), deps = [ "//submodules/PresentationDataUtils:PresentationDataUtils", + "//Nicegram/NGCopyProtectedContent:NGCopyProtectedContent", "//Nicegram/NGRemoteConfig:NGRemoteConfig", ], visibility = [ diff --git a/Nicegram/NGSecretMenu/Sources/SecretMenuController.swift b/Nicegram/NGSecretMenu/Sources/SecretMenuController.swift index 7d4b90bae4c..316f15ca8fd 100644 --- a/Nicegram/NGSecretMenu/Sources/SecretMenuController.swift +++ b/Nicegram/NGSecretMenu/Sources/SecretMenuController.swift @@ -1,6 +1,7 @@ import AccountContext import Display import ItemListUI +import NGCopyProtectedContent import PresentationDataUtils import SwiftSignalKit import TelegramCore diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 45aca2f810b..f0a5fdcc2e7 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2950,8 +2950,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController return _appContext as! AppContext } - private var lotteryDataSubscription: AnyObject? - @available(iOS 13.0, *) private func nicegramInit() { // Localization @@ -2961,14 +2959,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController LocalizationServiceImpl.shared.setLanguageCode(activeLanguageCode) } } - - // Lottery - - let loadLotteryDataUseCase = appContext.resolveLoadLotteryDataUseCase() - - if !hideLottery { - loadLotteryDataUseCase.loadLotteryData(completion: { _ in }) - } } public func showNicegramAssistant(deeplink: Deeplink?) { diff --git a/submodules/GalleryUI/BUILD b/submodules/GalleryUI/BUILD index f109fdac5a3..dceb2eef072 100644 --- a/submodules/GalleryUI/BUILD +++ b/submodules/GalleryUI/BUILD @@ -1,6 +1,9 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -NGDEPS = ["//Nicegram/NGData:NGData"] +NGDEPS = [ + "//Nicegram/NGCopyProtectedContent:NGCopyProtectedContent", + "//Nicegram/NGData:NGData", +] swift_library( name = "GalleryUI", diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 06515034351..9ede7986232 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -1,3 +1,6 @@ +// MARK: Nicegram CopyProtectedContent +import NGCopyProtectedContent +// import Foundation import UIKit import AsyncDisplayKit @@ -675,6 +678,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll canEdit = false } + // MARK: Nicegram CopyProtectedContent + canShare = shouldShowInterfaceForCopyContent(message: message) + // + var authorNameText: String? if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), let authorSignature = forwardInfo.authorSignature { authorNameText = authorSignature @@ -1191,6 +1198,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll self.interacting?(true) if let currentMessage = self.currentMessage { + // MARK: Nicegram CopyProtectedContent + if shouldSubscribeToCopyContent(message: currentMessage) { + self.interacting?(false) + routeToNicegramPremiumForCopyContent(presentationData: presentationData) + return + } + // let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Messages.MessageGroup(id: currentMessage.id)) |> deliverOnMainQueue).start(next: { [weak self] messages in if let strongSelf = self, !messages.isEmpty { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CacheStorageSettings.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CacheStorageSettings.swift index 51d634ef59a..dbcd01e24fa 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CacheStorageSettings.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CacheStorageSettings.swift @@ -19,10 +19,10 @@ public struct CacheStorageSettings: Codable, Equatable { public var categoryStorageTimeout: [PeerStorageCategory: Int32] public static var defaultSettings: CacheStorageSettings { - // MARK: Nicegram CacheSettings, change defaultCacheStorageLimitGigabytes to 5 * 1024 * 1024 + // MARK: Nicegram CacheSettings, change defaultCacheStorageLimitGigabytes to 2 return CacheStorageSettings( defaultCacheStorageTimeout: Int32.max, - defaultCacheStorageLimitGigabytes: 5 * 1024 * 1024, + defaultCacheStorageLimitGigabytes: 2, categoryStorageTimeout: [ .privateChats: Int32.max, .groups: Int32(31 * 24 * 60 * 60), @@ -51,8 +51,8 @@ public struct CacheStorageSettings: Codable, Equatable { } else if let value = try container.decodeIfPresent(Int32.self, forKey: "sizeLimit") { self.defaultCacheStorageLimitGigabytes = value } else { - // MARK: Nicegram CacheSettings, change defaultCacheStorageLimitGigabytes to 5 * 1024 * 1024 - self.defaultCacheStorageLimitGigabytes = 5 * 1024 * 1024 + // MARK: Nicegram CacheSettings, change defaultCacheStorageLimitGigabytes to 2 + self.defaultCacheStorageLimitGigabytes = 2 } if let data = try container.decodeIfPresent(Data.self, forKey: "categoryStorageTimeoutJson") { diff --git a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift index c53671d612f..8f6a87d2624 100644 --- a/submodules/TelegramCore/Sources/Utils/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/MessageUtils.swift @@ -251,11 +251,6 @@ public extension Message { } func isCopyProtected() -> Bool { - // MARK: - Nicegram (allow saving+forwarding content when copy protected) - if canCopyProtectedContent() { - return false - } - if self.flags.contains(.CopyProtected) { return true } else if let group = self.peers[self.id.peerId] as? TelegramGroup, group.flags.contains(.copyProtectionEnabled) { diff --git a/submodules/TelegramCore/Sources/Utils/NGUtils.swift b/submodules/TelegramCore/Sources/Utils/NGUtils.swift index 279b485fa01..911bec50062 100644 --- a/submodules/TelegramCore/Sources/Utils/NGUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/NGUtils.swift @@ -1,21 +1,5 @@ import Foundation -// MARK: Nicegram CopyProtectedContent - -private let bypassCopyProtectionKey = "ng:bypassCopyProtection" - -public func getBypassCopyProtection() -> Bool { - return UserDefaults.standard.bool(forKey: bypassCopyProtectionKey) -} - -public func setBypassCopyProtection(_ value: Bool) { - UserDefaults.standard.set(value, forKey: bypassCopyProtectionKey) -} - -func canCopyProtectedContent() -> Bool { - return getBypassCopyProtection() -} - // MARK: Nicegram Translate private let savedTranslationTargetLanguageKey = "ng:savedTranslationTargetLanguage" diff --git a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift index f8bc3db70e9..74c52576bae 100644 --- a/submodules/TelegramCore/Sources/Utils/PeerUtils.swift +++ b/submodules/TelegramCore/Sources/Utils/PeerUtils.swift @@ -196,11 +196,6 @@ public extension Peer { } var isCopyProtectionEnabled: Bool { - // MARK: - Nicegram (allow saving+forwarding content when copy protected) - if canCopyProtectedContent() { - return false - } - switch self { case let group as TelegramGroup: return group.flags.contains(.copyProtectionEnabled) diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 9204942c02f..b8fbf030c88 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -1,6 +1,7 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") NGDEPS = [ + "//Nicegram/NGCopyProtectedContent:NGCopyProtectedContent", "//Nicegram/NGLogging:NGLogging", "//Nicegram/NGData:NGData", "//Nicegram/NGWebUtils:NGWebUtils", diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift index 95c6c5623ea..8048bec2475 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageKeepSizeComponent.swift @@ -35,6 +35,11 @@ private func totalDiskSpace() -> Int64 { private let maximumCacheSizeValues: [Int32] = { let diskSpace = totalDiskSpace() + // MARK: Nicegram CacheSettings + if diskSpace > 0 { + return [1, 2, 8, Int32.max] + } + // if diskSpace > 100 * 1024 * 1024 * 1024 { return [5, 20, 50, Int32.max] } else if diskSpace > 50 * 1024 * 1024 * 1024 { diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift index 03648eb9991..ba51910e258 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/StorageUsageScreen.swift @@ -1408,7 +1408,8 @@ final class StorageUsageScreenComponent: Component { guard let self, let component = self.component else { return } - let value = max(5, value) + // MARK: Nicegram CacheSettings, 'let value = max(5, value)' commented +// let value = max(5, value) let _ = updateCacheStorageSettingsInteractively(accountManager: component.context.sharedContext.accountManager, { current in var current = current current.defaultCacheStorageLimitGigabytes = value diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index fc264edf6fc..26641179acf 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -26,6 +26,7 @@ import TelegramNotices import ReactionListContextMenuContent import TelegramUIPreferences // MARK: Nicegram Imports +import NGCopyProtectedContent import NGUI import NGStrings import NGSubscription @@ -1123,10 +1124,18 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } if !isExpired { if !isPoll { - if !isCopyProtected { + // MARK: Nicegram CopyProtectedContent, '!isCopyProtected' changed to 'shouldShowInterfaceForCopyContent(message: message)' + if shouldShowInterfaceForCopyContent(message: message) { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopy, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in + // MARK: Nicegram CopyProtectedContent + if shouldSubscribeToCopyContent(message: message) { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + routeToNicegramPremiumForCopyContent(presentationData: presentationData) + return + } + // if let diceEmoji = diceEmoji { UIPasteboard.general.string = diceEmoji } else { @@ -1218,7 +1227,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } - if resourceAvailable, !message.containsSecretMedia, !chatPresentationInterfaceState.copyProtectionEnabled, !message.isCopyProtected() { + // MARK: Nicegram CopyProtectedContent, '!chatPresentationInterfaceState.copyProtectionEnabled, !message.isCopyProtected()' changed to 'shouldShowInterfaceForCopyContent(message: message)' + if resourceAvailable, !message.containsSecretMedia, shouldShowInterfaceForCopyContent(message: message) { var mediaReference: AnyMediaReference? var isVideo = false for media in message.media { @@ -1235,6 +1245,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState actions.append(.action(ContextMenuActionItem(text: isVideo ? chatPresentationInterfaceState.strings.Gallery_SaveVideo : chatPresentationInterfaceState.strings.Gallery_SaveImage, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in + // MARK: Nicegram CopyProtectedContent + if shouldSubscribeToCopyContent(message: message) { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + routeToNicegramPremiumForCopyContent(presentationData: presentationData) + return + } + // let _ = (saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: mediaReference) |> deliverOnMainQueue).start(completed: { Queue.mainQueue().after(0.2) { @@ -1701,10 +1718,16 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState var ngContextItems: [ContextMenuItem] = [] // Copyforward - if data.messageActions.options.contains(.forward) { + if shouldShowInterfaceForForwardAsCopy(message: message) { ngContextItems.append(.action(ContextMenuActionItem(text: l("Chat.ForwardAsCopy", chatPresentationInterfaceState.strings.baseLanguageCode), icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "CopyForward"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in + // MARK: Nicegram CopyProtectedContent + if shouldSubscribeToCopyContent(message: message) { + routeToNicegramPremiumForCopyContent(presentationData: presentationData) + return + } + // interfaceInteraction.copyForwardMessages(selectAll ? messages : [message]) f(.dismissWithoutContent) }))) diff --git a/versions.json b/versions.json index d09264bb06a..50b32c3a028 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "1.1.8", + "app": "1.1.9", "bazel": "5.3.1", "xcode": "14.1" }