Various improvements
@ -298,9 +298,9 @@ alternate_icon_folders = [
|
|||||||
"WhiteFilledIcon",
|
"WhiteFilledIcon",
|
||||||
"New1",
|
"New1",
|
||||||
"New2",
|
"New2",
|
||||||
"PremiumCosmic",
|
"Premium",
|
||||||
"PremiumCherry",
|
"PremiumBlack",
|
||||||
"PremiumDuck",
|
"PremiumTurbo",
|
||||||
]
|
]
|
||||||
|
|
||||||
[
|
[
|
||||||
|
BIN
Telegram/Telegram-iOS/Premium.alticon/Premium@2x.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
Telegram/Telegram-iOS/Premium.alticon/Premium@3x.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@2x.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
BIN
Telegram/Telegram-iOS/PremiumBlack.alticon/PremiumBlack@3x.png
Normal file
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 33 KiB |
BIN
Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@2x.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
Telegram/Telegram-iOS/PremiumTurbo.alticon/PremiumTurbo@3x.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -7561,13 +7561,13 @@ Sorry for the inconvenience.";
|
|||||||
"Premium.IncreaseLimit" = "Increase Limit";
|
"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.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.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.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.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.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.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. Unpin some of the currently pinned ones.";
|
||||||
"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **@** chats to the top.";
|
|
||||||
"Premium.MaxFavedStickersTitle" = "The Limit of %@ Stickers Reached";
|
"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.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.";
|
"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.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.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.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.Free" = "Free";
|
||||||
"Premium.Premium" = "Premium";
|
"Premium.Premium" = "Premium";
|
||||||
|
@ -22,7 +22,6 @@ swift_library(
|
|||||||
"//submodules/AlertUI:AlertUI",
|
"//submodules/AlertUI:AlertUI",
|
||||||
"//submodules/AppBundle:AppBundle",
|
"//submodules/AppBundle:AppBundle",
|
||||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
|
||||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||||
"//submodules/AnimationUI:AnimationUI",
|
"//submodules/AnimationUI:AnimationUI",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
|
@ -5,7 +5,6 @@ import AsyncDisplayKit
|
|||||||
import Display
|
import Display
|
||||||
import SolidRoundedButtonNode
|
import SolidRoundedButtonNode
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import OverlayStatusController
|
|
||||||
import AnimationUI
|
import AnimationUI
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
@ -56,9 +56,14 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
|||||||
|
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
context.engine.data.get(TelegramEngine.EngineData.Item.Messages.ChatListGroup(id: peerId)),
|
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
|
let location: TogglePeerChatPinnedLocation
|
||||||
var chatListFilter: ChatListFilter?
|
var chatListFilter: ChatListFilter?
|
||||||
if case let .chatList(filter) = source, let chatFilter = filter {
|
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)
|
return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { c, f in
|
}, action: { c, f in
|
||||||
c.dismiss(completion: {
|
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
|
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
||||||
var filters = filters
|
var filters = filters
|
||||||
for i in 0 ..< filters.count {
|
for i in 0 ..< filters.count {
|
||||||
|
@ -265,7 +265,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
|
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
|
||||||
case let .known(offset):
|
case let .known(offset):
|
||||||
if offset <= navigationBarSearchContentHeight + 1.0 && strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil {
|
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 {
|
} else {
|
||||||
if let searchContentNode = strongSelf.searchContentNode {
|
if let searchContentNode = strongSelf.searchContentNode {
|
||||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||||
|
@ -2169,7 +2169,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
videoNode.canAttachContent = true
|
videoNode.canAttachContent = true
|
||||||
videoNode.play()
|
videoNode.play()
|
||||||
|
|
||||||
self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
// self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
||||||
|
self.avatarNode.addSubnode(videoNode)
|
||||||
self.videoNode = videoNode
|
self.videoNode = videoNode
|
||||||
}
|
}
|
||||||
} else if let videoNode = self.videoNode {
|
} else if let videoNode = self.videoNode {
|
||||||
@ -2179,7 +2180,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
if let videoNode = self.videoNode {
|
if let videoNode = self.videoNode {
|
||||||
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
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
|
avatarFrame.origin.x = leftInset - avatarLeftInset + editingOffset + 10.0 + offset
|
||||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||||
if let videoNode = self.videoNode {
|
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
|
var onlineFrame = self.onlineNode.frame
|
||||||
|
@ -173,6 +173,7 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var currentAvailableSize: CGSize?
|
||||||
func update(component: SheetComponent<ChildEnvironmentType>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
func update(component: SheetComponent<ChildEnvironmentType>, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
component.animateOut.connect { [weak self] completion in
|
component.animateOut.connect { [weak self] completion in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -206,6 +207,11 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: 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.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
|
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) {
|
if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) {
|
||||||
self.animateIn()
|
self.animateIn()
|
||||||
} else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) {
|
} else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) {
|
||||||
|
@ -555,8 +555,16 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
|
|
||||||
var actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize)
|
var actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize)
|
||||||
|
|
||||||
|
var contentVerticalOffset: CGFloat = 0.0
|
||||||
if keepInPlace, case .extracted = self.source {
|
if keepInPlace, case .extracted = self.source {
|
||||||
actionsFrame.origin.y = contentRect.minY - contentActionsSpacing - actionsFrame.height
|
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 {
|
if centerActionsHorizontally {
|
||||||
actionsFrame.origin.x = floor(contentParentGlobalFrame.minX + contentRect.midX - actionsFrame.width / 2.0)
|
actionsFrame.origin.x = floor(contentParentGlobalFrame.minX + contentRect.midX - actionsFrame.width / 2.0)
|
||||||
@ -593,15 +601,19 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||||||
transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true)
|
transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true)
|
||||||
|
|
||||||
if let contentNode = contentNode {
|
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
|
let contentHeight: CGFloat
|
||||||
if self.actionsStackNode.topPositionLock != nil {
|
if self.actionsStackNode.topPositionLock != nil {
|
||||||
contentHeight = layout.size.height
|
contentHeight = layout.size.height
|
||||||
|
} else {
|
||||||
|
if keepInPlace, case .extracted = self.source {
|
||||||
|
contentHeight = (layout.statusBarHeight ?? 0.0) + actionsFrame.height + abs(actionsFrame.minY) + bottomInset + layout.intrinsicInsets.bottom
|
||||||
} else {
|
} else {
|
||||||
contentHeight = actionsFrame.maxY + bottomInset + layout.intrinsicInsets.bottom
|
contentHeight = actionsFrame.maxY + bottomInset + layout.intrinsicInsets.bottom
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let contentSize = CGSize(width: layout.size.width, height: contentHeight)
|
let contentSize = CGSize(width: layout.size.width, height: contentHeight)
|
||||||
|
|
||||||
if self.scrollNode.view.contentSize != contentSize {
|
if self.scrollNode.view.contentSize != contentSize {
|
||||||
|
@ -150,9 +150,11 @@ final class GameControllerNode: ViewControllerTracingNode {
|
|||||||
if let strongSelf = self, let message = strongSelf.message {
|
if let strongSelf = self, let message = strongSelf.message {
|
||||||
let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, as: nil) }
|
let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, as: nil) }
|
||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|
|> castError(ShareControllerError.self)
|
||||||
|> then(
|
|> then(
|
||||||
combineLatest(signals)
|
combineLatest(signals)
|
||||||
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, NoError> in return .complete() }
|
|> castError(ShareControllerError.self)
|
||||||
|
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, ShareControllerError> in return .complete() }
|
||||||
)
|
)
|
||||||
|> then(.single(.done))
|
|> then(.single(.done))
|
||||||
} else {
|
} else {
|
||||||
|
@ -80,7 +80,6 @@ public final class InAppPurchaseManager: NSObject {
|
|||||||
self.productRequest = productRequest
|
self.productRequest = productRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public var availableProducts: Signal<[Product], NoError> {
|
public var availableProducts: Signal<[Product], NoError> {
|
||||||
if self.products.isEmpty && self.productRequest == nil {
|
if self.products.isEmpty && self.productRequest == nil {
|
||||||
self.requestProducts()
|
self.requestProducts()
|
||||||
|
@ -169,6 +169,14 @@
|
|||||||
return self.backingItem.asset;
|
return self.backingItem.asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (SSignal *)avAsset
|
||||||
|
{
|
||||||
|
if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]])
|
||||||
|
return ((TGCameraCapturedVideo *)self.asset).avAsset;
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString *)uniqueId
|
- (NSString *)uniqueId
|
||||||
{
|
{
|
||||||
if (self.asset != nil)
|
if (self.asset != nil)
|
||||||
|
@ -48,6 +48,7 @@ swift_library(
|
|||||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||||
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
||||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||||
|
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -81,7 +81,7 @@ public final class PageIndicatorComponent: Component {
|
|||||||
|
|
||||||
private final class PageIndicatorView: UIView {
|
private final class PageIndicatorView: UIView {
|
||||||
var displayCount: Int {
|
var displayCount: Int {
|
||||||
return min(9, self.pageCount)
|
return min(11, self.pageCount)
|
||||||
}
|
}
|
||||||
var dotSize: CGFloat = 8.0
|
var dotSize: CGFloat = 8.0
|
||||||
var dotSpace: CGFloat = 10.0
|
var dotSpace: CGFloat = 10.0
|
||||||
|
@ -76,7 +76,7 @@ private final class PhoneView: UIView {
|
|||||||
self.borderView = UIImageView(image: phoneBorderImage)
|
self.borderView = UIImageView(image: phoneBorderImage)
|
||||||
|
|
||||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6), enableBlur: false)
|
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
|
self.statusNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
@ -634,7 +634,7 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
content: AnyComponent(PhoneDemoComponent(
|
content: AnyComponent(PhoneDemoComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
position: .bottom,
|
position: .bottom,
|
||||||
videoFile: configuration.videos["double_limits"]
|
videoFile: configuration.videos["more_upload"]
|
||||||
)),
|
)),
|
||||||
title: strings.Premium_UploadSize,
|
title: strings.Premium_UploadSize,
|
||||||
text: strings.Premium_UploadSizeInfo,
|
text: strings.Premium_UploadSizeInfo,
|
||||||
@ -740,7 +740,7 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
content: AnyComponent(PhoneDemoComponent(
|
content: AnyComponent(PhoneDemoComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
position: .top,
|
position: .top,
|
||||||
videoFile: configuration.videos["chat_management"]
|
videoFile: configuration.videos["advanced_chat_management"]
|
||||||
)),
|
)),
|
||||||
title: strings.Premium_ChatManagement,
|
title: strings.Premium_ChatManagement,
|
||||||
text: strings.Premium_ChatManagementInfo,
|
text: strings.Premium_ChatManagementInfo,
|
||||||
@ -774,7 +774,7 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
content: AnyComponent(PhoneDemoComponent(
|
content: AnyComponent(PhoneDemoComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
position: .top,
|
position: .top,
|
||||||
videoFile: configuration.videos["userpics"]
|
videoFile: configuration.videos["animated_userpics"]
|
||||||
)),
|
)),
|
||||||
title: strings.Premium_Avatar,
|
title: strings.Premium_Avatar,
|
||||||
text: strings.Premium_AvatarInfo,
|
text: strings.Premium_AvatarInfo,
|
||||||
@ -899,13 +899,15 @@ private final class DemoSheetContent: CombinedComponent {
|
|||||||
animationName: isStandalone && component.subject == .uniqueReactions ? "premium_unlock" : nil,
|
animationName: isStandalone && component.subject == .uniqueReactions ? "premium_unlock" : nil,
|
||||||
iconPosition: .right,
|
iconPosition: .right,
|
||||||
iconSpacing: 4.0,
|
iconSpacing: 4.0,
|
||||||
action: { [weak component] in
|
action: { [weak component, weak state] in
|
||||||
guard let component = component else {
|
guard let component = component else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
component.dismiss()
|
component.dismiss()
|
||||||
|
if let state = state, state.isPremium == false {
|
||||||
component.action()
|
component.action()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
),
|
),
|
||||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||||
transition: context.transition
|
transition: context.transition
|
||||||
|
@ -1158,7 +1158,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
|||||||
tapAction: { attributes, _ in
|
tapAction: { attributes, _ in
|
||||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
||||||
let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
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 {
|
} else {
|
||||||
let context = controller.context
|
let context = controller.context
|
||||||
@ -1362,6 +1364,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
}, completed: { [weak self] in
|
}, completed: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.isPremium = true
|
||||||
|
strongSelf.updated(transition: .easeInOut(duration: 0.25))
|
||||||
strongSelf.completion()
|
strongSelf.completion()
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -1631,13 +1635,40 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
|||||||
context.add(bottomPanel
|
context.add(bottomPanel
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height / 2.0))
|
||||||
.opacity(bottomPanelAlpha)
|
.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
|
context.add(bottomSeparator
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height))
|
||||||
.opacity(bottomPanelAlpha)
|
.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
|
context.add(button
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height + bottomPanelPadding + button.size.height / 2.0))
|
.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
|
completionImpl = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.view.addSubview(ConfettiView(frame: strongSelf.view.bounds))
|
strongSelf.view.addSubview(ConfettiView(frame: strongSelf.view.bounds))
|
||||||
Queue.mainQueue().after(2.0, {
|
|
||||||
self?.dismiss()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,6 +258,8 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
countWidth = 35.0
|
countWidth = 35.0
|
||||||
case 3:
|
case 3:
|
||||||
countWidth = 51.0
|
countWidth = 51.0
|
||||||
|
case 4:
|
||||||
|
countWidth = 60.0
|
||||||
default:
|
default:
|
||||||
countWidth = 51.0
|
countWidth = 51.0
|
||||||
}
|
}
|
||||||
@ -549,7 +551,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
|
|
||||||
context.add(inactiveValue
|
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
|
context.add(activeTitle
|
||||||
@ -693,7 +695,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
let premiumLimit = state.premiumLimits.maxFoldersCount
|
let premiumLimit = state.premiumLimits.maxFoldersCount
|
||||||
iconName = "Premium/Folder"
|
iconName = "Premium/Folder"
|
||||||
badgeText = "\(component.count)"
|
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)" : ""
|
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||||
@ -702,7 +704,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
let premiumLimit = state.premiumLimits.maxFolderChatsCount
|
let premiumLimit = state.premiumLimits.maxFolderChatsCount
|
||||||
iconName = "Premium/Chat"
|
iconName = "Premium/Chat"
|
||||||
badgeText = "\(component.count)"
|
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)" : ""
|
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||||
@ -711,7 +713,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
let premiumLimit = state.premiumLimits.maxPinnedChatCount
|
let premiumLimit = state.premiumLimits.maxPinnedChatCount
|
||||||
iconName = "Premium/Pin"
|
iconName = "Premium/Pin"
|
||||||
badgeText = "\(component.count)"
|
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)" : ""
|
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||||
badgePosition = CGFloat(component.count) / CGFloat(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 limit = Int64(state.limits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100
|
||||||
let premiumLimit = Int64(state.premiumLimits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100
|
let premiumLimit = Int64(state.premiumLimits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100
|
||||||
iconName = "Premium/File"
|
iconName = "Premium/File"
|
||||||
badgeText = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
|
badgeText = dataSizeString(component.count == 4 ? premiumLimit : 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
|
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)) : ""
|
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)) : ""
|
premiumValue = component.count != 4 ? dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) : ""
|
||||||
badgePosition = component.count == 4 ? 1.0 : 0.5
|
badgePosition = component.count == 4 ? 1.0 : 0.5
|
||||||
@ -730,7 +732,7 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
let premiumLimit = component.count + 1
|
let premiumLimit = component.count + 1
|
||||||
iconName = "Premium/Account"
|
iconName = "Premium/Account"
|
||||||
badgeText = "\(component.count)"
|
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)" : ""
|
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||||
if component.count == limit {
|
if component.count == limit {
|
||||||
|
@ -928,6 +928,7 @@ public class PremimLimitsListScreen: ViewController {
|
|||||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 }))
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 }))
|
||||||
|
|
||||||
self.navigationPresentation = .flatModal
|
self.navigationPresentation = .flatModal
|
||||||
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
@ -7,7 +7,7 @@ import SceneKit
|
|||||||
import GZip
|
import GZip
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
|
||||||
private let sceneVersion: Int = 2
|
private let sceneVersion: Int = 3
|
||||||
|
|
||||||
private func deg2rad(_ number: Float) -> Float {
|
private func deg2rad(_ number: Float) -> Float {
|
||||||
return number * .pi / 180
|
return number * .pi / 180
|
||||||
|
@ -133,6 +133,10 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for reaction in reactions {
|
||||||
|
sortedReactions.append(reaction)
|
||||||
|
}
|
||||||
|
|
||||||
self.reactions = sortedReactions
|
self.reactions = sortedReactions
|
||||||
|
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
@ -537,19 +541,21 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let relativeAngle = calculateRelativeAngle(angle)
|
let relativeAngle = calculateRelativeAngle(angle)
|
||||||
let distance = abs(relativeAngle) / CGFloat.pi
|
let distance = abs(relativeAngle) / CGFloat.pi
|
||||||
|
|
||||||
|
var updatedAngle = angle
|
||||||
|
updatedAngle += 10 * cos(updatedAngle)
|
||||||
|
|
||||||
let point = CGPoint(
|
let point = CGPoint(
|
||||||
x: cos(angle),
|
x: cos(updatedAngle),
|
||||||
y: sin(angle)
|
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)
|
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.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||||
containerNode.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
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.frame = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||||
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition)
|
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import TelegramPresentationData
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
final class StickersCarouselComponent: Component {
|
final class StickersCarouselComponent: Component {
|
||||||
public typealias EnvironmentType = DemoPageEnvironment
|
public typealias EnvironmentType = DemoPageEnvironment
|
||||||
@ -88,9 +89,13 @@ private class StickerNode: ASDisplayNode {
|
|||||||
public var animationNode: AnimatedStickerNode?
|
public var animationNode: AnimatedStickerNode?
|
||||||
public var additionalAnimationNode: AnimatedStickerNode?
|
public var additionalAnimationNode: AnimatedStickerNode?
|
||||||
|
|
||||||
|
private var placeholderNode: StickerShimmerEffectNode
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
private let effectDisposable = MetaDisposable()
|
private let effectDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var setupTimestamp: Double?
|
||||||
|
|
||||||
init(context: AccountContext, file: TelegramMediaFile) {
|
init(context: AccountContext, file: TelegramMediaFile) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.file = file
|
self.file = file
|
||||||
@ -123,6 +128,8 @@ private class StickerNode: ASDisplayNode {
|
|||||||
self.animationNode = nil
|
self.animationNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.placeholderNode = StickerShimmerEffectNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isUserInteractionEnabled = false
|
self.isUserInteractionEnabled = false
|
||||||
@ -136,6 +143,38 @@ private class StickerNode: ASDisplayNode {
|
|||||||
if let additionalAnimationNode = self.additionalAnimationNode {
|
if let additionalAnimationNode = self.additionalAnimationNode {
|
||||||
self.addSubnode(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 {
|
deinit {
|
||||||
@ -143,6 +182,18 @@ private class StickerNode: ASDisplayNode {
|
|||||||
self.effectDisposable.dispose()
|
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 visibility: Bool = false
|
||||||
private var centrality: Bool = false
|
private var centrality: Bool = false
|
||||||
|
|
||||||
@ -154,6 +205,12 @@ private class StickerNode: ASDisplayNode {
|
|||||||
public func setVisible(_ visible: Bool) {
|
public func setVisible(_ visible: Bool) {
|
||||||
self.visibility = visible
|
self.visibility = visible
|
||||||
self.updatePlayback()
|
self.updatePlayback()
|
||||||
|
|
||||||
|
self.setupTimestamp = CACurrentMediaTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
self.placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePlayback() {
|
private func updatePlayback() {
|
||||||
@ -196,6 +253,11 @@ private class StickerNode: ASDisplayNode {
|
|||||||
additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size)
|
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)
|
containerNode.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||||
transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.75)
|
transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.75)
|
||||||
transition.updateAlpha(node: containerNode, alpha: 1.0 - distance * 0.6)
|
transition.updateAlpha(node: containerNode, alpha: 1.0 - distance * 0.6)
|
||||||
|
itemNode.updateAbsoluteRect(itemFrame, within: size)
|
||||||
|
|
||||||
let isVisible = self.visibility && itemFrame.intersects(bounds)
|
let isVisible = self.visibility && itemFrame.intersects(bounds)
|
||||||
itemNode.setVisible(isVisible)
|
itemNode.setVisible(isVisible)
|
||||||
|
24
submodules/PromptUI/BUILD
Normal file
@ -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",
|
||||||
|
],
|
||||||
|
)
|
424
submodules/PromptUI/Sources/PromptController.swift
Normal file
@ -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<PresentationData, NoError>)? = 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
|
||||||
|
}
|
@ -42,7 +42,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
private let backgroundNode: NavigationBackgroundNode
|
private let backgroundNode: NavigationBackgroundNode
|
||||||
|
|
||||||
private let maskLayer: SimpleLayer
|
private let maskLayer: SimpleLayer
|
||||||
private let backgroundLayer: SimpleLayer
|
private let backgroundClippingLayer: SimpleLayer
|
||||||
|
private let backgroundMaskNode: ASDisplayNode
|
||||||
private let backgroundShadowLayer: SimpleLayer
|
private let backgroundShadowLayer: SimpleLayer
|
||||||
private let largeCircleLayer: SimpleLayer
|
private let largeCircleLayer: SimpleLayer
|
||||||
private let largeCircleShadowLayer: SimpleLayer
|
private let largeCircleShadowLayer: SimpleLayer
|
||||||
@ -51,23 +52,24 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
|
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
|
|
||||||
init(largeCircleSize: CGFloat, smallCircleSize: CGFloat) {
|
init(largeCircleSize: CGFloat, smallCircleSize: CGFloat, maskNode: ASDisplayNode) {
|
||||||
self.largeCircleSize = largeCircleSize
|
self.largeCircleSize = largeCircleSize
|
||||||
self.smallCircleSize = smallCircleSize
|
self.smallCircleSize = smallCircleSize
|
||||||
|
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true)
|
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true)
|
||||||
|
|
||||||
self.maskLayer = SimpleLayer()
|
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.backgroundShadowLayer = SimpleLayer()
|
||||||
self.largeCircleLayer = SimpleLayer()
|
self.largeCircleLayer = SimpleLayer()
|
||||||
self.largeCircleShadowLayer = SimpleLayer()
|
self.largeCircleShadowLayer = SimpleLayer()
|
||||||
self.smallCircleLayer = SimpleLayer()
|
self.smallCircleLayer = SimpleLayer()
|
||||||
self.smallCircleShadowLayer = SimpleLayer()
|
self.smallCircleShadowLayer = SimpleLayer()
|
||||||
|
|
||||||
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
|
|
||||||
self.backgroundLayer.masksToBounds = true
|
|
||||||
|
|
||||||
self.largeCircleLayer.backgroundColor = UIColor.black.cgColor
|
self.largeCircleLayer.backgroundColor = UIColor.black.cgColor
|
||||||
self.largeCircleLayer.masksToBounds = true
|
self.largeCircleLayer.masksToBounds = true
|
||||||
self.largeCircleLayer.cornerRadius = largeCircleSize / 2.0
|
self.largeCircleLayer.cornerRadius = largeCircleSize / 2.0
|
||||||
@ -77,7 +79,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
self.smallCircleLayer.cornerRadius = smallCircleSize / 2.0
|
self.smallCircleLayer.cornerRadius = smallCircleSize / 2.0
|
||||||
|
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
self.backgroundLayer.cornerCurve = .circular
|
// self.backgroundLayer.cornerCurve = .circular
|
||||||
self.largeCircleLayer.cornerCurve = .circular
|
self.largeCircleLayer.cornerCurve = .circular
|
||||||
self.smallCircleLayer.cornerCurve = .circular
|
self.smallCircleLayer.cornerCurve = .circular
|
||||||
}
|
}
|
||||||
@ -96,7 +98,9 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
|
|
||||||
self.maskLayer.addSublayer(self.smallCircleLayer)
|
self.maskLayer.addSublayer(self.smallCircleLayer)
|
||||||
self.maskLayer.addSublayer(self.largeCircleLayer)
|
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
|
self.backgroundNode.layer.mask = self.maskLayer
|
||||||
}
|
}
|
||||||
@ -137,12 +141,14 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var backgroundFrame = CGRect(origin: CGPoint(), size: size)
|
var backgroundFrame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
var backgroundMaskNodeFrame = backgroundFrame
|
||||||
if isMinimized {
|
if isMinimized {
|
||||||
let updatedHeight = floor(size.height * 0.9)
|
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))
|
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 largeCircleFrame: CGRect
|
||||||
let smallCircleFrame: 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)
|
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.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)
|
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.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.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.backgroundClippingLayer.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.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)
|
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 visualSourceBackgroundFrame = sourceBackgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY)
|
||||||
let sourceShadowFrame = visualSourceBackgroundFrame.insetBy(dx: -shadowInset, dy: -shadowInset)
|
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.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.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(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(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)
|
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() {
|
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.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.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)
|
self.largeCircleShadowLayer.animateAlpha(from: CGFloat(self.largeCircleShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||||
|
@ -73,9 +73,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private let contentContainer: ASDisplayNode
|
private let contentContainer: ASDisplayNode
|
||||||
private let contentContainerMask: UIImageView
|
private let contentContainerMask: UIImageView
|
||||||
|
private let leftBackgroundMaskNode: ASDisplayNode
|
||||||
|
private let rightBackgroundMaskNode: ASDisplayNode
|
||||||
|
private let backgroundMaskNode: ASDisplayNode
|
||||||
private let scrollNode: ASScrollNode
|
private let scrollNode: ASScrollNode
|
||||||
private let previewingItemContainer: ASDisplayNode
|
private let previewingItemContainer: ASDisplayNode
|
||||||
private var visibleItemNodes: [Int: ReactionItemNode] = [:]
|
private var visibleItemNodes: [Int: ReactionItemNode] = [:]
|
||||||
|
private var visibleItemMaskNodes: [Int: ASDisplayNode] = [:]
|
||||||
|
|
||||||
private var longPressRecognizer: UILongPressGestureRecognizer?
|
private var longPressRecognizer: UILongPressGestureRecognizer?
|
||||||
private var longPressTimer: SwiftSignalKit.Timer?
|
private var longPressTimer: SwiftSignalKit.Timer?
|
||||||
@ -101,7 +105,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.items = items
|
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 = ASScrollNode()
|
||||||
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
@ -275,6 +286,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
highlightedReactionIndex = nil
|
highlightedReactionIndex = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var currentMaskFrame: CGRect?
|
||||||
|
var maskTransition: ContainedViewLayoutTransition?
|
||||||
|
|
||||||
var validIndices = Set<Int>()
|
var validIndices = Set<Int>()
|
||||||
var nextX: CGFloat = sideInset
|
var nextX: CGFloat = sideInset
|
||||||
for i in 0 ..< self.items.count {
|
for i in 0 ..< self.items.count {
|
||||||
@ -324,21 +338,38 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
var animateIn = false
|
var animateIn = false
|
||||||
|
|
||||||
|
let maskNode: ASDisplayNode?
|
||||||
let itemNode: ReactionItemNode
|
let itemNode: ReactionItemNode
|
||||||
var itemTransition = transition
|
var itemTransition = transition
|
||||||
if let current = self.visibleItemNodes[i] {
|
if let current = self.visibleItemNodes[i] {
|
||||||
itemNode = current
|
itemNode = current
|
||||||
|
maskNode = self.visibleItemMaskNodes[i]
|
||||||
} else {
|
} else {
|
||||||
animateIn = self.didAnimateIn
|
animateIn = self.didAnimateIn
|
||||||
itemTransition = .immediate
|
itemTransition = .immediate
|
||||||
|
|
||||||
if case let .reaction(item) = self.items[i] {
|
if case let .reaction(item) = self.items[i] {
|
||||||
itemNode = ReactionNode(context: self.context, theme: self.theme, item: item)
|
itemNode = ReactionNode(context: self.context, theme: self.theme, item: item)
|
||||||
|
maskNode = nil
|
||||||
} else {
|
} else {
|
||||||
itemNode = PremiumReactionsNode(theme: self.theme)
|
itemNode = PremiumReactionsNode(theme: self.theme)
|
||||||
|
maskNode = itemNode.maskNode
|
||||||
}
|
}
|
||||||
self.visibleItemNodes[i] = itemNode
|
self.visibleItemNodes[i] = itemNode
|
||||||
|
|
||||||
self.scrollNode.addSubnode(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 {
|
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] = []
|
var removedIndices: [Int] = []
|
||||||
for (index, itemNode) in self.visibleItemNodes {
|
for (index, itemNode) in self.visibleItemNodes {
|
||||||
if !validIndices.contains(index) {
|
if !validIndices.contains(index) {
|
||||||
@ -377,8 +417,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
itemNode.removeFromSupernode()
|
itemNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (index, maskNode) in self.visibleItemMaskNodes {
|
||||||
|
if !validIndices.contains(index) {
|
||||||
|
maskNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
for index in removedIndices {
|
for index in removedIndices {
|
||||||
self.visibleItemNodes.removeValue(forKey: index)
|
self.visibleItemNodes.removeValue(forKey: index)
|
||||||
|
self.visibleItemMaskNodes.removeValue(forKey: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ private let font = Font.medium(13.0)
|
|||||||
protocol ReactionItemNode: ASDisplayNode {
|
protocol ReactionItemNode: ASDisplayNode {
|
||||||
var isExtracted: Bool { get }
|
var isExtracted: Bool { get }
|
||||||
|
|
||||||
|
var maskNode: ASDisplayNode? { get }
|
||||||
|
|
||||||
func appear(animated: Bool)
|
func appear(animated: Bool)
|
||||||
func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition)
|
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()
|
self.fetchFullAnimationDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var maskNode: ASDisplayNode? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func appear(animated: Bool) {
|
func appear(animated: Bool) {
|
||||||
if animated {
|
if animated {
|
||||||
self.animateInAnimationNode?.visibility = true
|
self.animateInAnimationNode?.visibility = true
|
||||||
@ -351,13 +357,29 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
|
|||||||
var isExtracted: Bool = false
|
var isExtracted: Bool = false
|
||||||
|
|
||||||
let imageNode: ASImageNode
|
let imageNode: ASImageNode
|
||||||
|
let maskImageNode: ASImageNode
|
||||||
|
|
||||||
init(theme: PresentationTheme) {
|
init(theme: PresentationTheme) {
|
||||||
self.imageNode = ASImageNode()
|
self.imageNode = ASImageNode()
|
||||||
self.imageNode.contentMode = .center
|
self.imageNode.contentMode = .center
|
||||||
self.imageNode.displaysAsynchronously = false
|
self.imageNode.displaysAsynchronously = false
|
||||||
self.imageNode.isUserInteractionEnabled = 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()
|
super.init()
|
||||||
|
|
||||||
@ -371,4 +393,9 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
|
|||||||
func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var maskNode: ASDisplayNode? {
|
||||||
|
return self.maskImageNode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,9 +99,11 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let overlayNode: ASImageNode
|
private let overlayNode: ASImageNode
|
||||||
private let lockNode: ASImageNode
|
private let lockNode: ASImageNode
|
||||||
private let textNode: ASTextNode
|
private let textNode: ImmediateTextNode
|
||||||
private var action: (() -> Void)?
|
private var action: (() -> Void)?
|
||||||
|
|
||||||
|
private var locked = false
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.iconNode = ASImageNode()
|
self.iconNode = ASImageNode()
|
||||||
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
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.overlayNode.isLayerBacked = true
|
||||||
|
|
||||||
self.lockNode = ASImageNode()
|
self.lockNode = ASImageNode()
|
||||||
|
self.lockNode.contentMode = .scaleAspectFit
|
||||||
self.lockNode.displaysAsynchronously = false
|
self.lockNode.displaysAsynchronously = false
|
||||||
self.lockNode.isUserInteractionEnabled = false
|
self.lockNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.textNode = ASTextNode()
|
self.textNode = ImmediateTextNode()
|
||||||
self.textNode.isUserInteractionEnabled = false
|
self.textNode.isUserInteractionEnabled = false
|
||||||
self.textNode.displaysAsynchronously = 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) {
|
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.iconNode.image = icon
|
||||||
self.textNode.attributedText = title
|
self.textNode.attributedText = title
|
||||||
self.overlayNode.image = generateBorderImage(theme: theme, bordered: bordered, selected: selected)
|
self.overlayNode.image = generateBorderImage(theme: theme, bordered: bordered, selected: selected)
|
||||||
@ -135,6 +139,8 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||||||
self.action = {
|
self.action = {
|
||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -154,10 +160,17 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||||||
|
|
||||||
let bounds = self.bounds
|
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.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: 10.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))
|
||||||
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)
|
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
|
name = item.strings.Appearance_AppIconNew1
|
||||||
case "New2":
|
case "New2":
|
||||||
name = item.strings.Appearance_AppIconNew2
|
name = item.strings.Appearance_AppIconNew2
|
||||||
case "PremiumCosmic":
|
case "Premium":
|
||||||
name = "Cosmic"
|
name = "Premium"
|
||||||
case "PremiumCherry":
|
case "PremiumBlack":
|
||||||
name = "Cherry"
|
name = "Black"
|
||||||
case "PremiumDuck":
|
case "PremiumTurbo":
|
||||||
name = "Duck"
|
name = "Turbo"
|
||||||
default:
|
default:
|
||||||
name = icon.name
|
name = icon.name
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,10 @@ public enum ShareControllerExternalStatus {
|
|||||||
case done
|
case done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ShareControllerError {
|
||||||
|
case fileTooBig(Int64)
|
||||||
|
}
|
||||||
|
|
||||||
public struct ShareControllerSegmentedValue {
|
public struct ShareControllerSegmentedValue {
|
||||||
let title: String
|
let title: String
|
||||||
let subject: ShareControllerSubject
|
let subject: ShareControllerSubject
|
||||||
@ -61,7 +65,7 @@ public enum ShareControllerSubject {
|
|||||||
case image([ImageRepresentationWithReference])
|
case image([ImageRepresentationWithReference])
|
||||||
case media(AnyMediaReference)
|
case media(AnyMediaReference)
|
||||||
case mapMedia(TelegramMediaMap)
|
case mapMedia(TelegramMediaMap)
|
||||||
case fromExternal(([PeerId], String, Account, Bool) -> Signal<ShareControllerExternalStatus, NoError>)
|
case fromExternal(([PeerId], String, Account, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ExternalShareItem {
|
private enum ExternalShareItem {
|
||||||
@ -657,18 +661,20 @@ public final class ShareController: ViewController {
|
|||||||
let queue = Queue.mainQueue()
|
let queue = Queue.mainQueue()
|
||||||
var displayedError = false
|
var displayedError = false
|
||||||
return combineLatest(queue: queue, shareSignals)
|
return combineLatest(queue: queue, shareSignals)
|
||||||
|> mapToSignal { messageIdSets -> Signal<ShareState, NoError> in
|
|> castError(ShareControllerError.self)
|
||||||
var statuses: [Signal<(MessageId, PendingMessageStatus?, PendingMessageFailureReason?), NoError>] = []
|
|> mapToSignal { messageIdSets -> Signal<ShareState, ShareControllerError> in
|
||||||
|
var statuses: [Signal<(MessageId, PendingMessageStatus?, PendingMessageFailureReason?), ShareControllerError>] = []
|
||||||
for messageIds in messageIdSets {
|
for messageIds in messageIdSets {
|
||||||
for case let id? in messageIds {
|
for case let id? in messageIds {
|
||||||
statuses.append(account.pendingMessageManager.pendingMessageStatus(id)
|
statuses.append(account.pendingMessageManager.pendingMessageStatus(id)
|
||||||
|
|> castError(ShareControllerError.self)
|
||||||
|> map { status, error -> (MessageId, PendingMessageStatus?, PendingMessageFailureReason?) in
|
|> map { status, error -> (MessageId, PendingMessageStatus?, PendingMessageFailureReason?) in
|
||||||
return (id, status, error)
|
return (id, status, error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return combineLatest(queue: queue, statuses)
|
return combineLatest(queue: queue, statuses)
|
||||||
|> mapToSignal { statuses -> Signal<ShareState, NoError> in
|
|> mapToSignal { statuses -> Signal<ShareState, ShareControllerError> in
|
||||||
var hasStatuses = false
|
var hasStatuses = false
|
||||||
for (id, status, error) in statuses {
|
for (id, status, error) in statuses {
|
||||||
if let error = error {
|
if let error = error {
|
||||||
|
@ -59,7 +59,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
|
|
||||||
var dismiss: ((Bool) -> Void)?
|
var dismiss: ((Bool) -> Void)?
|
||||||
var cancel: (() -> Void)?
|
var cancel: (() -> Void)?
|
||||||
var share: ((String, [PeerId], Bool, Bool) -> Signal<ShareState, NoError>)?
|
var share: ((String, [PeerId], Bool, Bool) -> Signal<ShareState, ShareControllerError>)?
|
||||||
var shareExternal: ((Bool) -> Signal<ShareExternalState, NoError>)?
|
var shareExternal: ((Bool) -> Signal<ShareExternalState, NoError>)?
|
||||||
var switchToAnotherAccount: (() -> Void)?
|
var switchToAnotherAccount: (() -> Void)?
|
||||||
var debugAction: (() -> Void)?
|
var debugAction: (() -> Void)?
|
||||||
@ -673,7 +673,6 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
if !self.fromForeignApp {
|
if !self.fromForeignApp {
|
||||||
self.animateOut(shared: true, completion: {
|
self.animateOut(shared: true, completion: {
|
||||||
})
|
})
|
||||||
|
@ -47,16 +47,21 @@ private func scalePhotoImage(_ image: UIImage, dimensions: CGSize) -> UIImage? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func preparedShareItem(account: Account, to peerId: PeerId, value: [String: Any]) -> Signal<PreparedShareItem, Void> {
|
public enum PreparedShareItemError {
|
||||||
|
case generic
|
||||||
|
case fileTooBig(Int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func preparedShareItem(account: Account, to peerId: PeerId, value: [String: Any]) -> Signal<PreparedShareItem, PreparedShareItemError> {
|
||||||
if let imageData = value["scaledImageData"] as? Data, let dimensions = value["scaledImageDimensions"] as? NSValue {
|
if let imageData = value["scaledImageData"] as? Data, let dimensions = value["scaledImageDimensions"] as? NSValue {
|
||||||
let diminsionsSize = dimensions.cgSizeValue
|
let diminsionsSize = dimensions.cgSizeValue
|
||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|> then(
|
|> then(
|
||||||
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(diminsionsSize.width), height: Int32(diminsionsSize.height)))
|
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(diminsionsSize.width), height: Int32(diminsionsSize.height)))
|
||||||
|> mapError { _ -> Void in
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
return Void()
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
switch event {
|
switch event {
|
||||||
case let .progress(value):
|
case let .progress(value):
|
||||||
return .single(.progress(value))
|
return .single(.progress(value))
|
||||||
@ -72,10 +77,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
|||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|> then(
|
|> then(
|
||||||
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)))
|
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)))
|
||||||
|> mapError { _ -> Void in
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
return Void()
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
switch event {
|
switch event {
|
||||||
case let .progress(value):
|
case let .progress(value):
|
||||||
return .single(.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)
|
var finalDuration: Double = CMTimeGetSeconds(asset.duration)
|
||||||
|
|
||||||
func loadValues(_ avAsset: AVURLAsset) -> Signal<AVURLAsset, Void> {
|
func loadValues(_ avAsset: AVURLAsset) -> Signal<AVURLAsset, PreparedShareItemError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
avAsset.loadValuesAsynchronously(forKeys: ["tracks", "duration", "playable"]) {
|
avAsset.loadValuesAsynchronously(forKeys: ["tracks", "duration", "playable"]) {
|
||||||
subscriber.putNext(avAsset)
|
subscriber.putNext(avAsset)
|
||||||
@ -123,7 +128,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
|||||||
return .single(.preparing(true))
|
return .single(.preparing(true))
|
||||||
|> then(
|
|> then(
|
||||||
loadValues(asset)
|
loadValues(asset)
|
||||||
|> mapToSignal { asset -> Signal<PreparedShareItem, Void> in
|
|> mapToSignal { asset -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
let preset = adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium
|
let preset = adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium
|
||||||
let finalDimensions = TGMediaVideoConverter.dimensions(for: asset.originalSize, adjustments: adjustments, preset: preset)
|
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)
|
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)
|
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
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
return Void()
|
return .generic
|
||||||
}
|
}
|
||||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
switch event {
|
switch event {
|
||||||
case let .progress(value):
|
case let .progress(value):
|
||||||
return .single(.progress(value))
|
return .single(.progress(value))
|
||||||
@ -201,7 +206,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
|||||||
return .single(.preparing(true))
|
return .single(.preparing(true))
|
||||||
|> then(
|
|> then(
|
||||||
convertedData
|
convertedData
|
||||||
|> castError(Void.self)
|
|> castError(PreparedShareItemError.self)
|
||||||
|> mapToSignal { data, dimensions, duration, converted in
|
|> mapToSignal { data, dimensions, duration, converted in
|
||||||
var attributes: [TelegramMediaFileAttribute] = []
|
var attributes: [TelegramMediaFileAttribute] = []
|
||||||
let mimeType: String
|
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")]
|
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)
|
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() }
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
switch event {
|
switch event {
|
||||||
case let .progress(value):
|
case let .progress(value):
|
||||||
return .single(.progress(value))
|
return .single(.progress(value))
|
||||||
@ -230,8 +237,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
|||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|> then(
|
|> then(
|
||||||
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(scaledImage.size.width), height: Int32(scaledImage.size.height)))
|
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() }
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
switch event {
|
switch event {
|
||||||
case let .progress(value):
|
case let .progress(value):
|
||||||
return .single(.progress(value))
|
return .single(.progress(value))
|
||||||
@ -251,8 +260,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
|||||||
return .single(.preparing(long))
|
return .single(.preparing(long))
|
||||||
|> then(
|
|> 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)
|
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() }
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
switch event {
|
switch event {
|
||||||
case let .progress(value):
|
case let .progress(value):
|
||||||
return .single(.progress(value))
|
return .single(.progress(value))
|
||||||
@ -280,8 +291,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
|||||||
return .single(.preparing(long))
|
return .single(.preparing(long))
|
||||||
|> then(
|
|> 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)
|
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() }
|
|> mapError { _ -> PreparedShareItemError in
|
||||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
return .generic
|
||||||
|
}
|
||||||
|
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||||
switch event {
|
switch event {
|
||||||
case let .progress(value):
|
case let .progress(value):
|
||||||
return .single(.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 {
|
} else if let url = value["url"] as? URL {
|
||||||
if TGShareLocationSignals.isLocationURL(url) {
|
if TGShareLocationSignals.isLocationURL(url) {
|
||||||
return Signal<PreparedShareItem, Void> { subscriber in
|
return Signal<PreparedShareItem, PreparedShareItemError> { subscriber in
|
||||||
subscriber.putNext(.preparing(false))
|
subscriber.putNext(.preparing(false))
|
||||||
let disposable = TGShareLocationSignals.locationMessageContent(for: url).start(next: { value in
|
let disposable = TGShareLocationSignals.locationMessageContent(for: url).start(next: { value in
|
||||||
if let value = value as? TGShareLocationResult {
|
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<PreparedShareItems, Void> {
|
public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [MTSignal], additionalText: String) -> Signal<PreparedShareItems, PreparedShareItemError> {
|
||||||
var dataSignals: Signal<[String: Any], Void> = .complete()
|
var dataSignals: Signal<[String: Any], PreparedShareItemError> = .complete()
|
||||||
for dataItem in dataItems {
|
for dataItem in dataItems {
|
||||||
let wrappedSignal: Signal<[String: Any], NoError> = Signal { subscriber in
|
let wrappedSignal: Signal<[String: Any], NoError> = Signal { subscriber in
|
||||||
let disposable = dataItem.start(next: { value in
|
let disposable = dataItem.start(next: { value in
|
||||||
@ -349,7 +362,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [
|
|||||||
dataSignals = dataSignals
|
dataSignals = dataSignals
|
||||||
|> then(
|
|> then(
|
||||||
wrappedSignal
|
wrappedSignal
|
||||||
|> castError(Void.self)
|
|> castError(PreparedShareItemError.self)
|
||||||
|> take(1)
|
|> take(1)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -359,7 +372,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [
|
|||||||
|> reduceLeft(value: [[String: Any]](), f: { list, rest in
|
|> reduceLeft(value: [[String: Any]](), f: { list, rest in
|
||||||
return list + rest
|
return list + rest
|
||||||
})
|
})
|
||||||
|> mapToSignal { items -> Signal<[PreparedShareItem], Void> in
|
|> mapToSignal { items -> Signal<[PreparedShareItem], PreparedShareItemError> in
|
||||||
return combineLatest(items.map {
|
return combineLatest(items.map {
|
||||||
preparedShareItem(account: account, to: peerId, value: $0)
|
preparedShareItem(account: account, to: peerId, value: $0)
|
||||||
})
|
})
|
||||||
|
@ -34,6 +34,7 @@ swift_library(
|
|||||||
"//submodules/MoreButtonNode:MoreButtonNode",
|
"//submodules/MoreButtonNode:MoreButtonNode",
|
||||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||||
"//submodules/PremiumUI:PremiumUI",
|
"//submodules/PremiumUI:PremiumUI",
|
||||||
|
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -16,6 +16,8 @@ import UndoUI
|
|||||||
import ShareController
|
import ShareController
|
||||||
import TextFormat
|
import TextFormat
|
||||||
import PremiumUI
|
import PremiumUI
|
||||||
|
import OverlayStatusController
|
||||||
|
import PresentationDataUtils
|
||||||
|
|
||||||
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||||
case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool)
|
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
|
self.actionAreaSeparatorNode.alpha = backgroundAlpha
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onLoading: () -> Void = {}
|
||||||
|
var onReady: () -> Void = {}
|
||||||
|
var onError: () -> Void = {}
|
||||||
|
|
||||||
private var currentContents: LoadedStickerPack?
|
private var currentContents: LoadedStickerPack?
|
||||||
private func updateStickerPackContents(_ contents: LoadedStickerPack, hasPremium: Bool) {
|
private func updateStickerPackContents(_ contents: LoadedStickerPack, hasPremium: Bool) {
|
||||||
self.currentContents = contents
|
self.currentContents = contents
|
||||||
@ -590,6 +596,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
|
|
||||||
switch contents {
|
switch contents {
|
||||||
case .fetching:
|
case .fetching:
|
||||||
|
self.onLoading()
|
||||||
entries = []
|
entries = []
|
||||||
self.buttonNode.setTitle(self.presentationData.strings.Channel_NotificationLoading, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemDisabledTextColor, for: .normal)
|
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: [])
|
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||||
@ -620,20 +627,11 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
self.titleContainer.addSubnode(titlePlaceholderNode)
|
self.titleContainer.addSubnode(titlePlaceholderNode)
|
||||||
}
|
}
|
||||||
case .none:
|
case .none:
|
||||||
entries = []
|
self.onError()
|
||||||
self.buttonNode.setTitle(self.presentationData.strings.Common_Close, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal)
|
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.buttonNode.setBackgroundImage(nil, for: [])
|
self.controller?.dismiss(animated: true, completion: nil)
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
case let .result(info, items, installed):
|
case let .result(info, items, installed):
|
||||||
|
self.onReady()
|
||||||
if !items.isEmpty && self.currentStickerPack == nil {
|
if !items.isEmpty && self.currentStickerPack == nil {
|
||||||
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
||||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false)
|
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
|
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) {
|
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.context = context
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
@ -1104,6 +1106,15 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, presentInGlobalOverlay: presentInGlobalOverlay, sendSticker: sendSticker, openMention: openMention, controller: self.controller)
|
}, 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.containerContainingNode.addSubnode(container)
|
||||||
self.containers[i] = container
|
self.containers[i] = container
|
||||||
}
|
}
|
||||||
@ -1380,19 +1391,78 @@ 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())
|
self._ready.set(self.controllerNode.ready.get())
|
||||||
|
|
||||||
super.displayNodeDidLoad()
|
super.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isReady = false
|
||||||
|
|
||||||
override public func viewDidAppear(_ animated: Bool) {
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
if !self.alreadyDidAppear {
|
if !self.alreadyDidAppear {
|
||||||
self.alreadyDidAppear = true
|
self.alreadyDidAppear = true
|
||||||
|
|
||||||
|
if self.isReady {
|
||||||
self.controllerNode.animateIn()
|
self.controllerNode.animateIn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
@ -147,9 +147,6 @@ public struct ChatListFilterIncludePeers: Equatable, Hashable {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.peers.count + self.pinnedPeers.count >= 100 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
self.peers.insert(peerId, at: 0)
|
self.peers.insert(peerId, at: 0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
if query == "\u{2764}" {
|
if query == "\u{2764}" {
|
||||||
query = "\u{2764}\u{FE0F}"
|
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] = []
|
var result: [FoundStickerItem] = []
|
||||||
if scope.contains(.installed) {
|
if scope.contains(.installed) {
|
||||||
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
|
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) {
|
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) {
|
||||||
if let item = entry.contents.get(RecentMediaItem.self) {
|
if let item = entry.contents.get(RecentMediaItem.self) {
|
||||||
let file = item.media
|
let file = item.media
|
||||||
|
if file.isPremiumSticker && !isPremium {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if !currentItems.contains(file.fileId) {
|
if !currentItems.contains(file.fileId) {
|
||||||
for case let .Sticker(displayText, _, _) in file.attributes {
|
for case let .Sticker(displayText, _, _) in file.attributes {
|
||||||
@ -135,6 +140,10 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
var installedAnimatedItems: [FoundStickerItem] = []
|
var installedAnimatedItems: [FoundStickerItem] = []
|
||||||
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) {
|
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) {
|
||||||
if let item = item as? StickerPackItem {
|
if let item = item as? StickerPackItem {
|
||||||
|
if item.file.isPremiumSticker && !isPremium {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if !currentItems.contains(item.file.fileId) {
|
if !currentItems.contains(item.file.fileId) {
|
||||||
var stringRepresentations: [String] = []
|
var stringRepresentations: [String] = []
|
||||||
for key in item.indexKeys {
|
for key in item.indexKeys {
|
||||||
@ -158,12 +167,18 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
}
|
}
|
||||||
|
|
||||||
for file in recentAnimatedItems {
|
for file in recentAnimatedItems {
|
||||||
|
if file.isPremiumSticker && !isPremium {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if matchingRecentItemsIds.contains(file.fileId) {
|
if matchingRecentItemsIds.contains(file.fileId) {
|
||||||
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for file in recentItems {
|
for file in recentItems {
|
||||||
|
if file.isPremiumSticker && !isPremium {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if matchingRecentItemsIds.contains(file.fileId) {
|
if matchingRecentItemsIds.contains(file.fileId) {
|
||||||
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
||||||
}
|
}
|
||||||
@ -183,8 +198,8 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
cached = nil
|
cached = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result, cached)
|
return (result, cached, isPremium)
|
||||||
} |> mapToSignal { localItems, cached -> Signal<[FoundStickerItem], NoError> in
|
} |> mapToSignal { localItems, cached, isPremium -> Signal<[FoundStickerItem], NoError> in
|
||||||
var tempResult: [FoundStickerItem] = localItems
|
var tempResult: [FoundStickerItem] = localItems
|
||||||
if !scope.contains(.remote) {
|
if !scope.contains(.remote) {
|
||||||
return .single(tempResult)
|
return .single(tempResult)
|
||||||
@ -196,6 +211,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
var cachedAnimatedItems: [FoundStickerItem] = []
|
var cachedAnimatedItems: [FoundStickerItem] = []
|
||||||
|
|
||||||
for file in cached.items {
|
for file in cached.items {
|
||||||
|
if file.isPremiumSticker && !isPremium {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if !currentItemIds.contains(file.fileId) {
|
if !currentItemIds.contains(file.fileId) {
|
||||||
if file.isAnimatedSticker || file.isVideoSticker {
|
if file.isAnimatedSticker || file.isVideoSticker {
|
||||||
cachedAnimatedItems.append(FoundStickerItem(file: file, stringRepresentations: []))
|
cachedAnimatedItems.append(FoundStickerItem(file: file, stringRepresentations: []))
|
||||||
@ -226,6 +244,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
|||||||
var files: [TelegramMediaFile] = []
|
var files: [TelegramMediaFile] = []
|
||||||
for sticker in stickers {
|
for sticker in stickers {
|
||||||
if let file = telegramMediaFileFromApiDocument(sticker), let id = file.id {
|
if let file = telegramMediaFileFromApiDocument(sticker), let id = file.id {
|
||||||
|
if file.isPremiumSticker && !isPremium {
|
||||||
|
continue
|
||||||
|
}
|
||||||
files.append(file)
|
files.append(file)
|
||||||
if !currentItemIds.contains(id) {
|
if !currentItemIds.contains(id) {
|
||||||
if file.isAnimatedSticker || file.isVideoSticker {
|
if file.isAnimatedSticker || file.isVideoSticker {
|
||||||
|
@ -167,7 +167,6 @@ public enum PresentationResourceKey: Int32 {
|
|||||||
case chatInputMediaPanelTrendingGifsIcon
|
case chatInputMediaPanelTrendingGifsIcon
|
||||||
case chatInputMediaPanelStickersModeIcon
|
case chatInputMediaPanelStickersModeIcon
|
||||||
case chatInputMediaPanelPremiumIcon
|
case chatInputMediaPanelPremiumIcon
|
||||||
case chatInputMediaStickerGridPremiumIcon
|
|
||||||
|
|
||||||
case chatInputButtonPanelButtonImage
|
case chatInputButtonPanelButtonImage
|
||||||
case chatInputButtonPanelButtonHighlightedImage
|
case chatInputButtonPanelButtonHighlightedImage
|
||||||
|
@ -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? {
|
public static func chatInputMediaPanelRecentStickersIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatInputMediaPanelRecentStickersIconImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatInputMediaPanelRecentStickersIconImage.rawValue, { theme in
|
||||||
return generateImage(CGSize(width: 42.0, height: 42.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 42.0, height: 42.0), contextGenerator: { size, context in
|
||||||
|
12
submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ReactionsBg.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
344
submodules/TelegramUI/Images.xcassets/Premium/ReactionsBackground.imageset/ReactionsBg.pdf
vendored
Normal file
@ -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
|
12
submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "ReactionsFg.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
127
submodules/TelegramUI/Images.xcassets/Premium/ReactionsForeground.imageset/ReactionsFg.pdf
vendored
Normal file
@ -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
|
@ -680,9 +680,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
icons.append(PresentationAppIcon(name: "WhiteFilledIcon", imageName: "WhiteFilledIcon"))
|
icons.append(PresentationAppIcon(name: "WhiteFilledIcon", imageName: "WhiteFilledIcon"))
|
||||||
}
|
}
|
||||||
|
|
||||||
icons.append(PresentationAppIcon(name: "PremiumCosmic", imageName: "PremiumCosmic", isPremium: true))
|
icons.append(PresentationAppIcon(name: "Premium", imageName: "Premium", isPremium: true))
|
||||||
icons.append(PresentationAppIcon(name: "PremiumCherry", imageName: "PremiumCherry", isPremium: true))
|
icons.append(PresentationAppIcon(name: "PremiumBlack", imageName: "PremiumBlack", isPremium: true))
|
||||||
icons.append(PresentationAppIcon(name: "PremiumDuck", imageName: "PremiumDuck", isPremium: true))
|
icons.append(PresentationAppIcon(name: "PremiumTurbo", imageName: "PremiumTurbo", isPremium: true))
|
||||||
|
|
||||||
return icons
|
return icons
|
||||||
} else {
|
} else {
|
||||||
|
@ -243,18 +243,19 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
|
|||||||
if let parentController = parentController {
|
if let parentController = parentController {
|
||||||
parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, text, account, silently in
|
parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, text, account, silently in
|
||||||
return legacyAssetPickerEnqueueMessages(account: account, signals: signals!)
|
return legacyAssetPickerEnqueueMessages(account: account, signals: signals!)
|
||||||
|> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], NoError> in
|
|> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], ShareControllerError> in
|
||||||
return .single([])
|
return .single([])
|
||||||
}
|
}
|
||||||
|> mapToSignal { messages -> Signal<ShareControllerExternalStatus, NoError> in
|
|> mapToSignal { messages -> Signal<ShareControllerExternalStatus, ShareControllerError> in
|
||||||
let resultSignals = peerIds.map({ peerId in
|
let resultSignals = peerIds.map({ peerId in
|
||||||
return enqueueMessages(account: account, peerId: peerId, messages: messages.map { $0.message })
|
return enqueueMessages(account: account, peerId: peerId, messages: messages.map { $0.message })
|
||||||
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, NoError> in
|
|> castError(ShareControllerError.self)
|
||||||
|
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, ShareControllerError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return combineLatest(resultSignals)
|
return combineLatest(resultSignals)
|
||||||
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, NoError> in
|
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, ShareControllerError> in
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|> then(.single(ShareControllerExternalStatus.done))
|
|> then(.single(ShareControllerExternalStatus.done))
|
||||||
|
@ -368,10 +368,15 @@ public class ShareRootControllerImpl {
|
|||||||
let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)!
|
let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)!
|
||||||
return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText)
|
return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText)
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<PreparedShareItems?, NoError> in
|
|> `catch` { error -> Signal<PreparedShareItems?, ShareControllerError> in
|
||||||
|
switch error {
|
||||||
|
case .generic:
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
|
case let .fileTooBig(size):
|
||||||
|
return .fail(.fileTooBig(size))
|
||||||
}
|
}
|
||||||
|> mapToSignal { state -> Signal<ShareControllerExternalStatus, NoError> in
|
}
|
||||||
|
|> mapToSignal { state -> Signal<ShareControllerExternalStatus, ShareControllerError> in
|
||||||
guard let state = state else {
|
guard let state = state else {
|
||||||
return .single(.done)
|
return .single(.done)
|
||||||
}
|
}
|
||||||
@ -382,11 +387,14 @@ public class ShareRootControllerImpl {
|
|||||||
return .single(.progress(value))
|
return .single(.progress(value))
|
||||||
case let .userInteractionRequired(value):
|
case let .userInteractionRequired(value):
|
||||||
return requestUserInteraction(value)
|
return requestUserInteraction(value)
|
||||||
|> mapToSignal { contents -> Signal<ShareControllerExternalStatus, NoError> in
|
|> castError(ShareControllerError.self)
|
||||||
|
|> mapToSignal { contents -> Signal<ShareControllerExternalStatus, ShareControllerError> in
|
||||||
return sentItems(peerIds, contents, account, silently)
|
return sentItems(peerIds, contents, account, silently)
|
||||||
|
|> castError(ShareControllerError.self)
|
||||||
}
|
}
|
||||||
case let .done(contents):
|
case let .done(contents):
|
||||||
return sentItems(peerIds, contents, account, silently)
|
return sentItems(peerIds, contents, account, silently)
|
||||||
|
|> castError(ShareControllerError.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -26,6 +26,7 @@ swift_library(
|
|||||||
"//submodules/UrlHandling:UrlHandling",
|
"//submodules/UrlHandling:UrlHandling",
|
||||||
"//submodules/MoreButtonNode:MoreButtonNode",
|
"//submodules/MoreButtonNode:MoreButtonNode",
|
||||||
"//submodules/BotPaymentsUI:BotPaymentsUI",
|
"//submodules/BotPaymentsUI:BotPaymentsUI",
|
||||||
|
"//submodules/PromptUI:PromptUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -19,6 +19,7 @@ import LegacyComponents
|
|||||||
import UrlHandling
|
import UrlHandling
|
||||||
import MoreButtonNode
|
import MoreButtonNode
|
||||||
import BotPaymentsUI
|
import BotPaymentsUI
|
||||||
|
import PromptUI
|
||||||
|
|
||||||
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
||||||
|
|
||||||
@ -463,6 +464,33 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||||||
decisionHandler(.prompt)
|
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?
|
private var targetContentOffset: CGPoint?
|
||||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
let contentOffset = scrollView.contentOffset.y
|
let contentOffset = scrollView.contentOffset.y
|
||||||
|