diff --git a/Telegram/BUILD b/Telegram/BUILD index 83f04b0dcf..c8c41abea2 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -298,9 +298,9 @@ alternate_icon_folders = [ "WhiteFilledIcon", "New1", "New2", - "PremiumCosmic", - "PremiumCherry", - "PremiumDuck", + "Premium", + "PremiumBlack", + "PremiumTurbo", ] [ diff --git a/Telegram/Telegram-iOS/Premium.alticon/Premium@2x.png b/Telegram/Telegram-iOS/Premium.alticon/Premium@2x.png new file mode 100644 index 0000000000..00ea76d714 Binary files /dev/null and b/Telegram/Telegram-iOS/Premium.alticon/Premium@2x.png differ diff --git a/Telegram/Telegram-iOS/Premium.alticon/Premium@3x.png b/Telegram/Telegram-iOS/Premium.alticon/Premium@3x.png new file mode 100644 index 0000000000..1a67519593 Binary files /dev/null and b/Telegram/Telegram-iOS/Premium.alticon/Premium@3x.png differ diff --git a/Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@2x.png b/Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@2x.png new file mode 100644 index 0000000000..cb953d3546 Binary files /dev/null and b/Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@2x.png differ diff --git a/Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@3x.png b/Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@3x.png new file mode 100644 index 0000000000..a3833ef0c7 Binary files /dev/null and b/Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@3x.png differ diff --git a/Telegram/Telegram-iOS/PremiumCherry.alticon/PremiumCherry@2x.png b/Telegram/Telegram-iOS/PremiumCherry.alticon/PremiumCherry@2x.png deleted file mode 100644 index 1552a49b63..0000000000 Binary files a/Telegram/Telegram-iOS/PremiumCherry.alticon/PremiumCherry@2x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/PremiumCherry.alticon/PremiumCherry@3x.png b/Telegram/Telegram-iOS/PremiumCherry.alticon/PremiumCherry@3x.png deleted file mode 100644 index 78b61d2481..0000000000 Binary files a/Telegram/Telegram-iOS/PremiumCherry.alticon/PremiumCherry@3x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/PremiumCosmic.alticon/PremiumCosmic@2x.png b/Telegram/Telegram-iOS/PremiumCosmic.alticon/PremiumCosmic@2x.png deleted file mode 100644 index e077e61bf4..0000000000 Binary files a/Telegram/Telegram-iOS/PremiumCosmic.alticon/PremiumCosmic@2x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/PremiumCosmic.alticon/PremiumCosmic@3x.png b/Telegram/Telegram-iOS/PremiumCosmic.alticon/PremiumCosmic@3x.png deleted file mode 100644 index 41d8434f8b..0000000000 Binary files a/Telegram/Telegram-iOS/PremiumCosmic.alticon/PremiumCosmic@3x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/PremiumDuck.alticon/PremiumDuck@2x.png b/Telegram/Telegram-iOS/PremiumDuck.alticon/PremiumDuck@2x.png deleted file mode 100644 index d50c516203..0000000000 Binary files a/Telegram/Telegram-iOS/PremiumDuck.alticon/PremiumDuck@2x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/PremiumDuck.alticon/PremiumDuck@3x.png b/Telegram/Telegram-iOS/PremiumDuck.alticon/PremiumDuck@3x.png deleted file mode 100644 index c24790716f..0000000000 Binary files a/Telegram/Telegram-iOS/PremiumDuck.alticon/PremiumDuck@3x.png and /dev/null differ diff --git a/Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@2x.png b/Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@2x.png new file mode 100644 index 0000000000..7eccb509ee Binary files /dev/null and b/Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@2x.png differ diff --git a/Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@3x.png b/Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@3x.png new file mode 100644 index 0000000000..a243d72e63 Binary files /dev/null and b/Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@3x.png differ diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 42fa86cc74..3c9ae80fa6 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7561,13 +7561,13 @@ Sorry for the inconvenience."; "Premium.IncreaseLimit" = "Increase Limit"; "Premium.MaxFoldersCountText" = "You have reached the limit of **%1$@** folders. You can double the limit to **%2$@** folders by subscribing to **Telegram Premium**."; +"Premium.MaxFoldersCountFinalText" = "Sorry, you can't create more than **%1$@** folders."; "Premium.MaxChatsInFolderText" = "Sorry, you can't add more than **%1$@** chats to a folder. You can increase this limit to **%2$@** by upgrading to **Telegram Premium**."; "Premium.MaxChatsInFolderFinalText" = "Sorry, you can't add more than **%@** chats to a folder."; "Premium.MaxFileSizeText" = "Double this limit to %@ per file by subscribing to **Telegram Premium**."; "Premium.MaxFileSizeFinalText" = "The document can't be sent, because it is larger than **%@**."; "Premium.MaxPinsText" = "Sorry, you can't pin more than **%1$@** chats to the top. Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **%2$@** chats."; -"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **@** chats to the top. Unpin some of the currently pinned ones."; -"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **@** chats to the top."; +"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **%@** chats to the top. Unpin some of the currently pinned ones."; "Premium.MaxFavedStickersTitle" = "The Limit of %@ Stickers Reached"; "Premium.MaxFavedStickersText" = "An older sticker was replaced with this one. You can [increase the limit]() to %@ stickers."; "Premium.MaxFavedStickersFinalText" = "An older sticker was replaced with this one."; @@ -7575,6 +7575,7 @@ Sorry for the inconvenience."; "Premium.MaxSavedGifsText" = "An older GIF was replaced with this one. You can [increase the limit]() to %@ GIFS."; "Premium.MaxSavedGifsFinalText" = "An older GIF was replaced with this one."; "Premium.MaxAccountsText" = "You have reached the limit of **%@** connected accounts. You can free one place by subscribing to **Telegram Premium**."; +"Premium.MaxAccountsFinalText" = "You have reached the limit of **%@** connected accounts."; "Premium.Free" = "Free"; "Premium.Premium" = "Premium"; diff --git a/submodules/AuthTransferUI/BUILD b/submodules/AuthTransferUI/BUILD index 805946bb57..1dd094a4db 100644 --- a/submodules/AuthTransferUI/BUILD +++ b/submodules/AuthTransferUI/BUILD @@ -22,7 +22,6 @@ swift_library( "//submodules/AlertUI:AlertUI", "//submodules/AppBundle:AppBundle", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", - "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/AnimationUI:AnimationUI", "//submodules/PresentationDataUtils:PresentationDataUtils", diff --git a/submodules/AuthTransferUI/Sources/AuthTransferConfirmationScreen.swift b/submodules/AuthTransferUI/Sources/AuthTransferConfirmationScreen.swift index 75660fb5a4..9588ef451e 100644 --- a/submodules/AuthTransferUI/Sources/AuthTransferConfirmationScreen.swift +++ b/submodules/AuthTransferUI/Sources/AuthTransferConfirmationScreen.swift @@ -5,7 +5,6 @@ import AsyncDisplayKit import Display import SolidRoundedButtonNode import SwiftSignalKit -import OverlayStatusController import AnimationUI import AccountContext import TelegramPresentationData diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 915e07168d..48189255c3 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -56,9 +56,14 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch return combineLatest( context.engine.data.get(TelegramEngine.EngineData.Item.Messages.ChatListGroup(id: peerId)), - context.engine.peers.recentlySearchedPeers() |> take(1) + context.engine.peers.recentlySearchedPeers() |> take(1), + context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), + TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) + ) ) - |> mapToSignal { peerGroup, recentlySearchedPeers -> Signal<[ContextMenuItem], NoError> in + |> mapToSignal { peerGroup, recentlySearchedPeers, limitsData -> Signal<[ContextMenuItem], NoError> in let location: TogglePeerChatPinnedLocation var chatListFilter: ChatListFilter? if case let .chatList(filter) = source, let chatFilter = filter { @@ -243,6 +248,30 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor) }, action: { c, f in c.dismiss(completion: { + let isPremium = limitsData.0?.isPremium ?? false + let (_, limits, premiumLimits) = limitsData + + let limit = limits.maxFolderChatsCount + let premiumLimit = premiumLimits.maxFolderChatsCount + + let count = data.includePeers.peers.count - 1 + if count >= premiumLimit { + let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {}) + chatListController?.push(controller) + return + } else if count >= limit && !isPremium { + var replaceImpl: ((ViewController) -> Void)? + let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: { + let controller = PremiumIntroScreen(context: context, source: .chatsPerFolder) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + chatListController?.push(controller) + return + } + let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in var filters = filters for i in 0 ..< filters.count { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 49736d94ae..7fbf92b2c0 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -265,7 +265,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top) case let .known(offset): if offset <= navigationBarSearchContentHeight + 1.0 && strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil { - strongSelf.selectTab(id: .all) + let _ = (strongSelf.context.engine.peers.currentChatListFilters() + |> deliverOnMainQueue).start(next: { [weak self] filters in + guard let strongSelf = self else { + return + } + let targetTab: ChatListFilterTabEntryId + let firstFilter = filters.first ?? .allChats + switch firstFilter { + case .allChats: + targetTab = .all + case let .filter(id, _, _, _): + targetTab = .filter(id) + } + + strongSelf.selectTab(id: targetTab) + }) } else { if let searchContentNode = strongSelf.searchContentNode { searchContentNode.updateExpansionProgress(1.0, animated: true) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index a9ee17e93e..313f0ff86e 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2169,7 +2169,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { videoNode.canAttachContent = true videoNode.play() - self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode) +// self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode) + self.avatarNode.addSubnode(videoNode) self.videoNode = videoNode } } else if let videoNode = self.videoNode { @@ -2179,7 +2180,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let videoNode = self.videoNode { videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate) - videoNode.frame = self.avatarNode.frame + videoNode.frame = self.avatarNode.bounds } } @@ -2218,7 +2219,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { avatarFrame.origin.x = leftInset - avatarLeftInset + editingOffset + 10.0 + offset transition.updateFrame(node: self.avatarNode, frame: avatarFrame) if let videoNode = self.videoNode { - transition.updateFrame(node: videoNode, frame: avatarFrame) + transition.updateFrame(node: videoNode, frame: CGRect(origin: .zero, size: avatarFrame.size)) } var onlineFrame = self.onlineNode.frame diff --git a/submodules/Components/SheetComponent/Sources/SheetComponent.swift b/submodules/Components/SheetComponent/Sources/SheetComponent.swift index bb79db3b77..80bdfc3a67 100644 --- a/submodules/Components/SheetComponent/Sources/SheetComponent.swift +++ b/submodules/Components/SheetComponent/Sources/SheetComponent.swift @@ -173,6 +173,7 @@ public final class SheetComponent: Component { } } + private var currentAvailableSize: CGSize? func update(component: SheetComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { component.animateOut.connect { [weak self] completion in guard let strongSelf = self else { @@ -206,6 +207,11 @@ public final class SheetComponent: Component { self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height) + contentSize.height, left: 0.0, bottom: 0.0, right: 0.0) self.ignoreScrolling = false + if let currentAvailableSize = self.currentAvailableSize, currentAvailableSize.height != availableSize.height { + self.scrollView.contentOffset = CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)) + } + self.currentAvailableSize = availableSize + if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) { self.animateIn() } else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) { diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index d54c82f468..aecd585a1e 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -555,8 +555,16 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo var actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize) + var contentVerticalOffset: CGFloat = 0.0 if keepInPlace, case .extracted = self.source { actionsFrame.origin.y = contentRect.minY - contentActionsSpacing - actionsFrame.height + let statusBarHeight = (layout.statusBarHeight ?? 0.0) + if actionsFrame.origin.y < statusBarHeight { + let updatedActionsOriginY = statusBarHeight + contentActionsSpacing + let delta = updatedActionsOriginY - actionsFrame.origin.y + actionsFrame.origin.y = updatedActionsOriginY + contentVerticalOffset = delta + } } if centerActionsHorizontally { actionsFrame.origin.x = floor(contentParentGlobalFrame.minX + contentRect.midX - actionsFrame.width / 2.0) @@ -593,14 +601,18 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true) if let contentNode = contentNode { - contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY), size: contentNode.containingItem.view.bounds.size), beginWithCurrentState: true) + contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset), size: contentNode.containingItem.view.bounds.size), beginWithCurrentState: true) } let contentHeight: CGFloat if self.actionsStackNode.topPositionLock != nil { contentHeight = layout.size.height } else { - contentHeight = actionsFrame.maxY + bottomInset + layout.intrinsicInsets.bottom + if keepInPlace, case .extracted = self.source { + contentHeight = (layout.statusBarHeight ?? 0.0) + actionsFrame.height + abs(actionsFrame.minY) + bottomInset + layout.intrinsicInsets.bottom + } else { + contentHeight = actionsFrame.maxY + bottomInset + layout.intrinsicInsets.bottom + } } let contentSize = CGSize(width: layout.size.width, height: contentHeight) diff --git a/submodules/GameUI/Sources/GameControllerNode.swift b/submodules/GameUI/Sources/GameControllerNode.swift index 5ef9eab990..f86105cecf 100644 --- a/submodules/GameUI/Sources/GameControllerNode.swift +++ b/submodules/GameUI/Sources/GameControllerNode.swift @@ -150,9 +150,11 @@ final class GameControllerNode: ViewControllerTracingNode { if let strongSelf = self, let message = strongSelf.message { let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, as: nil) } return .single(.preparing(false)) + |> castError(ShareControllerError.self) |> then( combineLatest(signals) - |> mapToSignal { _ -> Signal in return .complete() } + |> castError(ShareControllerError.self) + |> mapToSignal { _ -> Signal in return .complete() } ) |> then(.single(.done)) } else { diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index a00809bbd9..dc4a8ab663 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -80,7 +80,6 @@ public final class InAppPurchaseManager: NSObject { self.productRequest = productRequest } - public var availableProducts: Signal<[Product], NoError> { if self.products.isEmpty && self.productRequest == nil { self.requestProducts() diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m index fd6ca066a1..42b91ab52f 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItem.m @@ -169,6 +169,14 @@ return self.backingItem.asset; } +- (SSignal *)avAsset +{ + if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]]) + return ((TGCameraCapturedVideo *)self.asset).avAsset; + + return nil; +} + - (NSString *)uniqueId { if (self.asset != nil) diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 956e69ada8..7cf7d94251 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -48,6 +48,7 @@ swift_library( "//submodules/MediaPlayer:UniversalMediaPlayer", "//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent", "//submodules/RadialStatusNode:RadialStatusNode", + "//submodules/ShimmerEffect:ShimmerEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Sources/PageIndicatorComponent.swift b/submodules/PremiumUI/Sources/PageIndicatorComponent.swift index ed4af59f87..d48c9e08b1 100644 --- a/submodules/PremiumUI/Sources/PageIndicatorComponent.swift +++ b/submodules/PremiumUI/Sources/PageIndicatorComponent.swift @@ -81,7 +81,7 @@ public final class PageIndicatorComponent: Component { private final class PageIndicatorView: UIView { var displayCount: Int { - return min(9, self.pageCount) + return min(11, self.pageCount) } var dotSize: CGFloat = 8.0 var dotSpace: CGFloat = 10.0 diff --git a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift index 6e062a5d76..40837302ab 100644 --- a/submodules/PremiumUI/Sources/PhoneDemoComponent.swift +++ b/submodules/PremiumUI/Sources/PhoneDemoComponent.swift @@ -76,7 +76,7 @@ private final class PhoneView: UIView { self.borderView = UIImageView(image: phoneBorderImage) self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6), enableBlur: false) - self.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true)) + self.statusNode.transitionToState(.none) self.statusNode.isUserInteractionEnabled = false super.init(frame: frame) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index a0b0fae0ec..1673f25a0c 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -634,7 +634,7 @@ private final class DemoSheetContent: CombinedComponent { content: AnyComponent(PhoneDemoComponent( context: component.context, position: .bottom, - videoFile: configuration.videos["double_limits"] + videoFile: configuration.videos["more_upload"] )), title: strings.Premium_UploadSize, text: strings.Premium_UploadSizeInfo, @@ -740,7 +740,7 @@ private final class DemoSheetContent: CombinedComponent { content: AnyComponent(PhoneDemoComponent( context: component.context, position: .top, - videoFile: configuration.videos["chat_management"] + videoFile: configuration.videos["advanced_chat_management"] )), title: strings.Premium_ChatManagement, text: strings.Premium_ChatManagementInfo, @@ -774,7 +774,7 @@ private final class DemoSheetContent: CombinedComponent { content: AnyComponent(PhoneDemoComponent( context: component.context, position: .top, - videoFile: configuration.videos["userpics"] + videoFile: configuration.videos["animated_userpics"] )), title: strings.Premium_Avatar, text: strings.Premium_AvatarInfo, @@ -899,12 +899,14 @@ private final class DemoSheetContent: CombinedComponent { animationName: isStandalone && component.subject == .uniqueReactions ? "premium_unlock" : nil, iconPosition: .right, iconSpacing: 4.0, - action: { [weak component] in + action: { [weak component, weak state] in guard let component = component else { return } component.dismiss() - component.action() + if let state = state, state.isPremium == false { + component.action() + } } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index f4cdd18c5e..75072a76af 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1158,7 +1158,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { tapAction: { attributes, _ in if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { - if url == "cancel" { + if url.hasPrefix("https://") { + controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: true, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {}) + } else if url == "cancel" { } else { let context = controller.context @@ -1362,6 +1364,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent { }, completed: { [weak self] in if let strongSelf = self { + strongSelf.isPremium = true + strongSelf.updated(transition: .easeInOut(duration: 0.25)) strongSelf.completion() } })) @@ -1631,13 +1635,40 @@ private final class PremiumIntroScreenComponent: CombinedComponent { context.add(bottomPanel .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height / 2.0)) .opacity(bottomPanelAlpha) + .disappear(Transition.Disappear { view, transition, completion in + if case .none = transition.animation { + completion() + return + } + view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: bottomPanel.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + }) ) context.add(bottomSeparator .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height)) .opacity(bottomPanelAlpha) + .disappear(Transition.Disappear { view, transition, completion in + if case .none = transition.animation { + completion() + return + } + view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: bottomPanel.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + }) ) context.add(button .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height + bottomPanelPadding + button.size.height / 2.0)) + .disappear(Transition.Disappear { view, transition, completion in + if case .none = transition.animation { + completion() + return + } + view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: bottomPanel.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + }) ) } @@ -1704,9 +1735,6 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { completionImpl = { [weak self] in if let strongSelf = self { strongSelf.view.addSubview(ConfettiView(frame: strongSelf.view.bounds)) - Queue.mainQueue().after(2.0, { - self?.dismiss() - }) } } } diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 258025b7eb..4499c8d429 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -258,6 +258,8 @@ private class PremiumLimitAnimationComponent: Component { countWidth = 35.0 case 3: countWidth = 51.0 + case 4: + countWidth = 60.0 default: countWidth = 51.0 } @@ -549,7 +551,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent { ) context.add(inactiveValue - .position(CGPoint(x: context.availableSize.width / 2.0 - activeValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) + .position(CGPoint(x: context.availableSize.width / 2.0 - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0)) ) context.add(activeTitle @@ -693,7 +695,7 @@ private final class LimitSheetContent: CombinedComponent { let premiumLimit = state.premiumLimits.maxFoldersCount iconName = "Premium/Folder" badgeText = "\(component.count)" - string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string + string = component.count >= premiumLimit ? strings.Premium_MaxFoldersCountFinalText("\(premiumLimit)").string : strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" badgePosition = CGFloat(component.count) / CGFloat(premiumLimit) @@ -702,7 +704,7 @@ private final class LimitSheetContent: CombinedComponent { let premiumLimit = state.premiumLimits.maxFolderChatsCount iconName = "Premium/Chat" badgeText = "\(component.count)" - string = strings.Premium_MaxChatsInFolderText("\(limit)", "\(premiumLimit)").string + string = component.count >= premiumLimit ? strings.Premium_MaxChatsInFolderFinalText("\(premiumLimit)").string : strings.Premium_MaxChatsInFolderText("\(limit)", "\(premiumLimit)").string defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" badgePosition = CGFloat(component.count) / CGFloat(premiumLimit) @@ -711,7 +713,7 @@ private final class LimitSheetContent: CombinedComponent { let premiumLimit = state.premiumLimits.maxPinnedChatCount iconName = "Premium/Pin" badgeText = "\(component.count)" - string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string + string = component.count >= premiumLimit ? strings.Premium_MaxPinsFinalText("\(premiumLimit)").string : strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" badgePosition = CGFloat(component.count) / CGFloat(premiumLimit) @@ -719,8 +721,8 @@ private final class LimitSheetContent: CombinedComponent { let limit = Int64(state.limits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100 let premiumLimit = Int64(state.premiumLimits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100 iconName = "Premium/File" - badgeText = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) - string = strings.Premium_MaxFileSizeText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string + badgeText = dataSizeString(component.count == 4 ? premiumLimit : limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) + string = component.count == 4 ? strings.Premium_MaxFileSizeFinalText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string : strings.Premium_MaxFileSizeText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string defaultValue = component.count == 4 ? dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) : "" premiumValue = component.count != 4 ? dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) : "" badgePosition = component.count == 4 ? 1.0 : 0.5 @@ -730,7 +732,7 @@ private final class LimitSheetContent: CombinedComponent { let premiumLimit = component.count + 1 iconName = "Premium/Account" badgeText = "\(component.count)" - string = strings.Premium_MaxAccountsText("\(component.count)").string + string = component.count >= premiumLimit ? strings.Premium_MaxAccountsFinalText("\(premiumLimit)").string : strings.Premium_MaxAccountsText("\(limit)").string defaultValue = component.count > limit ? "\(limit)" : "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" if component.count == limit { diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 0748e9b586..26c8b0e3c4 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -928,6 +928,7 @@ public class PremimLimitsListScreen: ViewController { super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 })) self.navigationPresentation = .flatModal + self.statusBar.statusBarStyle = .Ignore } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/PremiumUI/Sources/PremiumStarComponent.swift b/submodules/PremiumUI/Sources/PremiumStarComponent.swift index d645de56c3..9f5532e27b 100644 --- a/submodules/PremiumUI/Sources/PremiumStarComponent.swift +++ b/submodules/PremiumUI/Sources/PremiumStarComponent.swift @@ -7,7 +7,7 @@ import SceneKit import GZip import AppBundle -private let sceneVersion: Int = 2 +private let sceneVersion: Int = 3 private func deg2rad(_ number: Float) -> Float { return number * .pi / 180 diff --git a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift index 937875a754..1973691b64 100644 --- a/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/ReactionsCarouselComponent.swift @@ -133,6 +133,10 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { } } + for reaction in reactions { + sortedReactions.append(reaction) + } + self.reactions = sortedReactions self.scrollNode = ASScrollNode() @@ -537,19 +541,21 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { let relativeAngle = calculateRelativeAngle(angle) let distance = abs(relativeAngle) / CGFloat.pi + var updatedAngle = angle + updatedAngle += 10 * cos(updatedAngle) + let point = CGPoint( - x: cos(angle), - y: sin(angle) + x: cos(updatedAngle), + y: sin(updatedAngle) ) let itemFrame = CGRect(origin: CGPoint(x: size.width * 0.5 + point.x * areaSize.width * 0.5 - itemSize.width * 0.5, y: size.height * 0.5 + point.y * areaSize.height * 0.5 - itemSize.height * 0.5), size: itemSize) containerNode.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) containerNode.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY) - transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.65) + transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.8) itemNode.frame = CGRect(origin: CGPoint(), size: itemFrame.size) itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition) - } } } diff --git a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift index 8b430cd17b..ab904965bc 100644 --- a/submodules/PremiumUI/Sources/StickersCarouselComponent.swift +++ b/submodules/PremiumUI/Sources/StickersCarouselComponent.swift @@ -11,6 +11,7 @@ import TelegramPresentationData import AccountContext import AnimatedStickerNode import TelegramAnimatedStickerNode +import ShimmerEffect final class StickersCarouselComponent: Component { public typealias EnvironmentType = DemoPageEnvironment @@ -88,15 +89,19 @@ private class StickerNode: ASDisplayNode { public var animationNode: AnimatedStickerNode? public var additionalAnimationNode: AnimatedStickerNode? + private var placeholderNode: StickerShimmerEffectNode + private let disposable = MetaDisposable() private let effectDisposable = MetaDisposable() + private var setupTimestamp: Double? + init(context: AccountContext, file: TelegramMediaFile) { self.context = context self.file = file self.imageNode = TransformImageNode() - + if file.isPremiumSticker { let animationNode = AnimatedStickerNode() self.animationNode = animationNode @@ -123,6 +128,8 @@ private class StickerNode: ASDisplayNode { self.animationNode = nil } + self.placeholderNode = StickerShimmerEffectNode() + super.init() self.isUserInteractionEnabled = false @@ -136,6 +143,38 @@ private class StickerNode: ASDisplayNode { if let additionalAnimationNode = self.additionalAnimationNode { self.addSubnode(additionalAnimationNode) } + + self.addSubnode(self.placeholderNode) + + var firstTime = true + self.imageNode.imageUpdated = { [weak self] image in + guard let strongSelf = self else { + return + } + if image != nil { + strongSelf.removePlaceholder(animated: !firstTime) + } + firstTime = false + } + + if let animationNode = self.animationNode { + animationNode.started = { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.imageNode.alpha = 0.0 + + let current = CACurrentMediaTime() + if let setupTimestamp = strongSelf.setupTimestamp, current - setupTimestamp > 0.3 { + if !strongSelf.placeholderNode.alpha.isZero { + strongSelf.removePlaceholder(animated: true) + } + } else { + strongSelf.removePlaceholder(animated: false) + } + } + } } deinit { @@ -143,6 +182,18 @@ private class StickerNode: ASDisplayNode { self.effectDisposable.dispose() } + + private func removePlaceholder(animated: Bool) { + if !animated { + self.placeholderNode.removeFromSupernode() + } else { + self.placeholderNode.alpha = 0.0 + self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in + self?.placeholderNode.removeFromSupernode() + }) + } + } + private var visibility: Bool = false private var centrality: Bool = false @@ -154,6 +205,12 @@ private class StickerNode: ASDisplayNode { public func setVisible(_ visible: Bool) { self.visibility = visible self.updatePlayback() + + self.setupTimestamp = CACurrentMediaTime() + } + + func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.placeholderNode.updateAbsoluteRect(rect, within: containerSize) } private func updatePlayback() { @@ -196,6 +253,11 @@ private class StickerNode: ASDisplayNode { additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size) } } + + let placeholderFrame = CGRect(origin: .zero, size: size) + let thumbnailDimensions = PixelDimensions(width: 512, height: 512) + self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: self.file.immediateThumbnailData, size: placeholderFrame.size, imageSize: thumbnailDimensions.cgSize) + self.placeholderNode.frame = placeholderFrame } } } @@ -527,6 +589,7 @@ private class StickersCarouselNode: ASDisplayNode, UIScrollViewDelegate { containerNode.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY) transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.75) transition.updateAlpha(node: containerNode, alpha: 1.0 - distance * 0.6) + itemNode.updateAbsoluteRect(itemFrame, within: size) let isVisible = self.visibility && itemFrame.intersects(bounds) itemNode.setVisible(isVisible) diff --git a/submodules/PromptUI/BUILD b/submodules/PromptUI/BUILD new file mode 100644 index 0000000000..aec3f5594f --- /dev/null +++ b/submodules/PromptUI/BUILD @@ -0,0 +1,24 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "PromptUI", + module_name = "PromptUI", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/Display:Display", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/AccountContext:AccountContext", + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/PromptUI/Sources/PromptController.swift b/submodules/PromptUI/Sources/PromptController.swift new file mode 100644 index 0000000000..ae86e0ba03 --- /dev/null +++ b/submodules/PromptUI/Sources/PromptController.swift @@ -0,0 +1,424 @@ +import Foundation +import UIKit +import SwiftSignalKit +import AsyncDisplayKit +import Display +import Postbox +import TelegramCore +import TelegramPresentationData +import AccountContext + +private final class PromptInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { + private var theme: PresentationTheme + private let backgroundNode: ASImageNode + private let textInputNode: EditableTextNode + private let placeholderNode: ASTextNode + + var updateHeight: (() -> Void)? + var complete: (() -> Void)? + var textChanged: ((String) -> Void)? + + private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0) + private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0) + + var text: String { + get { + return self.textInputNode.attributedText?.string ?? "" + } + set { + self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor) + self.placeholderNode.isHidden = !newValue.isEmpty + } + } + + var placeholder: String = "" { + didSet { + self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) + } + } + + init(theme: PresentationTheme, placeholder: String) { + self.theme = theme + + self.backgroundNode = ASImageNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0) + + self.textInputNode = EditableTextNode() + self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor] + self.textInputNode.clipsToBounds = true + self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0) + self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0) + self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance + self.textInputNode.keyboardType = .default + self.textInputNode.autocapitalizationType = .sentences + self.textInputNode.returnKeyType = .done + self.textInputNode.autocorrectionType = .default + self.textInputNode.tintColor = theme.actionSheet.controlAccentColor + + self.placeholderNode = ASTextNode() + self.placeholderNode.isUserInteractionEnabled = false + self.placeholderNode.displaysAsynchronously = false + self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) + + super.init() + + self.textInputNode.delegate = self + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.textInputNode) + self.addSubnode(self.placeholderNode) + } + + func updateTheme(_ theme: PresentationTheme) { + self.theme = theme + + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0) + self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance + self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor) + self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor + } + + func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + let backgroundInsets = self.backgroundInsets + let inputInsets = self.inputInsets + + let textFieldHeight = self.calculateTextFieldMetrics(width: width) + let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom + + let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + + let placeholderSize = self.placeholderNode.measure(backgroundFrame.size) + transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize)) + + transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height))) + + return panelHeight + } + + func activateInput() { + self.textInputNode.becomeFirstResponder() + } + + func deactivateInput() { + self.textInputNode.resignFirstResponder() + } + + @objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) { + self.updateTextNodeText(animated: true) + self.textChanged?(editableTextNode.textView.text) + self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty + } + + func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + if text == "\n" { + self.complete?() + return false + } + return true + } + + private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat { + let backgroundInsets = self.backgroundInsets + let inputInsets = self.inputInsets + + let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height)) + + return min(61.0, max(33.0, unboundTextFieldHeight)) + } + + private func updateTextNodeText(animated: Bool) { + let backgroundInsets = self.backgroundInsets + + let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width) + + let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom + if !self.bounds.size.height.isEqual(to: panelHeight) { + self.updateHeight?() + } + } + + @objc func clearPressed() { + self.textInputNode.attributedText = nil + self.deactivateInput() + } +} + +private final class PromptAlertContentNode: AlertContentNode { + private let strings: PresentationStrings + private let text: String + + private let textNode: ASTextNode + let inputFieldNode: PromptInputFieldNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private let disposable = MetaDisposable() + + private var validLayout: CGSize? + + private let hapticFeedback = HapticFeedback() + + var complete: (() -> Void)? { + didSet { + self.inputFieldNode.complete = self.complete + } + } + + override var dismissOnOutsideTap: Bool { + return self.isUserInteractionEnabled + } + + init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], text: String, value: String?) { + self.strings = strings + self.text = text + + self.textNode = ASTextNode() + self.textNode.maximumNumberOfLines = 2 + + self.inputFieldNode = PromptInputFieldNode(theme: ptheme, placeholder: "") + self.inputFieldNode.text = value ?? "" + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + self.addSubnode(self.textNode) + + self.addSubnode(self.inputFieldNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + self.actionNodes.last?.actionEnabled = true + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + + self.inputFieldNode.updateHeight = { [weak self] in + if let strongSelf = self { + if let _ = strongSelf.validLayout { + strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring)) + } + } + } + +// self.inputFieldNode.textChanged = { [weak self] text in +// if let strongSelf = self, let lastNode = strongSelf.actionNodes.last { +// lastNode.actionEnabled = !text.isEmpty +// } +// } + + self.updateTheme(theme) + } + + deinit { + self.disposable.dispose() + } + + var value: String { + return self.inputFieldNode.text + } + + override func updateTheme(_ theme: AlertControllerTheme) { + self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + var size = size + size.width = min(size.width, 270.0) + let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude) + + let hadValidLayout = self.validLayout != nil + + self.validLayout = size + + var origin: CGPoint = CGPoint(x: 0.0, y: 20.0) + let spacing: CGFloat = 5.0 + + let titleSize = CGSize() +// transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize)) +// origin.y += titleSize.height + 4.0 + + let textSize = self.textNode.measure(measureSize) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)) + origin.y += textSize.height + 6.0 + spacing + + let actionButtonHeight: CGFloat = 44.0 + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = TextAlertContentActionLayout.horizontal + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0) + + var contentWidth = max(titleSize.width, minActionsWidth) + contentWidth = max(contentWidth, 234.0) + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let resultWidth = contentWidth + insets.left + insets.right + + let inputFieldWidth = resultWidth + let inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition) + let inputHeight = inputFieldHeight + transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight)) + transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0) + + let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + actionsHeight + insets.top + insets.bottom) + + transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + if !hadValidLayout { + self.inputFieldNode.activateInput() + } + + return resultSize + } + + func animateError() { + self.inputFieldNode.layer.addShakeAnimation() + self.hapticFeedback.error() + } +} + +public func promptController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, text: String, value: String?, apply: @escaping (String?) -> Void) -> AlertController { + let presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 } + + var dismissImpl: ((Bool) -> Void)? + var applyImpl: (() -> Void)? + + let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + dismissImpl?(true) + apply(nil) + }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: { + dismissImpl?(true) + applyImpl?() + })] + + let contentNode = PromptAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, text: text, value: value) + contentNode.complete = { + applyImpl?() + } + applyImpl = { [weak contentNode] in + guard let contentNode = contentNode else { + return + } + apply(contentNode.value) + } + + let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) + let presentationDataDisposable = (updatedPresentationData?.signal ?? sharedContext.presentationData).start(next: { [weak controller, weak contentNode] presentationData in + controller?.theme = AlertControllerTheme(presentationData: presentationData) + contentNode?.inputFieldNode.updateTheme(presentationData.theme) + }) + controller.dismissed = { + presentationDataDisposable.dispose() + } + dismissImpl = { [weak controller] animated in + contentNode.inputFieldNode.deactivateInput() + if animated { + controller?.dismissAnimated() + } else { + controller?.dismiss() + } + } + return controller +} diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift index 0b73fb9202..8db843527c 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextBackgroundNode.swift @@ -42,7 +42,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode { private let backgroundNode: NavigationBackgroundNode private let maskLayer: SimpleLayer - private let backgroundLayer: SimpleLayer + private let backgroundClippingLayer: SimpleLayer + private let backgroundMaskNode: ASDisplayNode private let backgroundShadowLayer: SimpleLayer private let largeCircleLayer: SimpleLayer private let largeCircleShadowLayer: SimpleLayer @@ -51,23 +52,24 @@ final class ReactionContextBackgroundNode: ASDisplayNode { private var theme: PresentationTheme? - init(largeCircleSize: CGFloat, smallCircleSize: CGFloat) { + init(largeCircleSize: CGFloat, smallCircleSize: CGFloat, maskNode: ASDisplayNode) { self.largeCircleSize = largeCircleSize self.smallCircleSize = smallCircleSize self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true) self.maskLayer = SimpleLayer() - self.backgroundLayer = SimpleLayer() + self.backgroundClippingLayer = SimpleLayer() + self.backgroundClippingLayer.cornerRadius = 52.0 + self.backgroundClippingLayer.masksToBounds = true + self.backgroundMaskNode = maskNode + self.backgroundShadowLayer = SimpleLayer() self.largeCircleLayer = SimpleLayer() self.largeCircleShadowLayer = SimpleLayer() self.smallCircleLayer = SimpleLayer() self.smallCircleShadowLayer = SimpleLayer() - self.backgroundLayer.backgroundColor = UIColor.black.cgColor - self.backgroundLayer.masksToBounds = true - self.largeCircleLayer.backgroundColor = UIColor.black.cgColor self.largeCircleLayer.masksToBounds = true self.largeCircleLayer.cornerRadius = largeCircleSize / 2.0 @@ -77,7 +79,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode { self.smallCircleLayer.cornerRadius = smallCircleSize / 2.0 if #available(iOS 13.0, *) { - self.backgroundLayer.cornerCurve = .circular +// self.backgroundLayer.cornerCurve = .circular self.largeCircleLayer.cornerCurve = .circular self.smallCircleLayer.cornerCurve = .circular } @@ -96,7 +98,9 @@ final class ReactionContextBackgroundNode: ASDisplayNode { self.maskLayer.addSublayer(self.smallCircleLayer) self.maskLayer.addSublayer(self.largeCircleLayer) - self.maskLayer.addSublayer(self.backgroundLayer) + self.maskLayer.addSublayer(self.backgroundClippingLayer) + + self.backgroundClippingLayer.addSublayer(self.backgroundMaskNode.layer) self.backgroundNode.layer.mask = self.maskLayer } @@ -137,12 +141,14 @@ final class ReactionContextBackgroundNode: ASDisplayNode { } var backgroundFrame = CGRect(origin: CGPoint(), size: size) + var backgroundMaskNodeFrame = backgroundFrame if isMinimized { let updatedHeight = floor(size.height * 0.9) backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - updatedHeight), size: CGSize(width: size.width, height: updatedHeight)) + backgroundMaskNodeFrame = backgroundMaskNodeFrame.offsetBy(dx: 0.0, dy: (updatedHeight - backgroundMaskNodeFrame.height) * 0.5) } - transition.updateCornerRadius(layer: self.backgroundLayer, cornerRadius: backgroundFrame.height / 2.0) + transition.updateCornerRadius(layer: self.backgroundClippingLayer, cornerRadius: backgroundFrame.height / 2.0) let largeCircleFrame: CGRect let smallCircleFrame: CGRect @@ -156,7 +162,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode { let contentBounds = backgroundFrame.insetBy(dx: -10.0, dy: -10.0).union(largeCircleFrame).union(smallCircleFrame) - transition.updateFrame(layer: self.backgroundLayer, frame: backgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true) + transition.updateFrame(node: self.backgroundMaskNode, frame: backgroundMaskNodeFrame, beginWithCurrentState: true) + transition.updateFrame(layer: self.backgroundClippingLayer, frame: backgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true) transition.updateFrame(layer: self.largeCircleLayer, frame: largeCircleFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true) transition.updateFrame(layer: self.smallCircleLayer, frame: smallCircleFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true) @@ -181,8 +188,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode { self.largeCircleLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay) self.largeCircleShadowLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay) - self.backgroundLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.01, delay: mainCircleDelay) - self.backgroundLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay) + self.backgroundClippingLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.01, delay: mainCircleDelay) + self.backgroundClippingLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay) self.backgroundShadowLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay) } @@ -197,14 +204,14 @@ final class ReactionContextBackgroundNode: ASDisplayNode { let visualSourceBackgroundFrame = sourceBackgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY) let sourceShadowFrame = visualSourceBackgroundFrame.insetBy(dx: -shadowInset, dy: -shadowInset) - self.backgroundLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: visualSourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) - self.backgroundLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualSourceBackgroundFrame.size)), to: NSValue(cgRect: self.backgroundLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) + self.backgroundClippingLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: visualSourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) + self.backgroundClippingLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualSourceBackgroundFrame.size)), to: NSValue(cgRect: self.backgroundClippingLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) self.backgroundShadowLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceShadowFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true) self.backgroundShadowLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceShadowFrame.size)), to: NSValue(cgRect: self.backgroundShadowLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping) } func animateOut() { - self.backgroundLayer.animateAlpha(from: CGFloat(self.backgroundLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) + self.backgroundClippingLayer.animateAlpha(from: CGFloat(self.backgroundClippingLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) self.backgroundShadowLayer.animateAlpha(from: CGFloat(self.backgroundShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false) self.largeCircleLayer.animateAlpha(from: CGFloat(self.largeCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false) self.largeCircleShadowLayer.animateAlpha(from: CGFloat(self.largeCircleShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false) diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 0a6a09a083..12692c3fa4 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -73,9 +73,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { private let contentContainer: ASDisplayNode private let contentContainerMask: UIImageView + private let leftBackgroundMaskNode: ASDisplayNode + private let rightBackgroundMaskNode: ASDisplayNode + private let backgroundMaskNode: ASDisplayNode private let scrollNode: ASScrollNode private let previewingItemContainer: ASDisplayNode private var visibleItemNodes: [Int: ReactionItemNode] = [:] + private var visibleItemMaskNodes: [Int: ASDisplayNode] = [:] private var longPressRecognizer: UILongPressGestureRecognizer? private var longPressTimer: SwiftSignalKit.Timer? @@ -101,7 +105,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { self.theme = theme self.items = items - self.backgroundNode = ReactionContextBackgroundNode(largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize) + self.backgroundMaskNode = ASDisplayNode() + self.backgroundNode = ReactionContextBackgroundNode(largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode) + self.leftBackgroundMaskNode = ASDisplayNode() + self.leftBackgroundMaskNode.backgroundColor = .black + self.rightBackgroundMaskNode = ASDisplayNode() + self.rightBackgroundMaskNode.backgroundColor = .black + self.backgroundMaskNode.addSubnode(self.leftBackgroundMaskNode) + self.backgroundMaskNode.addSubnode(self.rightBackgroundMaskNode) self.scrollNode = ASScrollNode() self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true @@ -275,6 +286,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { highlightedReactionIndex = nil } + var currentMaskFrame: CGRect? + var maskTransition: ContainedViewLayoutTransition? + var validIndices = Set() var nextX: CGFloat = sideInset for i in 0 ..< self.items.count { @@ -324,21 +338,38 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { var animateIn = false + let maskNode: ASDisplayNode? let itemNode: ReactionItemNode var itemTransition = transition if let current = self.visibleItemNodes[i] { itemNode = current + maskNode = self.visibleItemMaskNodes[i] } else { animateIn = self.didAnimateIn itemTransition = .immediate if case let .reaction(item) = self.items[i] { itemNode = ReactionNode(context: self.context, theme: self.theme, item: item) + maskNode = nil } else { itemNode = PremiumReactionsNode(theme: self.theme) + maskNode = itemNode.maskNode } self.visibleItemNodes[i] = itemNode + self.scrollNode.addSubnode(itemNode) + + if let maskNode = maskNode { + self.visibleItemMaskNodes[i] = maskNode + self.backgroundMaskNode.addSubnode(maskNode) + } + } + maskTransition = itemTransition + + if let maskNode = maskNode { + let maskFrame = CGRect(origin: CGPoint(x: -self.scrollNode.view.contentOffset.x + itemFrame.minX, y: 0.0), size: CGSize(width: itemFrame.width, height: itemFrame.height + 12.0)) + itemTransition.updateFrame(node: maskNode, frame: maskFrame) + currentMaskFrame = maskFrame } if !itemNode.isExtracted { @@ -370,6 +401,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } } + if let currentMaskFrame = currentMaskFrame { + let transition = maskTransition ?? transition + transition.updateFrame(node: self.leftBackgroundMaskNode, frame: CGRect(x: -1000.0 + currentMaskFrame.minX, y: 0.0, width: 1000.0, height: 52.0)) + transition.updateFrame(node: self.rightBackgroundMaskNode, frame: CGRect(x: currentMaskFrame.maxX, y: 0.0, width: 1000.0, height: 52.0)) + } else { + self.leftBackgroundMaskNode.frame = CGRect(x: 0.0, y: 0.0, width: 1000.0, height: 52.0) + self.rightBackgroundMaskNode.frame = CGRect(origin: .zero, size: .zero) + } + var removedIndices: [Int] = [] for (index, itemNode) in self.visibleItemNodes { if !validIndices.contains(index) { @@ -377,8 +417,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { itemNode.removeFromSupernode() } } + for (index, maskNode) in self.visibleItemMaskNodes { + if !validIndices.contains(index) { + maskNode.removeFromSupernode() + } + } for index in removedIndices { self.visibleItemNodes.removeValue(forKey: index) + self.visibleItemMaskNodes.removeValue(forKey: index) } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 0fa71707a6..989b7b81f5 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -38,6 +38,8 @@ private let font = Font.medium(13.0) protocol ReactionItemNode: ASDisplayNode { var isExtracted: Bool { get } + var maskNode: ASDisplayNode? { get } + func appear(animated: Bool) func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) } @@ -107,6 +109,10 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { self.fetchFullAnimationDisposable?.dispose() } + var maskNode: ASDisplayNode? { + return nil + } + func appear(animated: Bool) { if animated { self.animateInAnimationNode?.visibility = true @@ -351,13 +357,29 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode { var isExtracted: Bool = false let imageNode: ASImageNode + let maskImageNode: ASImageNode init(theme: PresentationTheme) { self.imageNode = ASImageNode() self.imageNode.contentMode = .center self.imageNode.displaysAsynchronously = false self.imageNode.isUserInteractionEnabled = false - self.imageNode.image = generatePremiumReactionIcon() + self.imageNode.image = UIImage(bundleImageName: "Premium/ReactionsForeground") + + self.maskImageNode = ASImageNode() + if let backgroundImage = UIImage(bundleImageName: "Premium/ReactionsBackground") { + self.maskImageNode.image = generateImage(CGSize(width: 40.0, height: 52.0), contextGenerator: { size, context in + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + + if let cgImage = backgroundImage.cgImage { + let maskFrame = CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 10.0) + context.clip(to: maskFrame, mask: cgImage) + } + context.setBlendMode(.clear) + context.fill(CGRect(origin: .zero, size: size)) + }) + } super.init() @@ -371,4 +393,9 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode { func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) { self.imageNode.frame = CGRect(origin: CGPoint(), size: size) } + + + var maskNode: ASDisplayNode? { + return self.maskImageNode + } } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift index 2ccdd27dee..ea6d3fa44d 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsAppIconItem.swift @@ -99,9 +99,11 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode { private let iconNode: ASImageNode private let overlayNode: ASImageNode private let lockNode: ASImageNode - private let textNode: ASTextNode + private let textNode: ImmediateTextNode private var action: (() -> Void)? + private var locked = false + override init() { self.iconNode = ASImageNode() self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0)) @@ -112,10 +114,11 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode { self.overlayNode.isLayerBacked = true self.lockNode = ASImageNode() + self.lockNode.contentMode = .scaleAspectFit self.lockNode.displaysAsynchronously = false self.lockNode.isUserInteractionEnabled = false - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false @@ -128,6 +131,7 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode { } func setup(theme: PresentationTheme, icon: UIImage, title: NSAttributedString, locked: Bool, color: UIColor, bordered: Bool, selected: Bool, action: @escaping () -> Void) { + self.locked = locked self.iconNode.image = icon self.textNode.attributedText = title self.overlayNode.image = generateBorderImage(theme: theme, bordered: bordered, selected: selected) @@ -135,6 +139,8 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode { self.action = { action() } + + self.setNeedsLayout() } override func didLoad() { @@ -154,10 +160,17 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode { let bounds = self.bounds - self.iconNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0)) - self.overlayNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0)) - self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 87.0), size: CGSize(width: bounds.size.width, height: 16.0)) - self.lockNode.frame = CGRect(x: 9.0, y: 90.0, width: 6.0, height: 8.0) + self.iconNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0)) + self.overlayNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0)) + + let textSize = self.textNode.updateLayout(bounds.size) + var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - textSize.width)) / 2.0, y: 87.0), size: textSize) + if self.locked { + textFrame = textFrame.offsetBy(dx: 5.0, dy: 0.0) + } + self.textNode.frame = textFrame + + self.lockNode.frame = CGRect(x: self.textNode.frame.minX - 10.0, y: 90.0, width: 6.0, height: 8.0) } } @@ -331,12 +344,12 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode { name = item.strings.Appearance_AppIconNew1 case "New2": name = item.strings.Appearance_AppIconNew2 - case "PremiumCosmic": - name = "Cosmic" - case "PremiumCherry": - name = "Cherry" - case "PremiumDuck": - name = "Duck" + case "Premium": + name = "Premium" + case "PremiumBlack": + name = "Black" + case "PremiumTurbo": + name = "Turbo" default: name = icon.name } diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift index db524dc28e..9055ca93b5 100644 --- a/submodules/ShareController/Sources/ShareController.swift +++ b/submodules/ShareController/Sources/ShareController.swift @@ -39,6 +39,10 @@ public enum ShareControllerExternalStatus { case done } +public enum ShareControllerError { + case fileTooBig(Int64) +} + public struct ShareControllerSegmentedValue { let title: String let subject: ShareControllerSubject @@ -61,7 +65,7 @@ public enum ShareControllerSubject { case image([ImageRepresentationWithReference]) case media(AnyMediaReference) case mapMedia(TelegramMediaMap) - case fromExternal(([PeerId], String, Account, Bool) -> Signal) + case fromExternal(([PeerId], String, Account, Bool) -> Signal) } private enum ExternalShareItem { @@ -657,18 +661,20 @@ public final class ShareController: ViewController { let queue = Queue.mainQueue() var displayedError = false return combineLatest(queue: queue, shareSignals) - |> mapToSignal { messageIdSets -> Signal in - var statuses: [Signal<(MessageId, PendingMessageStatus?, PendingMessageFailureReason?), NoError>] = [] + |> castError(ShareControllerError.self) + |> mapToSignal { messageIdSets -> Signal in + var statuses: [Signal<(MessageId, PendingMessageStatus?, PendingMessageFailureReason?), ShareControllerError>] = [] for messageIds in messageIdSets { for case let id? in messageIds { statuses.append(account.pendingMessageManager.pendingMessageStatus(id) + |> castError(ShareControllerError.self) |> map { status, error -> (MessageId, PendingMessageStatus?, PendingMessageFailureReason?) in return (id, status, error) }) } } return combineLatest(queue: queue, statuses) - |> mapToSignal { statuses -> Signal in + |> mapToSignal { statuses -> Signal in var hasStatuses = false for (id, status, error) in statuses { if let error = error { diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 64ccea13ea..fd5f9b8214 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -59,7 +59,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate var dismiss: ((Bool) -> Void)? var cancel: (() -> Void)? - var share: ((String, [PeerId], Bool, Bool) -> Signal)? + var share: ((String, [PeerId], Bool, Bool) -> Signal)? var shareExternal: ((Bool) -> Signal)? var switchToAnotherAccount: (() -> Void)? var debugAction: (() -> Void)? @@ -673,7 +673,6 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate }) } - // if !self.fromForeignApp { self.animateOut(shared: true, completion: { }) diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 33ddb6af04..d0bad3b932 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -47,16 +47,21 @@ private func scalePhotoImage(_ image: UIImage, dimensions: CGSize) -> UIImage? { } } -private func preparedShareItem(account: Account, to peerId: PeerId, value: [String: Any]) -> Signal { +public enum PreparedShareItemError { + case generic + case fileTooBig(Int64) +} + +private func preparedShareItem(account: Account, to peerId: PeerId, value: [String: Any]) -> Signal { if let imageData = value["scaledImageData"] as? Data, let dimensions = value["scaledImageDimensions"] as? NSValue { let diminsionsSize = dimensions.cgSizeValue return .single(.preparing(false)) |> then( standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(diminsionsSize.width), height: Int32(diminsionsSize.height))) - |> mapError { _ -> Void in - return Void() + |> mapError { _ -> PreparedShareItemError in + return .generic } - |> mapToSignal { event -> Signal in + |> mapToSignal { event -> Signal in switch event { case let .progress(value): return .single(.progress(value)) @@ -72,10 +77,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.preparing(false)) |> then( standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))) - |> mapError { _ -> Void in - return Void() + |> mapError { _ -> PreparedShareItemError in + return .generic } - |> mapToSignal { event -> Signal in + |> mapToSignal { event -> Signal in switch event { case let .progress(value): return .single(.progress(value)) @@ -111,7 +116,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri } var finalDuration: Double = CMTimeGetSeconds(asset.duration) - func loadValues(_ avAsset: AVURLAsset) -> Signal { + func loadValues(_ avAsset: AVURLAsset) -> Signal { return Signal { subscriber in avAsset.loadValuesAsynchronously(forKeys: ["tracks", "duration", "playable"]) { subscriber.putNext(avAsset) @@ -123,7 +128,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.preparing(true)) |> then( loadValues(asset) - |> mapToSignal { asset -> Signal in + |> mapToSignal { asset -> Signal in let preset = adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium let finalDimensions = TGMediaVideoConverter.dimensions(for: asset.originalSize, adjustments: adjustments, preset: preset) @@ -142,10 +147,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: asset.url.path, adjustments: resourceAdjustments) return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024) - |> mapError { _ -> Void in - return Void() + |> mapError { _ -> PreparedShareItemError in + return .generic } - |> mapToSignal { event -> Signal in + |> mapToSignal { event -> Signal in switch event { case let .progress(value): return .single(.progress(value)) @@ -201,7 +206,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.preparing(true)) |> then( convertedData - |> castError(Void.self) + |> castError(PreparedShareItemError.self) |> mapToSignal { data, dimensions, duration, converted in var attributes: [TelegramMediaFileAttribute] = [] let mimeType: String @@ -213,8 +218,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")] } return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 10 * 1024 * 1024) - |> mapError { _ -> Void in return Void() } - |> mapToSignal { event -> Signal in + |> mapError { _ -> PreparedShareItemError in + return .generic + } + |> mapToSignal { event -> Signal in switch event { case let .progress(value): return .single(.progress(value)) @@ -230,8 +237,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.preparing(false)) |> then( standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(scaledImage.size.width), height: Int32(scaledImage.size.height))) - |> mapError { _ -> Void in return Void() } - |> mapToSignal { event -> Signal in + |> mapError { _ -> PreparedShareItemError in + return .generic + } + |> mapToSignal { event -> Signal in switch event { case let .progress(value): return .single(.progress(value)) @@ -251,8 +260,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.preparing(long)) |> then( standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 10 * 1024 * 1024) - |> mapError { _ -> Void in return Void() } - |> mapToSignal { event -> Signal in + |> mapError { _ -> PreparedShareItemError in + return .generic + } + |> mapToSignal { event -> Signal in switch event { case let .progress(value): return .single(.progress(value)) @@ -280,8 +291,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri return .single(.preparing(long)) |> then( standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: mimeType, attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform?.makeData()), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 10 * 1024 * 1024) - |> mapError { _ -> Void in return Void() } - |> mapToSignal { event -> Signal in + |> mapError { _ -> PreparedShareItemError in + return .generic + } + |> mapToSignal { event -> Signal in switch event { case let .progress(value): return .single(.progress(value)) @@ -300,7 +313,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri ) } else if let url = value["url"] as? URL { if TGShareLocationSignals.isLocationURL(url) { - return Signal { subscriber in + return Signal { subscriber in subscriber.putNext(.preparing(false)) let disposable = TGShareLocationSignals.locationMessageContent(for: url).start(next: { value in if let value = value as? TGShareLocationResult { @@ -332,8 +345,8 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri } } -public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [MTSignal], additionalText: String) -> Signal { - var dataSignals: Signal<[String: Any], Void> = .complete() +public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [MTSignal], additionalText: String) -> Signal { + var dataSignals: Signal<[String: Any], PreparedShareItemError> = .complete() for dataItem in dataItems { let wrappedSignal: Signal<[String: Any], NoError> = Signal { subscriber in let disposable = dataItem.start(next: { value in @@ -349,7 +362,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [ dataSignals = dataSignals |> then( wrappedSignal - |> castError(Void.self) + |> castError(PreparedShareItemError.self) |> take(1) ) } @@ -359,7 +372,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [ |> reduceLeft(value: [[String: Any]](), f: { list, rest in return list + rest }) - |> mapToSignal { items -> Signal<[PreparedShareItem], Void> in + |> mapToSignal { items -> Signal<[PreparedShareItem], PreparedShareItemError> in return combineLatest(items.map { preparedShareItem(account: account, to: peerId, value: $0) }) diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index 827381143e..78d84217e6 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -34,6 +34,7 @@ swift_library( "//submodules/MoreButtonNode:MoreButtonNode", "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/PremiumUI:PremiumUI", + "//submodules/OverlayStatusController:OverlayStatusController", ], visibility = [ "//visibility:public", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index afbc4aeef8..443f256dda 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -16,6 +16,8 @@ import UndoUI import ShareController import TextFormat import PremiumUI +import OverlayStatusController +import PresentationDataUtils private enum StickerPackPreviewGridEntry: Comparable, Identifiable { case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool) @@ -577,6 +579,10 @@ private final class StickerPackContainer: ASDisplayNode { self.actionAreaSeparatorNode.alpha = backgroundAlpha } + var onLoading: () -> Void = {} + var onReady: () -> Void = {} + var onError: () -> Void = {} + private var currentContents: LoadedStickerPack? private func updateStickerPackContents(_ contents: LoadedStickerPack, hasPremium: Bool) { self.currentContents = contents @@ -590,6 +596,7 @@ private final class StickerPackContainer: ASDisplayNode { switch contents { case .fetching: + self.onLoading() entries = [] self.buttonNode.setTitle(self.presentationData.strings.Channel_NotificationLoading, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemDisabledTextColor, for: .normal) self.buttonNode.setBackgroundImage(nil, for: []) @@ -620,20 +627,11 @@ private final class StickerPackContainer: ASDisplayNode { self.titleContainer.addSubnode(titlePlaceholderNode) } case .none: - entries = [] - self.buttonNode.setTitle(self.presentationData.strings.Common_Close, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal) - self.buttonNode.setBackgroundImage(nil, for: []) - - for _ in 0 ..< 16 { - let resolvedStableId = self.nextStableId - self.nextStableId += 1 - entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: nil, isEmpty: true, isPremium: false, isLocked: false)) - } - if let titlePlaceholderNode = self.titlePlaceholderNode { - self.titlePlaceholderNode = nil - titlePlaceholderNode.removeFromSupernode() - } + self.onError() + self.controller?.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root)) + self.controller?.dismiss(animated: true, completion: nil) case let .result(info, items, installed): + self.onReady() if !items.isEmpty && self.currentStickerPack == nil { if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne { scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false) @@ -978,6 +976,10 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { return self._ready } + var onLoading: () -> Void = {} + var onReady: () -> Void = {} + var onError: () -> Void = {} + init(context: AccountContext, controller: StickerPackScreenImpl, stickerPacks: [StickerPackReference], initialSelectedStickerPackIndex: Int, modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, openMention: @escaping (String) -> Void) { self.context = context self.controller = controller @@ -1104,6 +1106,15 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { } } }, presentInGlobalOverlay: presentInGlobalOverlay, sendSticker: sendSticker, openMention: openMention, controller: self.controller) + container.onReady = { [weak self] in + self?.onReady() + } + container.onLoading = { [weak self] in + self?.onLoading() + } + container.onError = { [weak self] in + self?.onError() + } self.containerContainingNode.addSubnode(container) self.containers[i] = container } @@ -1380,17 +1391,76 @@ public final class StickerPackScreenImpl: ViewController { })) }) + var loaded = false + var dismissed = false + + var overlayStatusController: ViewController? + let cancelImpl: (() -> Void)? = { [weak self] in + dismissed = true + overlayStatusController?.dismiss() + self?.dismiss() + } + + self.controllerNode.onReady = { [weak self] in + loaded = true + + if let strongSelf = self { + if !dismissed { + if let overlayStatusController = overlayStatusController { + overlayStatusController.dismiss() + } + + if strongSelf.alreadyDidAppear { + strongSelf.controllerNode.animateIn() + } else { + strongSelf.isReady = true + } + + self?.controllerNode.isHidden = false + self?.controllerNode.animateIn() + } + } + } + + let presentationData = self.presentationData + self.controllerNode.onLoading = { [weak self] in + Queue.mainQueue().after(0.15, { + if !loaded { + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + self?.present(controller, in: .window(.root)) + overlayStatusController = controller + } + }) + } + + self.controllerNode.onError = { + loaded = true + + if let overlayStatusController = overlayStatusController { + overlayStatusController.dismiss() + } + } + + self.controllerNode.isHidden = true + self._ready.set(self.controllerNode.ready.get()) super.displayNodeDidLoad() } + private var isReady = false + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if !self.alreadyDidAppear { self.alreadyDidAppear = true - self.controllerNode.animateIn() + + if self.isReady { + self.controllerNode.animateIn() + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index 2cb2f5f900..8afc0dd85f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -147,9 +147,6 @@ public struct ChatListFilterIncludePeers: Equatable, Hashable { return false } - if self.peers.count + self.pinnedPeers.count >= 100 { - return false - } self.peers.insert(peerId, at: 0) return true } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index 7060d8e146..7d61a5a8f3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -85,7 +85,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic if query == "\u{2764}" { query = "\u{2764}\u{FE0F}" } - return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?) in + return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool) in + let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false + var result: [FoundStickerItem] = [] if scope.contains(.installed) { for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) { @@ -108,6 +110,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) { if let item = entry.contents.get(RecentMediaItem.self) { let file = item.media + if file.isPremiumSticker && !isPremium { + continue + } if !currentItems.contains(file.fileId) { for case let .Sticker(displayText, _, _) in file.attributes { @@ -135,6 +140,10 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic var installedAnimatedItems: [FoundStickerItem] = [] for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) { if let item = item as? StickerPackItem { + if item.file.isPremiumSticker && !isPremium { + continue + } + if !currentItems.contains(item.file.fileId) { var stringRepresentations: [String] = [] for key in item.indexKeys { @@ -158,12 +167,18 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic } for file in recentAnimatedItems { + if file.isPremiumSticker && !isPremium { + continue + } if matchingRecentItemsIds.contains(file.fileId) { result.append(FoundStickerItem(file: file, stringRepresentations: [query])) } } for file in recentItems { + if file.isPremiumSticker && !isPremium { + continue + } if matchingRecentItemsIds.contains(file.fileId) { result.append(FoundStickerItem(file: file, stringRepresentations: [query])) } @@ -183,8 +198,8 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic cached = nil } - return (result, cached) - } |> mapToSignal { localItems, cached -> Signal<[FoundStickerItem], NoError> in + return (result, cached, isPremium) + } |> mapToSignal { localItems, cached, isPremium -> Signal<[FoundStickerItem], NoError> in var tempResult: [FoundStickerItem] = localItems if !scope.contains(.remote) { return .single(tempResult) @@ -196,6 +211,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic var cachedAnimatedItems: [FoundStickerItem] = [] for file in cached.items { + if file.isPremiumSticker && !isPremium { + continue + } if !currentItemIds.contains(file.fileId) { if file.isAnimatedSticker || file.isVideoSticker { cachedAnimatedItems.append(FoundStickerItem(file: file, stringRepresentations: [])) @@ -226,6 +244,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic var files: [TelegramMediaFile] = [] for sticker in stickers { if let file = telegramMediaFileFromApiDocument(sticker), let id = file.id { + if file.isPremiumSticker && !isPremium { + continue + } files.append(file) if !currentItemIds.contains(id) { if file.isAnimatedSticker || file.isVideoSticker { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 0a11af13f9..da1c28b02e 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -167,7 +167,6 @@ public enum PresentationResourceKey: Int32 { case chatInputMediaPanelTrendingGifsIcon case chatInputMediaPanelStickersModeIcon case chatInputMediaPanelPremiumIcon - case chatInputMediaStickerGridPremiumIcon case chatInputButtonPanelButtonImage case chatInputButtonPanelButtonHighlightedImage diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 58778dfbb2..e569f326f5 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -319,40 +319,6 @@ public struct PresentationResourcesChat { }) } - public static func chatInputMediaStickerGridPremiumIcon(_ theme: PresentationTheme) -> UIImage? { - return theme.image(PresentationResourceKey.chatInputMediaStickerGridPremiumIcon.rawValue, { theme in - return generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - if let backgroundImage = UIImage(bundleImageName: "Premium/BackgroundIcon"), let foregroundImage = UIImage(bundleImageName: "Premium/ForegroundIcon") { - context.saveGState() - if let cgImage = backgroundImage.cgImage { - context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) - } - - let colorsArray: [CGColor] = [ - UIColor(rgb: 0x6B93FF).cgColor, - UIColor(rgb: 0x6B93FF).cgColor, - UIColor(rgb: 0x976FFF).cgColor, - UIColor(rgb: 0xE46ACE).cgColor, - UIColor(rgb: 0xE46ACE).cgColor - ] - var locations: [CGFloat] = [0.0, 0.15, 0.5, 0.85, 1.0] - let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! - - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) - - context.restoreGState() - - if let cgImage = foregroundImage.cgImage { - context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) - } - context.setFillColor(UIColor.white.cgColor) - context.fill(CGRect(origin: CGPoint(), size: size)) - } - }) - }) - } - public static func chatInputMediaPanelRecentStickersIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInputMediaPanelRecentStickersIconImage.rawValue, { theme in return generateImage(CGSize(width: 42.0, height: 42.0), contextGenerator: { size, context in diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/Contents.json new file mode 100644 index 0000000000..2e34907284 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ReactionsBg.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/ReactionsBg.pdf b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/ReactionsBg.pdf new file mode 100644 index 0000000000..833557c50b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/ReactionsBg.pdf @@ -0,0 +1,344 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.022736 cm +0.000000 0.000000 0.000000 scn +32.000000 16.000000 m +32.000000 7.163445 24.836555 0.000000 16.000000 0.000000 c +7.163444 0.000000 0.000000 7.163445 0.000000 16.000000 c +0.000000 24.836555 7.163444 32.000000 16.000000 32.000000 c +24.836555 32.000000 32.000000 24.836555 32.000000 16.000000 c +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 21.500017 1.022857 cm +0.000000 0.000000 0.000000 scn +2.663995 8.499941 m +2.663995 9.513936 3.486000 10.335941 4.499994 10.335941 c +5.513990 10.335941 6.335995 9.513936 6.335995 8.499941 c +6.335995 6.998853 l +6.172779 7.000001 5.994865 7.000001 5.800001 7.000001 c +3.200000 7.000001 l +3.005131 7.000001 2.827214 7.000001 2.663995 6.998853 c +2.663995 8.499941 l +h +1.335994 6.879089 m +1.335994 8.499941 l +1.335994 10.247370 2.752566 11.663941 4.499994 11.663941 c +6.247424 11.663941 7.663995 10.247370 7.663995 8.499941 c +7.663995 6.879093 l +7.751150 6.852844 7.831669 6.820897 7.907982 6.782014 c +8.284306 6.590267 8.590266 6.284306 8.782013 5.907981 c +9.000000 5.480158 9.000000 4.920106 9.000000 3.800001 c +9.000000 3.200002 l +9.000000 2.079896 9.000000 1.519843 8.782013 1.092020 c +8.590266 0.715696 8.284306 0.409734 7.907982 0.217987 c +7.480158 0.000000 6.920105 0.000000 5.800001 0.000000 c +3.200000 0.000000 l +2.079895 0.000000 1.519843 0.000000 1.092019 0.217987 c +0.715695 0.409734 0.409734 0.715696 0.217987 1.092020 c +0.000000 1.519843 0.000000 2.079895 0.000000 3.200000 c +0.000000 3.800000 l +0.000000 4.920106 0.000000 5.480158 0.217987 5.907981 c +0.409734 6.284306 0.715695 6.590267 1.092019 6.782014 c +1.168328 6.820895 1.248844 6.852841 1.335994 6.879089 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 21.500017 -1.023652 cm +0.000000 0.000000 0.000000 scn +6.335995 9.045362 m +6.328803 8.022530 l +7.358852 8.015287 l +7.358852 9.045362 l +6.335995 9.045362 l +h +2.663995 9.045362 m +1.641138 9.045362 l +1.641138 8.015287 l +2.671187 8.022530 l +2.663995 9.045362 l +h +1.335994 8.925598 m +1.630970 7.946198 l +2.358852 8.165421 l +2.358852 8.925598 l +1.335994 8.925598 l +h +7.663995 8.925602 m +6.641138 8.925602 l +6.641138 8.165421 l +7.369023 7.946200 l +7.663995 8.925602 l +h +7.907982 8.828523 m +7.443614 7.917150 l +7.443614 7.917150 l +7.907982 8.828523 l +h +8.782013 7.954490 m +7.870641 7.490123 l +7.870641 7.490123 l +8.782013 7.954490 l +h +8.782013 3.138529 m +7.870641 3.602897 l +7.870641 3.602897 l +8.782013 3.138529 l +h +7.907982 2.264496 m +7.443614 3.175869 l +7.443614 3.175869 l +7.907982 2.264496 l +h +1.092019 2.264496 m +1.556386 3.175869 l +1.556386 3.175869 l +1.092019 2.264496 l +h +0.217987 3.138529 m +-0.693385 2.674161 l +-0.693385 2.674161 l +0.217987 3.138529 l +h +0.217987 7.954490 m +-0.693385 8.418858 l +0.217987 7.954490 l +h +1.092019 8.828523 m +0.627653 9.739895 l +0.627652 9.739895 l +1.092019 8.828523 l +h +4.499994 13.405307 m +2.921091 13.405307 1.641138 12.125353 1.641138 10.546450 c +3.686852 10.546450 l +3.686852 10.995537 4.050909 11.359593 4.499994 11.359593 c +4.499994 13.405307 l +h +7.358852 10.546450 m +7.358852 12.125353 6.078898 13.405307 4.499994 13.405307 c +4.499994 11.359593 l +4.949081 11.359593 5.313138 10.995537 5.313138 10.546450 c +7.358852 10.546450 l +h +7.358852 9.045362 m +7.358852 10.546450 l +5.313138 10.546450 l +5.313138 9.045362 l +7.358852 9.045362 l +h +5.800001 8.023653 m +5.995777 8.023653 6.169879 8.023647 6.328803 8.022530 c +6.343188 10.068193 l +6.175677 10.069371 5.993953 10.069366 5.800001 10.069366 c +5.800001 8.023653 l +h +3.200000 8.023653 m +5.800001 8.023653 l +5.800001 10.069366 l +3.200000 10.069366 l +3.200000 8.023653 l +h +2.671187 8.022530 m +2.830113 8.023647 3.004220 8.023653 3.200000 8.023653 c +3.200000 10.069366 l +3.006043 10.069366 2.824316 10.069371 2.656802 10.068193 c +2.671187 8.022530 l +h +1.641138 10.546450 m +1.641138 9.045362 l +3.686852 9.045362 l +3.686852 10.546450 l +1.641138 10.546450 l +h +2.358852 8.925598 m +2.358852 10.546450 l +0.313137 10.546450 l +0.313137 8.925598 l +2.358852 8.925598 l +h +2.358852 10.546450 m +2.358852 11.728971 3.317474 12.687593 4.499994 12.687593 c +4.499994 14.733307 l +2.187657 14.733307 0.313137 12.858788 0.313137 10.546450 c +2.358852 10.546450 l +h +4.499994 12.687593 m +5.682515 12.687593 6.641138 11.728971 6.641138 10.546450 c +8.686852 10.546450 l +8.686852 12.858788 6.812332 14.733307 4.499994 14.733307 c +4.499994 12.687593 l +h +6.641138 10.546450 m +6.641138 8.925602 l +8.686852 8.925602 l +8.686852 10.546450 l +6.641138 10.546450 l +h +7.369023 7.946200 m +7.402400 7.936147 7.425747 7.926254 7.443614 7.917150 c +8.372350 9.739895 l +8.237591 9.808558 8.099900 9.862558 7.958967 9.905004 c +7.369023 7.946200 l +h +7.443614 7.917150 m +7.627475 7.823469 7.776959 7.673985 7.870641 7.490123 c +9.693386 8.418858 l +9.403575 8.987644 8.941136 9.450083 8.372349 9.739895 c +7.443614 7.917150 l +h +7.870641 7.490123 m +7.893919 7.444438 7.931211 7.347062 7.953292 7.076812 c +7.976348 6.794622 7.977143 6.423440 7.977143 5.846510 c +10.022858 5.846510 l +10.022858 6.389684 10.023653 6.858581 9.992212 7.243399 c +9.959796 7.640157 9.888095 8.036718 9.693385 8.418858 c +7.870641 7.490123 l +h +7.977143 5.846510 m +7.977143 5.246511 l +10.022858 5.246511 l +10.022858 5.846510 l +7.977143 5.846510 l +h +7.977143 5.246511 m +7.977143 4.669580 7.976348 4.298397 7.953292 4.016207 c +7.931211 3.745956 7.893919 3.648581 7.870641 3.602897 c +9.693385 2.674161 l +9.888095 3.056299 9.959796 3.452862 9.992212 3.849620 c +10.023653 4.234438 10.022858 4.703335 10.022858 5.246511 c +7.977143 5.246511 l +h +7.870641 3.602897 m +7.776958 3.419034 7.627475 3.269550 7.443614 3.175869 c +8.372349 1.353124 l +8.941137 1.642936 9.403575 2.105375 9.693385 2.674161 c +7.870641 3.602897 l +h +7.443614 3.175869 m +7.397930 3.152591 7.300553 3.115298 7.030303 3.093218 c +6.748113 3.070162 6.376931 3.069366 5.800001 3.069366 c +5.800001 1.023652 l +6.343175 1.023652 6.812072 1.022857 7.196890 1.054297 c +7.593648 1.086714 7.990210 1.158415 8.372349 1.353124 c +7.443614 3.175869 l +h +5.800001 3.069366 m +3.200000 3.069366 l +3.200000 1.023652 l +5.800001 1.023652 l +5.800001 3.069366 l +h +3.200000 3.069366 m +2.623069 3.069366 2.251888 3.070162 1.969697 3.093218 c +1.699448 3.115298 1.602071 3.152591 1.556386 3.175869 c +0.627651 1.353124 l +1.009790 1.158415 1.406352 1.086714 1.803111 1.054297 c +2.187928 1.022857 2.656826 1.023652 3.200000 1.023652 c +3.200000 3.069366 l +h +1.556386 3.175869 m +1.372526 3.269550 1.223041 3.419035 1.129359 3.602897 c +-0.693385 2.674161 l +-0.403574 2.105375 0.058864 1.642936 0.627652 1.353124 c +1.556386 3.175869 l +h +1.129359 3.602897 m +1.106082 3.648581 1.068789 3.745956 1.046708 4.016207 c +1.023653 4.298397 1.022857 4.669579 1.022857 5.246509 c +-1.022857 5.246509 l +-1.022857 4.703335 -1.023653 4.234438 -0.992212 3.849620 c +-0.959795 3.452862 -0.888095 3.056299 -0.693385 2.674161 c +1.129359 3.602897 l +h +1.022857 5.246509 m +1.022857 5.846509 l +-1.022857 5.846509 l +-1.022857 5.246509 l +1.022857 5.246509 l +h +1.022857 5.846509 m +1.022857 6.423440 1.023653 6.794621 1.046708 7.076812 c +1.068789 7.347062 1.106082 7.444438 1.129359 7.490123 c +-0.693385 8.418858 l +-0.888095 8.036718 -0.959795 7.640157 -0.992212 7.243399 c +-1.023653 6.858581 -1.022857 6.389684 -1.022857 5.846509 c +1.022857 5.846509 l +h +1.129359 7.490123 m +1.223041 7.673985 1.372525 7.823468 1.556386 7.917150 c +0.627652 9.739895 l +0.058864 9.450083 -0.403574 8.987644 -0.693385 8.418858 c +1.129359 7.490123 l +h +1.556385 7.917150 m +1.574255 7.926254 1.597601 7.936147 1.630970 7.946198 c +1.041019 9.904999 l +0.900087 9.862554 0.762402 9.808554 0.627653 9.739895 c +1.556385 7.917150 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 7556 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 32.000000 32.022736 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000007646 00000 n +0000007669 00000 n +0000007842 00000 n +0000007916 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +7975 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/Contents.json new file mode 100644 index 0000000000..b3d1bacbf7 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ReactionsFg.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/ReactionsFg.pdf b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/ReactionsFg.pdf new file mode 100644 index 0000000000..400f4e421b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/ReactionsFg.pdf @@ -0,0 +1,127 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 21.500017 1.000122 cm +1.000000 1.000000 1.000000 scn +2.663995 8.499941 m +2.663995 9.513935 3.486000 10.335940 4.499994 10.335940 c +5.513989 10.335940 6.335995 9.513935 6.335995 8.499941 c +6.335995 6.998852 l +6.172778 7.000000 5.994864 7.000000 5.800000 7.000000 c +3.200000 7.000000 l +3.005131 7.000000 2.827214 7.000000 2.663995 6.998852 c +2.663995 8.499941 l +h +1.335994 6.879089 m +1.335994 8.499941 l +1.335994 10.247370 2.752566 11.663940 4.499994 11.663940 c +6.247423 11.663940 7.663995 10.247370 7.663995 8.499941 c +7.663995 6.879092 l +7.751150 6.852843 7.831669 6.820896 7.907981 6.782013 c +8.284306 6.590266 8.590266 6.284305 8.782013 5.907981 c +9.000000 5.480157 9.000000 4.920105 9.000000 3.800000 c +9.000000 3.200001 l +9.000000 2.079895 9.000000 1.519842 8.782013 1.092019 c +8.590266 0.715695 8.284306 0.409734 7.907981 0.217987 c +7.480158 0.000000 6.920105 0.000000 5.800000 0.000000 c +3.200000 0.000000 l +2.079895 0.000000 1.519843 0.000000 1.092019 0.217987 c +0.715695 0.409734 0.409734 0.715695 0.217987 1.092019 c +0.000000 1.519842 0.000000 2.079895 0.000000 3.200000 c +0.000000 3.800000 l +0.000000 4.920105 0.000000 5.480157 0.217987 5.907981 c +0.409734 6.284305 0.715695 6.590266 1.092019 6.782013 c +1.168328 6.820894 1.248844 6.852841 1.335994 6.879089 c +h +f* +n +Q +q +1.000000 0.000000 -0.000000 1.000000 15.291429 6.857141 cm +1.000000 1.000000 1.000000 scn +0.000000 17.571430 m +0.000000 17.965918 0.319797 18.285715 0.714286 18.285715 c +0.714286 18.285715 l +1.108775 18.285715 1.428571 17.965919 1.428571 17.571430 c +1.428571 0.714285 l +1.428571 0.319798 1.108775 0.000000 0.714286 0.000000 c +0.714286 0.000000 l +0.319797 0.000000 0.000000 0.319796 0.000000 0.714285 c +0.000000 17.571430 l +h +f +n +Q +q +0.000000 1.000000 -1.000000 0.000000 8.285715 15.285715 cm +1.000000 1.000000 1.000000 scn +0.000000 0.714286 m +0.000000 1.108775 0.319797 1.428571 0.714286 1.428571 c +0.714286 1.428571 l +1.108775 1.428571 1.428571 1.108775 1.428571 0.714286 c +1.428571 -16.142859 l +1.428571 -16.537346 1.108775 -16.857143 0.714286 -16.857143 c +0.714286 -16.857143 l +0.319797 -16.857143 0.000000 -16.537348 0.000000 -16.142859 c +0.000000 0.714286 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 2220 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 32.000000 32.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002310 00000 n +0000002333 00000 n +0000002506 00000 n +0000002580 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2639 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 1e166dcfbf..303e1bf107 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -680,9 +680,9 @@ private func extractAccountManagerState(records: AccountRecordsView `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], NoError> in + |> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], ShareControllerError> in return .single([]) } - |> mapToSignal { messages -> Signal in + |> mapToSignal { messages -> Signal in let resultSignals = peerIds.map({ peerId in return enqueueMessages(account: account, peerId: peerId, messages: messages.map { $0.message }) - |> mapToSignal { _ -> Signal in + |> castError(ShareControllerError.self) + |> mapToSignal { _ -> Signal in return .complete() } }) return combineLatest(resultSignals) - |> mapToSignal { _ -> Signal in + |> mapToSignal { _ -> Signal in return .complete() } |> then(.single(ShareControllerExternalStatus.done)) diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift index 13fb68e647..4b04feff0d 100644 --- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift +++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift @@ -368,10 +368,15 @@ public class ShareRootControllerImpl { let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)! return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText) |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) + |> `catch` { error -> Signal in + switch error { + case .generic: + return .single(nil) + case let .fileTooBig(size): + return .fail(.fileTooBig(size)) + } } - |> mapToSignal { state -> Signal in + |> mapToSignal { state -> Signal in guard let state = state else { return .single(.done) } @@ -382,11 +387,14 @@ public class ShareRootControllerImpl { return .single(.progress(value)) case let .userInteractionRequired(value): return requestUserInteraction(value) - |> mapToSignal { contents -> Signal in + |> castError(ShareControllerError.self) + |> mapToSignal { contents -> Signal in return sentItems(peerIds, contents, account, silently) + |> castError(ShareControllerError.self) } case let .done(contents): return sentItems(peerIds, contents, account, silently) + |> castError(ShareControllerError.self) } } } else { diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index a3161a58c3..09c1980683 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -26,6 +26,7 @@ swift_library( "//submodules/UrlHandling:UrlHandling", "//submodules/MoreButtonNode:MoreButtonNode", "//submodules/BotPaymentsUI:BotPaymentsUI", + "//submodules/PromptUI:PromptUI", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index fcaeb0c054..8f1ef8a75b 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -19,6 +19,7 @@ import LegacyComponents import UrlHandling import MoreButtonNode import BotPaymentsUI +import PromptUI private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] @@ -462,6 +463,33 @@ public final class WebAppController: ViewController, AttachmentContainable { func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { decisionHandler(.prompt) } + + func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { + let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: message, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { + completionHandler() + })]) + self.controller?.present(alertController, in: .window(.root)) + } + + func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { + let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: message, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { + completionHandler(false) + }), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: { + completionHandler(true) + })]) + self.controller?.present(alertController, in: .window(.root)) + } + + func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) { + let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: self.controller?.updatedPresentationData, text: prompt, value: defaultText, apply: { value in + if let value = value { + completionHandler(value) + } else { + completionHandler(nil) + } + }) + self.controller?.present(promptController, in: .window(.root)) + } private var targetContentOffset: CGPoint? func scrollViewDidScroll(_ scrollView: UIScrollView) {