Merge commit 'ab8f433658f5aaa91b381b19b5283411fcc2104c'
@ -298,9 +298,9 @@ alternate_icon_folders = [
|
||||
"WhiteFilledIcon",
|
||||
"New1",
|
||||
"New2",
|
||||
"PremiumCosmic",
|
||||
"PremiumCherry",
|
||||
"PremiumDuck",
|
||||
"Premium",
|
||||
"PremiumBlack",
|
||||
"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 |
@ -7568,13 +7568,13 @@ Sorry for the inconvenience.";
|
||||
"Premium.IncreaseLimit" = "Increase Limit";
|
||||
|
||||
"Premium.MaxFoldersCountText" = "You have reached the limit of **%1$@** folders. You can double the limit to **%2$@** folders by subscribing to **Telegram Premium**.";
|
||||
"Premium.MaxFoldersCountFinalText" = "Sorry, you can't create more than **%1$@** folders.";
|
||||
"Premium.MaxChatsInFolderText" = "Sorry, you can't add more than **%1$@** chats to a folder. You can increase this limit to **%2$@** by upgrading to **Telegram Premium**.";
|
||||
"Premium.MaxChatsInFolderFinalText" = "Sorry, you can't add more than **%@** chats to a folder.";
|
||||
"Premium.MaxFileSizeText" = "Double this limit to %@ per file by subscribing to **Telegram Premium**.";
|
||||
"Premium.MaxFileSizeFinalText" = "The document can't be sent, because it is larger than **%@**.";
|
||||
"Premium.MaxPinsText" = "Sorry, you can't pin more than **%1$@** chats to the top. Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **%2$@** chats.";
|
||||
"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **@** chats to the top. Unpin some of the currently pinned ones.";
|
||||
"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **@** chats to the top.";
|
||||
"Premium.MaxPinsFinalText" = "Sorry, you can't pin more than **%@** chats to the top. Unpin some of the currently pinned ones.";
|
||||
"Premium.MaxFavedStickersTitle" = "The Limit of %@ Stickers Reached";
|
||||
"Premium.MaxFavedStickersText" = "An older sticker was replaced with this one. You can [increase the limit]() to %@ stickers.";
|
||||
"Premium.MaxFavedStickersFinalText" = "An older sticker was replaced with this one.";
|
||||
@ -7582,6 +7582,7 @@ Sorry for the inconvenience.";
|
||||
"Premium.MaxSavedGifsText" = "An older GIF was replaced with this one. You can [increase the limit]() to %@ GIFS.";
|
||||
"Premium.MaxSavedGifsFinalText" = "An older GIF was replaced with this one.";
|
||||
"Premium.MaxAccountsText" = "You have reached the limit of **%@** connected accounts. You can free one place by subscribing to **Telegram Premium**.";
|
||||
"Premium.MaxAccountsFinalText" = "You have reached the limit of **%@** connected accounts.";
|
||||
|
||||
"Premium.Free" = "Free";
|
||||
"Premium.Premium" = "Premium";
|
||||
|
@ -22,7 +22,6 @@ swift_library(
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/AnimationUI:AnimationUI",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
|
@ -5,7 +5,6 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
import SolidRoundedButtonNode
|
||||
import SwiftSignalKit
|
||||
import OverlayStatusController
|
||||
import AnimationUI
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
|
@ -56,9 +56,14 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
|
||||
return combineLatest(
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Messages.ChatListGroup(id: peerId)),
|
||||
context.engine.peers.recentlySearchedPeers() |> take(1)
|
||||
context.engine.peers.recentlySearchedPeers() |> take(1),
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
|
||||
)
|
||||
)
|
||||
|> mapToSignal { peerGroup, recentlySearchedPeers -> Signal<[ContextMenuItem], NoError> in
|
||||
|> mapToSignal { peerGroup, recentlySearchedPeers, limitsData -> Signal<[ContextMenuItem], NoError> in
|
||||
let location: TogglePeerChatPinnedLocation
|
||||
var chatListFilter: ChatListFilter?
|
||||
if case let .chatList(filter) = source, let chatFilter = filter {
|
||||
@ -243,6 +248,30 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
|
||||
return generateTintedImage(image: UIImage(bundleImageName: imageName), color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, f in
|
||||
c.dismiss(completion: {
|
||||
let isPremium = limitsData.0?.isPremium ?? false
|
||||
let (_, limits, premiumLimits) = limitsData
|
||||
|
||||
let limit = limits.maxFolderChatsCount
|
||||
let premiumLimit = premiumLimits.maxFolderChatsCount
|
||||
|
||||
let count = data.includePeers.peers.count - 1
|
||||
if count >= premiumLimit {
|
||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {})
|
||||
chatListController?.push(controller)
|
||||
return
|
||||
} else if count >= limit && !isPremium {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
||||
let controller = PremiumIntroScreen(context: context, source: .chatsPerFolder)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
chatListController?.push(controller)
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
||||
var filters = filters
|
||||
for i in 0 ..< filters.count {
|
||||
|
@ -265,7 +265,22 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
strongSelf.chatListDisplayNode.containerNode.currentItemNode.scrollToPosition(.top)
|
||||
case let .known(offset):
|
||||
if offset <= navigationBarSearchContentHeight + 1.0 && strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter != nil {
|
||||
strongSelf.selectTab(id: .all)
|
||||
let _ = (strongSelf.context.engine.peers.currentChatListFilters()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let targetTab: ChatListFilterTabEntryId
|
||||
let firstFilter = filters.first ?? .allChats
|
||||
switch firstFilter {
|
||||
case .allChats:
|
||||
targetTab = .all
|
||||
case let .filter(id, _, _, _):
|
||||
targetTab = .filter(id)
|
||||
}
|
||||
|
||||
strongSelf.selectTab(id: targetTab)
|
||||
})
|
||||
} else {
|
||||
if let searchContentNode = strongSelf.searchContentNode {
|
||||
searchContentNode.updateExpansionProgress(1.0, animated: true)
|
||||
|
@ -2169,7 +2169,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
videoNode.canAttachContent = true
|
||||
videoNode.play()
|
||||
|
||||
self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
||||
// self.contextContainer.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
|
||||
self.avatarNode.addSubnode(videoNode)
|
||||
self.videoNode = videoNode
|
||||
}
|
||||
} else if let videoNode = self.videoNode {
|
||||
@ -2179,7 +2180,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
|
||||
videoNode.frame = self.avatarNode.frame
|
||||
videoNode.frame = self.avatarNode.bounds
|
||||
}
|
||||
}
|
||||
|
||||
@ -2218,7 +2219,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
avatarFrame.origin.x = leftInset - avatarLeftInset + editingOffset + 10.0 + offset
|
||||
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
if let videoNode = self.videoNode {
|
||||
transition.updateFrame(node: videoNode, frame: avatarFrame)
|
||||
transition.updateFrame(node: videoNode, frame: CGRect(origin: .zero, size: avatarFrame.size))
|
||||
}
|
||||
|
||||
var onlineFrame = self.onlineNode.frame
|
||||
|
@ -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 {
|
||||
component.animateOut.connect { [weak self] completion in
|
||||
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.ignoreScrolling = false
|
||||
|
||||
if let currentAvailableSize = self.currentAvailableSize, currentAvailableSize.height != availableSize.height {
|
||||
self.scrollView.contentOffset = CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height))
|
||||
}
|
||||
self.currentAvailableSize = availableSize
|
||||
|
||||
if environment[SheetComponentEnvironment.self].value.isDisplaying, !self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateInTransition.self) {
|
||||
self.animateIn()
|
||||
} else if !environment[SheetComponentEnvironment.self].value.isDisplaying, self.previousIsDisplaying, let _ = transition.userData(ViewControllerComponentContainer.AnimateOutTransition.self) {
|
||||
|
@ -555,8 +555,16 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
var actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize)
|
||||
|
||||
var contentVerticalOffset: CGFloat = 0.0
|
||||
if keepInPlace, case .extracted = self.source {
|
||||
actionsFrame.origin.y = contentRect.minY - contentActionsSpacing - actionsFrame.height
|
||||
let statusBarHeight = (layout.statusBarHeight ?? 0.0)
|
||||
if actionsFrame.origin.y < statusBarHeight {
|
||||
let updatedActionsOriginY = statusBarHeight + contentActionsSpacing
|
||||
let delta = updatedActionsOriginY - actionsFrame.origin.y
|
||||
actionsFrame.origin.y = updatedActionsOriginY
|
||||
contentVerticalOffset = delta
|
||||
}
|
||||
}
|
||||
if centerActionsHorizontally {
|
||||
actionsFrame.origin.x = floor(contentParentGlobalFrame.minX + contentRect.midX - actionsFrame.width / 2.0)
|
||||
@ -593,14 +601,18 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
transition.updateFrame(node: self.actionsStackNode, frame: actionsFrame, beginWithCurrentState: true)
|
||||
|
||||
if let contentNode = contentNode {
|
||||
contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY), size: contentNode.containingItem.view.bounds.size), beginWithCurrentState: true)
|
||||
contentTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: contentParentGlobalFrame.minX + contentRect.minX - contentNode.containingItem.contentRect.minX, y: contentRect.minY - contentNode.containingItem.contentRect.minY + contentVerticalOffset), size: contentNode.containingItem.view.bounds.size), beginWithCurrentState: true)
|
||||
}
|
||||
|
||||
let contentHeight: CGFloat
|
||||
if self.actionsStackNode.topPositionLock != nil {
|
||||
contentHeight = layout.size.height
|
||||
} else {
|
||||
contentHeight = actionsFrame.maxY + bottomInset + layout.intrinsicInsets.bottom
|
||||
if keepInPlace, case .extracted = self.source {
|
||||
contentHeight = (layout.statusBarHeight ?? 0.0) + actionsFrame.height + abs(actionsFrame.minY) + bottomInset + layout.intrinsicInsets.bottom
|
||||
} else {
|
||||
contentHeight = actionsFrame.maxY + bottomInset + layout.intrinsicInsets.bottom
|
||||
}
|
||||
}
|
||||
let contentSize = CGSize(width: layout.size.width, height: contentHeight)
|
||||
|
||||
|
@ -150,9 +150,11 @@ final class GameControllerNode: ViewControllerTracingNode {
|
||||
if let strongSelf = self, let message = strongSelf.message {
|
||||
let signals = peerIds.map { TelegramEngine(account: account).messages.forwardGameWithScore(messageId: message.id, to: $0, as: nil) }
|
||||
return .single(.preparing(false))
|
||||
|> castError(ShareControllerError.self)
|
||||
|> then(
|
||||
combineLatest(signals)
|
||||
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, NoError> in return .complete() }
|
||||
|> castError(ShareControllerError.self)
|
||||
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, ShareControllerError> in return .complete() }
|
||||
)
|
||||
|> then(.single(.done))
|
||||
} else {
|
||||
|
@ -80,7 +80,6 @@ public final class InAppPurchaseManager: NSObject {
|
||||
self.productRequest = productRequest
|
||||
}
|
||||
|
||||
|
||||
public var availableProducts: Signal<[Product], NoError> {
|
||||
if self.products.isEmpty && self.productRequest == nil {
|
||||
self.requestProducts()
|
||||
|
@ -169,6 +169,14 @@
|
||||
return self.backingItem.asset;
|
||||
}
|
||||
|
||||
- (SSignal *)avAsset
|
||||
{
|
||||
if ([self.asset isKindOfClass:[TGCameraCapturedVideo class]])
|
||||
return ((TGCameraCapturedVideo *)self.asset).avAsset;
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)uniqueId
|
||||
{
|
||||
if (self.asset != nil)
|
||||
|
@ -48,6 +48,7 @@ swift_library(
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -81,7 +81,7 @@ public final class PageIndicatorComponent: Component {
|
||||
|
||||
private final class PageIndicatorView: UIView {
|
||||
var displayCount: Int {
|
||||
return min(9, self.pageCount)
|
||||
return min(11, self.pageCount)
|
||||
}
|
||||
var dotSize: CGFloat = 8.0
|
||||
var dotSpace: CGFloat = 10.0
|
||||
|
@ -76,7 +76,7 @@ private final class PhoneView: UIView {
|
||||
self.borderView = UIImageView(image: phoneBorderImage)
|
||||
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6), enableBlur: false)
|
||||
self.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true))
|
||||
self.statusNode.transitionToState(.none)
|
||||
self.statusNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init(frame: frame)
|
||||
|
@ -634,7 +634,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: component.context,
|
||||
position: .bottom,
|
||||
videoFile: configuration.videos["double_limits"]
|
||||
videoFile: configuration.videos["more_upload"]
|
||||
)),
|
||||
title: strings.Premium_UploadSize,
|
||||
text: strings.Premium_UploadSizeInfo,
|
||||
@ -740,7 +740,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: component.context,
|
||||
position: .top,
|
||||
videoFile: configuration.videos["chat_management"]
|
||||
videoFile: configuration.videos["advanced_chat_management"]
|
||||
)),
|
||||
title: strings.Premium_ChatManagement,
|
||||
text: strings.Premium_ChatManagementInfo,
|
||||
@ -774,7 +774,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: component.context,
|
||||
position: .top,
|
||||
videoFile: configuration.videos["userpics"]
|
||||
videoFile: configuration.videos["animated_userpics"]
|
||||
)),
|
||||
title: strings.Premium_Avatar,
|
||||
text: strings.Premium_AvatarInfo,
|
||||
@ -899,12 +899,14 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
animationName: isStandalone && component.subject == .uniqueReactions ? "premium_unlock" : nil,
|
||||
iconPosition: .right,
|
||||
iconSpacing: 4.0,
|
||||
action: { [weak component] in
|
||||
action: { [weak component, weak state] in
|
||||
guard let component = component else {
|
||||
return
|
||||
}
|
||||
component.dismiss()
|
||||
component.action()
|
||||
if let state = state, state.isPremium == false {
|
||||
component.action()
|
||||
}
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
|
@ -1158,7 +1158,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
|
||||
tapAction: { attributes, _ in
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String,
|
||||
let controller = environment.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController {
|
||||
if url == "cancel" {
|
||||
if url.hasPrefix("https://") {
|
||||
controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: true, presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {})
|
||||
} else if url == "cancel" {
|
||||
|
||||
} else {
|
||||
let context = controller.context
|
||||
@ -1362,6 +1364,8 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
|
||||
}, completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.isPremium = true
|
||||
strongSelf.updated(transition: .easeInOut(duration: 0.25))
|
||||
strongSelf.completion()
|
||||
}
|
||||
}))
|
||||
@ -1631,13 +1635,40 @@ private final class PremiumIntroScreenComponent: CombinedComponent {
|
||||
context.add(bottomPanel
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height / 2.0))
|
||||
.opacity(bottomPanelAlpha)
|
||||
.disappear(Transition.Disappear { view, transition, completion in
|
||||
if case .none = transition.animation {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: bottomPanel.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
})
|
||||
)
|
||||
context.add(bottomSeparator
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height))
|
||||
.opacity(bottomPanelAlpha)
|
||||
.disappear(Transition.Disappear { view, transition, completion in
|
||||
if case .none = transition.animation {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: bottomPanel.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
})
|
||||
)
|
||||
context.add(button
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - bottomPanel.size.height + bottomPanelPadding + button.size.height / 2.0))
|
||||
.disappear(Transition.Disappear { view, transition, completion in
|
||||
if case .none = transition.animation {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
view.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: bottomPanel.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -1704,9 +1735,6 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
completionImpl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.view.addSubview(ConfettiView(frame: strongSelf.view.bounds))
|
||||
Queue.mainQueue().after(2.0, {
|
||||
self?.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,6 +258,8 @@ private class PremiumLimitAnimationComponent: Component {
|
||||
countWidth = 35.0
|
||||
case 3:
|
||||
countWidth = 51.0
|
||||
case 4:
|
||||
countWidth = 60.0
|
||||
default:
|
||||
countWidth = 51.0
|
||||
}
|
||||
@ -549,7 +551,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
||||
)
|
||||
|
||||
context.add(inactiveValue
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0 - activeValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0 - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0))
|
||||
)
|
||||
|
||||
context.add(activeTitle
|
||||
@ -693,7 +695,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
let premiumLimit = state.premiumLimits.maxFoldersCount
|
||||
iconName = "Premium/Folder"
|
||||
badgeText = "\(component.count)"
|
||||
string = strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxFoldersCountFinalText("\(premiumLimit)").string : strings.Premium_MaxFoldersCountText("\(limit)", "\(premiumLimit)").string
|
||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||
@ -702,7 +704,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
let premiumLimit = state.premiumLimits.maxFolderChatsCount
|
||||
iconName = "Premium/Chat"
|
||||
badgeText = "\(component.count)"
|
||||
string = strings.Premium_MaxChatsInFolderText("\(limit)", "\(premiumLimit)").string
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxChatsInFolderFinalText("\(premiumLimit)").string : strings.Premium_MaxChatsInFolderText("\(limit)", "\(premiumLimit)").string
|
||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||
@ -711,7 +713,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
let premiumLimit = state.premiumLimits.maxPinnedChatCount
|
||||
iconName = "Premium/Pin"
|
||||
badgeText = "\(component.count)"
|
||||
string = strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxPinsFinalText("\(premiumLimit)").string : strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string
|
||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||
@ -719,8 +721,8 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
let limit = Int64(state.limits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100
|
||||
let premiumLimit = Int64(state.premiumLimits.maxUploadFileParts) * 512 * 1024 + 1024 * 1024 * 100
|
||||
iconName = "Premium/File"
|
||||
badgeText = dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
|
||||
string = strings.Premium_MaxFileSizeText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string
|
||||
badgeText = dataSizeString(component.count == 4 ? premiumLimit : limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))
|
||||
string = component.count == 4 ? strings.Premium_MaxFileSizeFinalText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string : strings.Premium_MaxFileSizeText(dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator))).string
|
||||
defaultValue = component.count == 4 ? dataSizeString(limit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) : ""
|
||||
premiumValue = component.count != 4 ? dataSizeString(premiumLimit, formatting: DataSizeStringFormatting(strings: environment.strings, decimalSeparator: environment.dateTimeFormat.decimalSeparator)) : ""
|
||||
badgePosition = component.count == 4 ? 1.0 : 0.5
|
||||
@ -730,7 +732,7 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
let premiumLimit = component.count + 1
|
||||
iconName = "Premium/Account"
|
||||
badgeText = "\(component.count)"
|
||||
string = strings.Premium_MaxAccountsText("\(component.count)").string
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxAccountsFinalText("\(premiumLimit)").string : strings.Premium_MaxAccountsText("\(limit)").string
|
||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
if component.count == limit {
|
||||
|
@ -928,6 +928,7 @@ public class PremimLimitsListScreen: ViewController {
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 }))
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -7,7 +7,7 @@ import SceneKit
|
||||
import GZip
|
||||
import AppBundle
|
||||
|
||||
private let sceneVersion: Int = 2
|
||||
private let sceneVersion: Int = 3
|
||||
|
||||
private func deg2rad(_ number: Float) -> Float {
|
||||
return number * .pi / 180
|
||||
|
@ -133,6 +133,10 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// for reaction in reactions {
|
||||
// sortedReactions.append(reaction)
|
||||
// }
|
||||
|
||||
self.reactions = sortedReactions
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
@ -537,19 +541,21 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let relativeAngle = calculateRelativeAngle(angle)
|
||||
let distance = abs(relativeAngle) / CGFloat.pi
|
||||
|
||||
let updatedAngle = angle
|
||||
// updatedAngle += 10 * cos(updatedAngle)
|
||||
|
||||
let point = CGPoint(
|
||||
x: cos(angle),
|
||||
y: sin(angle)
|
||||
x: cos(updatedAngle),
|
||||
y: sin(updatedAngle)
|
||||
)
|
||||
|
||||
let itemFrame = CGRect(origin: CGPoint(x: size.width * 0.5 + point.x * areaSize.width * 0.5 - itemSize.width * 0.5, y: size.height * 0.5 + point.y * areaSize.height * 0.5 - itemSize.height * 0.5), size: itemSize)
|
||||
containerNode.bounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
containerNode.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.65)
|
||||
transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.8)
|
||||
|
||||
itemNode.frame = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ShimmerEffect
|
||||
|
||||
final class StickersCarouselComponent: Component {
|
||||
public typealias EnvironmentType = DemoPageEnvironment
|
||||
@ -88,15 +89,19 @@ private class StickerNode: ASDisplayNode {
|
||||
public var animationNode: AnimatedStickerNode?
|
||||
public var additionalAnimationNode: AnimatedStickerNode?
|
||||
|
||||
private var placeholderNode: StickerShimmerEffectNode
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
private let effectDisposable = MetaDisposable()
|
||||
|
||||
private var setupTimestamp: Double?
|
||||
|
||||
init(context: AccountContext, file: TelegramMediaFile) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
|
||||
|
||||
if file.isPremiumSticker {
|
||||
let animationNode = AnimatedStickerNode()
|
||||
self.animationNode = animationNode
|
||||
@ -123,6 +128,8 @@ private class StickerNode: ASDisplayNode {
|
||||
self.animationNode = nil
|
||||
}
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
@ -136,6 +143,38 @@ private class StickerNode: ASDisplayNode {
|
||||
if let additionalAnimationNode = self.additionalAnimationNode {
|
||||
self.addSubnode(additionalAnimationNode)
|
||||
}
|
||||
|
||||
self.addSubnode(self.placeholderNode)
|
||||
|
||||
var firstTime = true
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.started = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.imageNode.alpha = 0.0
|
||||
|
||||
let current = CACurrentMediaTime()
|
||||
if let setupTimestamp = strongSelf.setupTimestamp, current - setupTimestamp > 0.3 {
|
||||
if !strongSelf.placeholderNode.alpha.isZero {
|
||||
strongSelf.removePlaceholder(animated: true)
|
||||
}
|
||||
} else {
|
||||
strongSelf.removePlaceholder(animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -143,6 +182,18 @@ private class StickerNode: ASDisplayNode {
|
||||
self.effectDisposable.dispose()
|
||||
}
|
||||
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if !animated {
|
||||
self.placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
self.placeholderNode.alpha = 0.0
|
||||
self.placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
|
||||
self?.placeholderNode.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private var visibility: Bool = false
|
||||
private var centrality: Bool = false
|
||||
|
||||
@ -154,6 +205,12 @@ private class StickerNode: ASDisplayNode {
|
||||
public func setVisible(_ visible: Bool) {
|
||||
self.visibility = visible
|
||||
self.updatePlayback()
|
||||
|
||||
self.setupTimestamp = CACurrentMediaTime()
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
|
||||
private func updatePlayback() {
|
||||
@ -196,6 +253,11 @@ private class StickerNode: ASDisplayNode {
|
||||
additionalAnimationNode.updateLayout(size: additionalAnimationNode.frame.size)
|
||||
}
|
||||
}
|
||||
|
||||
let placeholderFrame = CGRect(origin: .zero, size: size)
|
||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: self.file.immediateThumbnailData, size: placeholderFrame.size, imageSize: thumbnailDimensions.cgSize)
|
||||
self.placeholderNode.frame = placeholderFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -527,6 +589,7 @@ private class StickersCarouselNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
containerNode.position = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.75)
|
||||
transition.updateAlpha(node: containerNode, alpha: 1.0 - distance * 0.6)
|
||||
itemNode.updateAbsoluteRect(itemFrame, within: size)
|
||||
|
||||
let isVisible = self.visibility && itemFrame.intersects(bounds)
|
||||
itemNode.setVisible(isVisible)
|
||||
|
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 maskLayer: SimpleLayer
|
||||
private let backgroundLayer: SimpleLayer
|
||||
private let backgroundClippingLayer: SimpleLayer
|
||||
private let backgroundMaskNode: ASDisplayNode
|
||||
private let backgroundShadowLayer: SimpleLayer
|
||||
private let largeCircleLayer: SimpleLayer
|
||||
private let largeCircleShadowLayer: SimpleLayer
|
||||
@ -51,23 +52,24 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
init(largeCircleSize: CGFloat, smallCircleSize: CGFloat) {
|
||||
init(largeCircleSize: CGFloat, smallCircleSize: CGFloat, maskNode: ASDisplayNode) {
|
||||
self.largeCircleSize = largeCircleSize
|
||||
self.smallCircleSize = smallCircleSize
|
||||
|
||||
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: true)
|
||||
|
||||
self.maskLayer = SimpleLayer()
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.backgroundClippingLayer = SimpleLayer()
|
||||
self.backgroundClippingLayer.cornerRadius = 52.0
|
||||
self.backgroundClippingLayer.masksToBounds = true
|
||||
self.backgroundMaskNode = maskNode
|
||||
|
||||
self.backgroundShadowLayer = SimpleLayer()
|
||||
self.largeCircleLayer = SimpleLayer()
|
||||
self.largeCircleShadowLayer = SimpleLayer()
|
||||
self.smallCircleLayer = SimpleLayer()
|
||||
self.smallCircleShadowLayer = SimpleLayer()
|
||||
|
||||
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
|
||||
self.backgroundLayer.masksToBounds = true
|
||||
|
||||
self.largeCircleLayer.backgroundColor = UIColor.black.cgColor
|
||||
self.largeCircleLayer.masksToBounds = true
|
||||
self.largeCircleLayer.cornerRadius = largeCircleSize / 2.0
|
||||
@ -77,7 +79,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
self.smallCircleLayer.cornerRadius = smallCircleSize / 2.0
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
self.backgroundLayer.cornerCurve = .circular
|
||||
// self.backgroundLayer.cornerCurve = .circular
|
||||
self.largeCircleLayer.cornerCurve = .circular
|
||||
self.smallCircleLayer.cornerCurve = .circular
|
||||
}
|
||||
@ -96,7 +98,9 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
|
||||
self.maskLayer.addSublayer(self.smallCircleLayer)
|
||||
self.maskLayer.addSublayer(self.largeCircleLayer)
|
||||
self.maskLayer.addSublayer(self.backgroundLayer)
|
||||
self.maskLayer.addSublayer(self.backgroundClippingLayer)
|
||||
|
||||
self.backgroundClippingLayer.addSublayer(self.backgroundMaskNode.layer)
|
||||
|
||||
self.backgroundNode.layer.mask = self.maskLayer
|
||||
}
|
||||
@ -137,12 +141,14 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var backgroundFrame = CGRect(origin: CGPoint(), size: size)
|
||||
var backgroundMaskNodeFrame = backgroundFrame
|
||||
if isMinimized {
|
||||
let updatedHeight = floor(size.height * 0.9)
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - updatedHeight), size: CGSize(width: size.width, height: updatedHeight))
|
||||
backgroundMaskNodeFrame = backgroundMaskNodeFrame.offsetBy(dx: 0.0, dy: (updatedHeight - backgroundMaskNodeFrame.height) * 0.5)
|
||||
}
|
||||
|
||||
transition.updateCornerRadius(layer: self.backgroundLayer, cornerRadius: backgroundFrame.height / 2.0)
|
||||
transition.updateCornerRadius(layer: self.backgroundClippingLayer, cornerRadius: backgroundFrame.height / 2.0)
|
||||
|
||||
let largeCircleFrame: CGRect
|
||||
let smallCircleFrame: CGRect
|
||||
@ -156,7 +162,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
|
||||
let contentBounds = backgroundFrame.insetBy(dx: -10.0, dy: -10.0).union(largeCircleFrame).union(smallCircleFrame)
|
||||
|
||||
transition.updateFrame(layer: self.backgroundLayer, frame: backgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true)
|
||||
transition.updateFrame(node: self.backgroundMaskNode, frame: backgroundMaskNodeFrame, beginWithCurrentState: true)
|
||||
transition.updateFrame(layer: self.backgroundClippingLayer, frame: backgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true)
|
||||
transition.updateFrame(layer: self.largeCircleLayer, frame: largeCircleFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true)
|
||||
transition.updateFrame(layer: self.smallCircleLayer, frame: smallCircleFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY), beginWithCurrentState: true)
|
||||
|
||||
@ -181,8 +188,8 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
self.largeCircleLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay)
|
||||
self.largeCircleShadowLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: largeCircleDuration, delay: largeCircleDelay)
|
||||
|
||||
self.backgroundLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.01, delay: mainCircleDelay)
|
||||
self.backgroundLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
|
||||
self.backgroundClippingLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.01, delay: mainCircleDelay)
|
||||
self.backgroundClippingLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
|
||||
self.backgroundShadowLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
|
||||
}
|
||||
|
||||
@ -197,14 +204,14 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
let visualSourceBackgroundFrame = sourceBackgroundFrame.offsetBy(dx: -contentBounds.minX, dy: -contentBounds.minY)
|
||||
let sourceShadowFrame = visualSourceBackgroundFrame.insetBy(dx: -shadowInset, dy: -shadowInset)
|
||||
|
||||
self.backgroundLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: visualSourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.backgroundLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualSourceBackgroundFrame.size)), to: NSValue(cgRect: self.backgroundLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
self.backgroundClippingLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: visualSourceBackgroundFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.backgroundClippingLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualSourceBackgroundFrame.size)), to: NSValue(cgRect: self.backgroundClippingLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
self.backgroundShadowLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceShadowFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.backgroundShadowLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceShadowFrame.size)), to: NSValue(cgRect: self.backgroundShadowLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
}
|
||||
|
||||
func animateOut() {
|
||||
self.backgroundLayer.animateAlpha(from: CGFloat(self.backgroundLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.backgroundClippingLayer.animateAlpha(from: CGFloat(self.backgroundClippingLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.backgroundShadowLayer.animateAlpha(from: CGFloat(self.backgroundShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
self.largeCircleLayer.animateAlpha(from: CGFloat(self.largeCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.largeCircleShadowLayer.animateAlpha(from: CGFloat(self.largeCircleShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
|
@ -73,9 +73,13 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
private let contentContainer: ASDisplayNode
|
||||
private let contentContainerMask: UIImageView
|
||||
private let leftBackgroundMaskNode: ASDisplayNode
|
||||
private let rightBackgroundMaskNode: ASDisplayNode
|
||||
private let backgroundMaskNode: ASDisplayNode
|
||||
private let scrollNode: ASScrollNode
|
||||
private let previewingItemContainer: ASDisplayNode
|
||||
private var visibleItemNodes: [Int: ReactionItemNode] = [:]
|
||||
private var visibleItemMaskNodes: [Int: ASDisplayNode] = [:]
|
||||
|
||||
private var longPressRecognizer: UILongPressGestureRecognizer?
|
||||
private var longPressTimer: SwiftSignalKit.Timer?
|
||||
@ -101,7 +105,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.theme = theme
|
||||
self.items = items
|
||||
|
||||
self.backgroundNode = ReactionContextBackgroundNode(largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize)
|
||||
self.backgroundMaskNode = ASDisplayNode()
|
||||
self.backgroundNode = ReactionContextBackgroundNode(largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode)
|
||||
self.leftBackgroundMaskNode = ASDisplayNode()
|
||||
self.leftBackgroundMaskNode.backgroundColor = .black
|
||||
self.rightBackgroundMaskNode = ASDisplayNode()
|
||||
self.rightBackgroundMaskNode.backgroundColor = .black
|
||||
self.backgroundMaskNode.addSubnode(self.leftBackgroundMaskNode)
|
||||
self.backgroundMaskNode.addSubnode(self.rightBackgroundMaskNode)
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNode.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||
@ -275,6 +286,9 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
highlightedReactionIndex = nil
|
||||
}
|
||||
|
||||
var currentMaskFrame: CGRect?
|
||||
var maskTransition: ContainedViewLayoutTransition?
|
||||
|
||||
var validIndices = Set<Int>()
|
||||
var nextX: CGFloat = sideInset
|
||||
for i in 0 ..< self.items.count {
|
||||
@ -324,21 +338,38 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var animateIn = false
|
||||
|
||||
let maskNode: ASDisplayNode?
|
||||
let itemNode: ReactionItemNode
|
||||
var itemTransition = transition
|
||||
if let current = self.visibleItemNodes[i] {
|
||||
itemNode = current
|
||||
maskNode = self.visibleItemMaskNodes[i]
|
||||
} else {
|
||||
animateIn = self.didAnimateIn
|
||||
itemTransition = .immediate
|
||||
|
||||
if case let .reaction(item) = self.items[i] {
|
||||
itemNode = ReactionNode(context: self.context, theme: self.theme, item: item)
|
||||
maskNode = nil
|
||||
} else {
|
||||
itemNode = PremiumReactionsNode(theme: self.theme)
|
||||
maskNode = itemNode.maskNode
|
||||
}
|
||||
self.visibleItemNodes[i] = itemNode
|
||||
|
||||
self.scrollNode.addSubnode(itemNode)
|
||||
|
||||
if let maskNode = maskNode {
|
||||
self.visibleItemMaskNodes[i] = maskNode
|
||||
self.backgroundMaskNode.addSubnode(maskNode)
|
||||
}
|
||||
}
|
||||
maskTransition = itemTransition
|
||||
|
||||
if let maskNode = maskNode {
|
||||
let maskFrame = CGRect(origin: CGPoint(x: -self.scrollNode.view.contentOffset.x + itemFrame.minX, y: 0.0), size: CGSize(width: itemFrame.width, height: itemFrame.height + 12.0))
|
||||
itemTransition.updateFrame(node: maskNode, frame: maskFrame)
|
||||
currentMaskFrame = maskFrame
|
||||
}
|
||||
|
||||
if !itemNode.isExtracted {
|
||||
@ -370,6 +401,15 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if let currentMaskFrame = currentMaskFrame {
|
||||
let transition = maskTransition ?? transition
|
||||
transition.updateFrame(node: self.leftBackgroundMaskNode, frame: CGRect(x: -1000.0 + currentMaskFrame.minX, y: 0.0, width: 1000.0, height: 52.0))
|
||||
transition.updateFrame(node: self.rightBackgroundMaskNode, frame: CGRect(x: currentMaskFrame.maxX, y: 0.0, width: 1000.0, height: 52.0))
|
||||
} else {
|
||||
self.leftBackgroundMaskNode.frame = CGRect(x: 0.0, y: 0.0, width: 1000.0, height: 52.0)
|
||||
self.rightBackgroundMaskNode.frame = CGRect(origin: .zero, size: .zero)
|
||||
}
|
||||
|
||||
var removedIndices: [Int] = []
|
||||
for (index, itemNode) in self.visibleItemNodes {
|
||||
if !validIndices.contains(index) {
|
||||
@ -377,8 +417,14 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
itemNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
for (index, maskNode) in self.visibleItemMaskNodes {
|
||||
if !validIndices.contains(index) {
|
||||
maskNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
for index in removedIndices {
|
||||
self.visibleItemNodes.removeValue(forKey: index)
|
||||
self.visibleItemMaskNodes.removeValue(forKey: index)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,8 @@ private let font = Font.medium(13.0)
|
||||
protocol ReactionItemNode: ASDisplayNode {
|
||||
var isExtracted: Bool { get }
|
||||
|
||||
var maskNode: ASDisplayNode? { get }
|
||||
|
||||
func appear(animated: Bool)
|
||||
func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition)
|
||||
}
|
||||
@ -107,6 +109,10 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
|
||||
self.fetchFullAnimationDisposable?.dispose()
|
||||
}
|
||||
|
||||
var maskNode: ASDisplayNode? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func appear(animated: Bool) {
|
||||
if animated {
|
||||
self.animateInAnimationNode?.visibility = true
|
||||
@ -351,13 +357,29 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
|
||||
var isExtracted: Bool = false
|
||||
|
||||
let imageNode: ASImageNode
|
||||
let maskImageNode: ASImageNode
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.contentMode = .center
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.imageNode.isUserInteractionEnabled = false
|
||||
self.imageNode.image = generatePremiumReactionIcon()
|
||||
self.imageNode.image = UIImage(bundleImageName: "Premium/ReactionsForeground")
|
||||
|
||||
self.maskImageNode = ASImageNode()
|
||||
if let backgroundImage = UIImage(bundleImageName: "Premium/ReactionsBackground") {
|
||||
self.maskImageNode.image = generateImage(CGSize(width: 40.0, height: 52.0), contextGenerator: { size, context in
|
||||
context.setFillColor(UIColor.black.cgColor)
|
||||
context.fill(CGRect(origin: .zero, size: size))
|
||||
|
||||
if let cgImage = backgroundImage.cgImage {
|
||||
let maskFrame = CGRect(origin: .zero, size: size).insetBy(dx: 4.0, dy: 10.0)
|
||||
context.clip(to: maskFrame, mask: cgImage)
|
||||
}
|
||||
context.setBlendMode(.clear)
|
||||
context.fill(CGRect(origin: .zero, size: size))
|
||||
})
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
@ -371,4 +393,9 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode {
|
||||
func updateLayout(size: CGSize, isExpanded: Bool, largeExpanded: Bool, isPreviewing: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
|
||||
var maskNode: ASDisplayNode? {
|
||||
return self.maskImageNode
|
||||
}
|
||||
}
|
||||
|
@ -99,9 +99,11 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
||||
private let iconNode: ASImageNode
|
||||
private let overlayNode: ASImageNode
|
||||
private let lockNode: ASImageNode
|
||||
private let textNode: ASTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private var action: (() -> Void)?
|
||||
|
||||
private var locked = false
|
||||
|
||||
override init() {
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 62.0))
|
||||
@ -112,10 +114,11 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
||||
self.overlayNode.isLayerBacked = true
|
||||
|
||||
self.lockNode = ASImageNode()
|
||||
self.lockNode.contentMode = .scaleAspectFit
|
||||
self.lockNode.displaysAsynchronously = false
|
||||
self.lockNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
|
||||
@ -128,6 +131,7 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
||||
}
|
||||
|
||||
func setup(theme: PresentationTheme, icon: UIImage, title: NSAttributedString, locked: Bool, color: UIColor, bordered: Bool, selected: Bool, action: @escaping () -> Void) {
|
||||
self.locked = locked
|
||||
self.iconNode.image = icon
|
||||
self.textNode.attributedText = title
|
||||
self.overlayNode.image = generateBorderImage(theme: theme, bordered: bordered, selected: selected)
|
||||
@ -135,6 +139,8 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
||||
self.action = {
|
||||
action()
|
||||
}
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -154,10 +160,17 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
||||
|
||||
let bounds = self.bounds
|
||||
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 87.0), size: CGSize(width: bounds.size.width, height: 16.0))
|
||||
self.lockNode.frame = CGRect(x: 9.0, y: 90.0, width: 6.0, height: 8.0)
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(x: 9.0, y: 14.0), size: CGSize(width: 62.0, height: 62.0))
|
||||
|
||||
let textSize = self.textNode.updateLayout(bounds.size)
|
||||
var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - textSize.width)) / 2.0, y: 87.0), size: textSize)
|
||||
if self.locked {
|
||||
textFrame = textFrame.offsetBy(dx: 5.0, dy: 0.0)
|
||||
}
|
||||
self.textNode.frame = textFrame
|
||||
|
||||
self.lockNode.frame = CGRect(x: self.textNode.frame.minX - 10.0, y: 90.0, width: 6.0, height: 8.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,12 +344,12 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
||||
name = item.strings.Appearance_AppIconNew1
|
||||
case "New2":
|
||||
name = item.strings.Appearance_AppIconNew2
|
||||
case "PremiumCosmic":
|
||||
name = "Cosmic"
|
||||
case "PremiumCherry":
|
||||
name = "Cherry"
|
||||
case "PremiumDuck":
|
||||
name = "Duck"
|
||||
case "Premium":
|
||||
name = "Premium"
|
||||
case "PremiumBlack":
|
||||
name = "Black"
|
||||
case "PremiumTurbo":
|
||||
name = "Turbo"
|
||||
default:
|
||||
name = icon.name
|
||||
}
|
||||
|
@ -39,6 +39,10 @@ public enum ShareControllerExternalStatus {
|
||||
case done
|
||||
}
|
||||
|
||||
public enum ShareControllerError {
|
||||
case fileTooBig(Int64)
|
||||
}
|
||||
|
||||
public struct ShareControllerSegmentedValue {
|
||||
let title: String
|
||||
let subject: ShareControllerSubject
|
||||
@ -61,7 +65,7 @@ public enum ShareControllerSubject {
|
||||
case image([ImageRepresentationWithReference])
|
||||
case media(AnyMediaReference)
|
||||
case mapMedia(TelegramMediaMap)
|
||||
case fromExternal(([PeerId], String, Account, Bool) -> Signal<ShareControllerExternalStatus, NoError>)
|
||||
case fromExternal(([PeerId], String, Account, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
|
||||
}
|
||||
|
||||
private enum ExternalShareItem {
|
||||
@ -670,18 +674,20 @@ public final class ShareController: ViewController {
|
||||
let queue = Queue.mainQueue()
|
||||
var displayedError = false
|
||||
return combineLatest(queue: queue, shareSignals)
|
||||
|> mapToSignal { messageIdSets -> Signal<ShareState, NoError> in
|
||||
var statuses: [Signal<(MessageId, PendingMessageStatus?, PendingMessageFailureReason?), NoError>] = []
|
||||
|> castError(ShareControllerError.self)
|
||||
|> mapToSignal { messageIdSets -> Signal<ShareState, ShareControllerError> in
|
||||
var statuses: [Signal<(MessageId, PendingMessageStatus?, PendingMessageFailureReason?), ShareControllerError>] = []
|
||||
for messageIds in messageIdSets {
|
||||
for case let id? in messageIds {
|
||||
statuses.append(account.pendingMessageManager.pendingMessageStatus(id)
|
||||
|> castError(ShareControllerError.self)
|
||||
|> map { status, error -> (MessageId, PendingMessageStatus?, PendingMessageFailureReason?) in
|
||||
return (id, status, error)
|
||||
})
|
||||
}
|
||||
}
|
||||
return combineLatest(queue: queue, statuses)
|
||||
|> mapToSignal { statuses -> Signal<ShareState, NoError> in
|
||||
|> mapToSignal { statuses -> Signal<ShareState, ShareControllerError> in
|
||||
var hasStatuses = false
|
||||
for (id, status, error) in statuses {
|
||||
if let error = error {
|
||||
|
@ -59,7 +59,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
var dismiss: ((Bool) -> 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 switchToAnotherAccount: (() -> Void)?
|
||||
var debugAction: (() -> Void)?
|
||||
@ -673,7 +673,6 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
if !self.fromForeignApp {
|
||||
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 {
|
||||
let diminsionsSize = dimensions.cgSizeValue
|
||||
return .single(.preparing(false))
|
||||
|> then(
|
||||
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(diminsionsSize.width), height: Int32(diminsionsSize.height)))
|
||||
|> mapError { _ -> Void in
|
||||
return Void()
|
||||
|> mapError { _ -> PreparedShareItemError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
switch event {
|
||||
case let .progress(value):
|
||||
return .single(.progress(value))
|
||||
@ -72,10 +77,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
return .single(.preparing(false))
|
||||
|> then(
|
||||
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)))
|
||||
|> mapError { _ -> Void in
|
||||
return Void()
|
||||
|> mapError { _ -> PreparedShareItemError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
switch event {
|
||||
case let .progress(value):
|
||||
return .single(.progress(value))
|
||||
@ -111,7 +116,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
}
|
||||
var finalDuration: Double = CMTimeGetSeconds(asset.duration)
|
||||
|
||||
func loadValues(_ avAsset: AVURLAsset) -> Signal<AVURLAsset, Void> {
|
||||
func loadValues(_ avAsset: AVURLAsset) -> Signal<AVURLAsset, PreparedShareItemError> {
|
||||
return Signal { subscriber in
|
||||
avAsset.loadValuesAsynchronously(forKeys: ["tracks", "duration", "playable"]) {
|
||||
subscriber.putNext(avAsset)
|
||||
@ -123,7 +128,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
return .single(.preparing(true))
|
||||
|> then(
|
||||
loadValues(asset)
|
||||
|> mapToSignal { asset -> Signal<PreparedShareItem, Void> in
|
||||
|> mapToSignal { asset -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
let preset = adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium
|
||||
let finalDimensions = TGMediaVideoConverter.dimensions(for: asset.originalSize, adjustments: adjustments, preset: preset)
|
||||
|
||||
@ -142,10 +147,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
|
||||
let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: asset.url.path, adjustments: resourceAdjustments)
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in
|
||||
return Void()
|
||||
|> mapError { _ -> PreparedShareItemError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
switch event {
|
||||
case let .progress(value):
|
||||
return .single(.progress(value))
|
||||
@ -201,7 +206,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
return .single(.preparing(true))
|
||||
|> then(
|
||||
convertedData
|
||||
|> castError(Void.self)
|
||||
|> castError(PreparedShareItemError.self)
|
||||
|> mapToSignal { data, dimensions, duration, converted in
|
||||
var attributes: [TelegramMediaFileAttribute] = []
|
||||
let mimeType: String
|
||||
@ -213,8 +218,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")]
|
||||
}
|
||||
return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), mimeType: mimeType, attributes: attributes, hintFileIsLarge: data.count > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in return Void() }
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
|> mapError { _ -> PreparedShareItemError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
switch event {
|
||||
case let .progress(value):
|
||||
return .single(.progress(value))
|
||||
@ -230,8 +237,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
return .single(.preparing(false))
|
||||
|> then(
|
||||
standaloneUploadedImage(account: account, peerId: peerId, text: "", data: imageData, dimensions: PixelDimensions(width: Int32(scaledImage.size.width), height: Int32(scaledImage.size.height)))
|
||||
|> mapError { _ -> Void in return Void() }
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
|> mapError { _ -> PreparedShareItemError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
switch event {
|
||||
case let .progress(value):
|
||||
return .single(.progress(value))
|
||||
@ -251,8 +260,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
return .single(.preparing(long))
|
||||
|> then(
|
||||
standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(data), thumbnailData: thumbnailData, mimeType: mimeType, attributes: [.FileName(fileName: fileName ?? "file")], hintFileIsLarge: data.count > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in return Void() }
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
|> mapError { _ -> PreparedShareItemError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
switch event {
|
||||
case let .progress(value):
|
||||
return .single(.progress(value))
|
||||
@ -280,8 +291,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
return .single(.preparing(long))
|
||||
|> then(
|
||||
standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .data(audioData), mimeType: mimeType, attributes: [.Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: artist, waveform: waveform?.makeData()), .FileName(fileName: fileName)], hintFileIsLarge: audioData.count > 10 * 1024 * 1024)
|
||||
|> mapError { _ -> Void in return Void() }
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, Void> in
|
||||
|> mapError { _ -> PreparedShareItemError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { event -> Signal<PreparedShareItem, PreparedShareItemError> in
|
||||
switch event {
|
||||
case let .progress(value):
|
||||
return .single(.progress(value))
|
||||
@ -300,7 +313,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
)
|
||||
} else if let url = value["url"] as? URL {
|
||||
if TGShareLocationSignals.isLocationURL(url) {
|
||||
return Signal<PreparedShareItem, Void> { subscriber in
|
||||
return Signal<PreparedShareItem, PreparedShareItemError> { subscriber in
|
||||
subscriber.putNext(.preparing(false))
|
||||
let disposable = TGShareLocationSignals.locationMessageContent(for: url).start(next: { value in
|
||||
if let value = value as? TGShareLocationResult {
|
||||
@ -332,8 +345,8 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri
|
||||
}
|
||||
}
|
||||
|
||||
public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [MTSignal], additionalText: String) -> Signal<PreparedShareItems, Void> {
|
||||
var dataSignals: Signal<[String: Any], Void> = .complete()
|
||||
public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [MTSignal], additionalText: String) -> Signal<PreparedShareItems, PreparedShareItemError> {
|
||||
var dataSignals: Signal<[String: Any], PreparedShareItemError> = .complete()
|
||||
for dataItem in dataItems {
|
||||
let wrappedSignal: Signal<[String: Any], NoError> = Signal { subscriber in
|
||||
let disposable = dataItem.start(next: { value in
|
||||
@ -349,7 +362,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [
|
||||
dataSignals = dataSignals
|
||||
|> then(
|
||||
wrappedSignal
|
||||
|> castError(Void.self)
|
||||
|> castError(PreparedShareItemError.self)
|
||||
|> take(1)
|
||||
)
|
||||
}
|
||||
@ -359,7 +372,7 @@ public func preparedShareItems(account: Account, to peerId: PeerId, dataItems: [
|
||||
|> reduceLeft(value: [[String: Any]](), f: { list, rest in
|
||||
return list + rest
|
||||
})
|
||||
|> mapToSignal { items -> Signal<[PreparedShareItem], Void> in
|
||||
|> mapToSignal { items -> Signal<[PreparedShareItem], PreparedShareItemError> in
|
||||
return combineLatest(items.map {
|
||||
preparedShareItem(account: account, to: peerId, value: $0)
|
||||
})
|
||||
|
@ -34,6 +34,7 @@ swift_library(
|
||||
"//submodules/MoreButtonNode:MoreButtonNode",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -16,6 +16,8 @@ import UndoUI
|
||||
import ShareController
|
||||
import TextFormat
|
||||
import PremiumUI
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
|
||||
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool)
|
||||
@ -577,6 +579,10 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
self.actionAreaSeparatorNode.alpha = backgroundAlpha
|
||||
}
|
||||
|
||||
var onLoading: () -> Void = {}
|
||||
var onReady: () -> Void = {}
|
||||
var onError: () -> Void = {}
|
||||
|
||||
private var currentContents: LoadedStickerPack?
|
||||
private func updateStickerPackContents(_ contents: LoadedStickerPack, hasPremium: Bool) {
|
||||
self.currentContents = contents
|
||||
@ -590,6 +596,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
|
||||
switch contents {
|
||||
case .fetching:
|
||||
self.onLoading()
|
||||
entries = []
|
||||
self.buttonNode.setTitle(self.presentationData.strings.Channel_NotificationLoading, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemDisabledTextColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
@ -620,20 +627,11 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
self.titleContainer.addSubnode(titlePlaceholderNode)
|
||||
}
|
||||
case .none:
|
||||
entries = []
|
||||
self.buttonNode.setTitle(self.presentationData.strings.Common_Close, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
|
||||
for _ in 0 ..< 16 {
|
||||
let resolvedStableId = self.nextStableId
|
||||
self.nextStableId += 1
|
||||
entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: nil, isEmpty: true, isPremium: false, isLocked: false))
|
||||
}
|
||||
if let titlePlaceholderNode = self.titlePlaceholderNode {
|
||||
self.titlePlaceholderNode = nil
|
||||
titlePlaceholderNode.removeFromSupernode()
|
||||
}
|
||||
self.onError()
|
||||
self.controller?.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
self.controller?.dismiss(animated: true, completion: nil)
|
||||
case let .result(info, items, installed):
|
||||
self.onReady()
|
||||
if !items.isEmpty && self.currentStickerPack == nil {
|
||||
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false)
|
||||
@ -978,6 +976,10 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
var onLoading: () -> Void = {}
|
||||
var onReady: () -> Void = {}
|
||||
var onError: () -> Void = {}
|
||||
|
||||
init(context: AccountContext, controller: StickerPackScreenImpl, stickerPacks: [StickerPackReference], initialSelectedStickerPackIndex: Int, modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, openMention: @escaping (String) -> Void) {
|
||||
self.context = context
|
||||
self.controller = controller
|
||||
@ -1104,6 +1106,15 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
}
|
||||
}, presentInGlobalOverlay: presentInGlobalOverlay, sendSticker: sendSticker, openMention: openMention, controller: self.controller)
|
||||
container.onReady = { [weak self] in
|
||||
self?.onReady()
|
||||
}
|
||||
container.onLoading = { [weak self] in
|
||||
self?.onLoading()
|
||||
}
|
||||
container.onError = { [weak self] in
|
||||
self?.onError()
|
||||
}
|
||||
self.containerContainingNode.addSubnode(container)
|
||||
self.containers[i] = container
|
||||
}
|
||||
@ -1380,17 +1391,76 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
}))
|
||||
})
|
||||
|
||||
var loaded = false
|
||||
var dismissed = false
|
||||
|
||||
var overlayStatusController: ViewController?
|
||||
let cancelImpl: (() -> Void)? = { [weak self] in
|
||||
dismissed = true
|
||||
overlayStatusController?.dismiss()
|
||||
self?.dismiss()
|
||||
}
|
||||
|
||||
self.controllerNode.onReady = { [weak self] in
|
||||
loaded = true
|
||||
|
||||
if let strongSelf = self {
|
||||
if !dismissed {
|
||||
if let overlayStatusController = overlayStatusController {
|
||||
overlayStatusController.dismiss()
|
||||
}
|
||||
|
||||
if strongSelf.alreadyDidAppear {
|
||||
strongSelf.controllerNode.animateIn()
|
||||
} else {
|
||||
strongSelf.isReady = true
|
||||
}
|
||||
|
||||
self?.controllerNode.isHidden = false
|
||||
self?.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = self.presentationData
|
||||
self.controllerNode.onLoading = { [weak self] in
|
||||
Queue.mainQueue().after(0.15, {
|
||||
if !loaded {
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
self?.present(controller, in: .window(.root))
|
||||
overlayStatusController = controller
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.controllerNode.onError = {
|
||||
loaded = true
|
||||
|
||||
if let overlayStatusController = overlayStatusController {
|
||||
overlayStatusController.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
self.controllerNode.isHidden = true
|
||||
|
||||
self._ready.set(self.controllerNode.ready.get())
|
||||
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
private var isReady = false
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if !self.alreadyDidAppear {
|
||||
self.alreadyDidAppear = true
|
||||
self.controllerNode.animateIn()
|
||||
|
||||
if self.isReady {
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,9 +147,6 @@ public struct ChatListFilterIncludePeers: Equatable, Hashable {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.peers.count + self.pinnedPeers.count >= 100 {
|
||||
return false
|
||||
}
|
||||
self.peers.insert(peerId, at: 0)
|
||||
return true
|
||||
}
|
||||
|
@ -85,7 +85,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
||||
if query == "\u{2764}" {
|
||||
query = "\u{2764}\u{FE0F}"
|
||||
}
|
||||
return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?) in
|
||||
return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool) in
|
||||
let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false
|
||||
|
||||
var result: [FoundStickerItem] = []
|
||||
if scope.contains(.installed) {
|
||||
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) {
|
||||
@ -108,6 +110,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
||||
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) {
|
||||
if let item = entry.contents.get(RecentMediaItem.self) {
|
||||
let file = item.media
|
||||
if file.isPremiumSticker && !isPremium {
|
||||
continue
|
||||
}
|
||||
|
||||
if !currentItems.contains(file.fileId) {
|
||||
for case let .Sticker(displayText, _, _) in file.attributes {
|
||||
@ -135,6 +140,10 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
||||
var installedAnimatedItems: [FoundStickerItem] = []
|
||||
for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) {
|
||||
if let item = item as? StickerPackItem {
|
||||
if item.file.isPremiumSticker && !isPremium {
|
||||
continue
|
||||
}
|
||||
|
||||
if !currentItems.contains(item.file.fileId) {
|
||||
var stringRepresentations: [String] = []
|
||||
for key in item.indexKeys {
|
||||
@ -158,12 +167,18 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
||||
}
|
||||
|
||||
for file in recentAnimatedItems {
|
||||
if file.isPremiumSticker && !isPremium {
|
||||
continue
|
||||
}
|
||||
if matchingRecentItemsIds.contains(file.fileId) {
|
||||
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
||||
}
|
||||
}
|
||||
|
||||
for file in recentItems {
|
||||
if file.isPremiumSticker && !isPremium {
|
||||
continue
|
||||
}
|
||||
if matchingRecentItemsIds.contains(file.fileId) {
|
||||
result.append(FoundStickerItem(file: file, stringRepresentations: [query]))
|
||||
}
|
||||
@ -183,8 +198,8 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
||||
cached = nil
|
||||
}
|
||||
|
||||
return (result, cached)
|
||||
} |> mapToSignal { localItems, cached -> Signal<[FoundStickerItem], NoError> in
|
||||
return (result, cached, isPremium)
|
||||
} |> mapToSignal { localItems, cached, isPremium -> Signal<[FoundStickerItem], NoError> in
|
||||
var tempResult: [FoundStickerItem] = localItems
|
||||
if !scope.contains(.remote) {
|
||||
return .single(tempResult)
|
||||
@ -196,6 +211,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
||||
var cachedAnimatedItems: [FoundStickerItem] = []
|
||||
|
||||
for file in cached.items {
|
||||
if file.isPremiumSticker && !isPremium {
|
||||
continue
|
||||
}
|
||||
if !currentItemIds.contains(file.fileId) {
|
||||
if file.isAnimatedSticker || file.isVideoSticker {
|
||||
cachedAnimatedItems.append(FoundStickerItem(file: file, stringRepresentations: []))
|
||||
@ -226,6 +244,9 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|
||||
var files: [TelegramMediaFile] = []
|
||||
for sticker in stickers {
|
||||
if let file = telegramMediaFileFromApiDocument(sticker), let id = file.id {
|
||||
if file.isPremiumSticker && !isPremium {
|
||||
continue
|
||||
}
|
||||
files.append(file)
|
||||
if !currentItemIds.contains(id) {
|
||||
if file.isAnimatedSticker || file.isVideoSticker {
|
||||
|
@ -167,7 +167,6 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatInputMediaPanelTrendingGifsIcon
|
||||
case chatInputMediaPanelStickersModeIcon
|
||||
case chatInputMediaPanelPremiumIcon
|
||||
case chatInputMediaStickerGridPremiumIcon
|
||||
|
||||
case chatInputButtonPanelButtonImage
|
||||
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? {
|
||||
return theme.image(PresentationResourceKey.chatInputMediaPanelRecentStickersIconImage.rawValue, { theme 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: "PremiumCosmic", imageName: "PremiumCosmic", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumCherry", imageName: "PremiumCherry", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumDuck", imageName: "PremiumDuck", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "Premium", imageName: "Premium", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumBlack", imageName: "PremiumBlack", isPremium: true))
|
||||
icons.append(PresentationAppIcon(name: "PremiumTurbo", imageName: "PremiumTurbo", isPremium: true))
|
||||
|
||||
return icons
|
||||
} else {
|
||||
|
@ -243,18 +243,19 @@ func presentedLegacyShortcutCamera(context: AccountContext, saveCapturedMedia: B
|
||||
if let parentController = parentController {
|
||||
parentController.present(ShareController(context: context, subject: .fromExternal({ peerIds, text, account, silently in
|
||||
return legacyAssetPickerEnqueueMessages(account: account, signals: signals!)
|
||||
|> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], NoError> in
|
||||
|> `catch` { _ -> Signal<[LegacyAssetPickerEnqueueMessage], ShareControllerError> in
|
||||
return .single([])
|
||||
}
|
||||
|> mapToSignal { messages -> Signal<ShareControllerExternalStatus, NoError> in
|
||||
|> mapToSignal { messages -> Signal<ShareControllerExternalStatus, ShareControllerError> in
|
||||
let resultSignals = peerIds.map({ peerId in
|
||||
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 combineLatest(resultSignals)
|
||||
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, NoError> in
|
||||
|> mapToSignal { _ -> Signal<ShareControllerExternalStatus, ShareControllerError> in
|
||||
return .complete()
|
||||
}
|
||||
|> then(.single(ShareControllerExternalStatus.done))
|
||||
|
@ -368,10 +368,15 @@ public class ShareRootControllerImpl {
|
||||
let rawSignals = TGItemProviderSignals.itemSignals(forInputItems: inputItems)!
|
||||
return preparedShareItems(account: account, to: peerIds[0], dataItems: rawSignals, additionalText: additionalText)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<PreparedShareItems?, NoError> in
|
||||
return .single(nil)
|
||||
|> `catch` { error -> Signal<PreparedShareItems?, ShareControllerError> in
|
||||
switch error {
|
||||
case .generic:
|
||||
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 {
|
||||
return .single(.done)
|
||||
}
|
||||
@ -382,11 +387,14 @@ public class ShareRootControllerImpl {
|
||||
return .single(.progress(value))
|
||||
case let .userInteractionRequired(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)
|
||||
|> castError(ShareControllerError.self)
|
||||
}
|
||||
case let .done(contents):
|
||||
return sentItems(peerIds, contents, account, silently)
|
||||
|> castError(ShareControllerError.self)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -26,6 +26,7 @@ swift_library(
|
||||
"//submodules/UrlHandling:UrlHandling",
|
||||
"//submodules/MoreButtonNode:MoreButtonNode",
|
||||
"//submodules/BotPaymentsUI:BotPaymentsUI",
|
||||
"//submodules/PromptUI:PromptUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -19,6 +19,7 @@ import LegacyComponents
|
||||
import UrlHandling
|
||||
import MoreButtonNode
|
||||
import BotPaymentsUI
|
||||
import PromptUI
|
||||
|
||||
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
||||
|
||||
@ -462,6 +463,33 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
|
||||
decisionHandler(.prompt)
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
|
||||
let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: message, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||
completionHandler()
|
||||
})])
|
||||
self.controller?.present(alertController, in: .window(.root))
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
|
||||
let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: message, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {
|
||||
completionHandler(false)
|
||||
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||
completionHandler(true)
|
||||
})])
|
||||
self.controller?.present(alertController, in: .window(.root))
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
|
||||
let promptController = promptController(sharedContext: self.context.sharedContext, updatedPresentationData: self.controller?.updatedPresentationData, text: prompt, value: defaultText, apply: { value in
|
||||
if let value = value {
|
||||
completionHandler(value)
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
})
|
||||
self.controller?.present(promptController, in: .window(.root))
|
||||
}
|
||||
|
||||
private var targetContentOffset: CGPoint?
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|