Merge commit 'ab8f433658f5aaa91b381b19b5283411fcc2104c'

This commit is contained in:
Ali 2022-06-04 21:55:06 +04:00
commit 705785e28e
58 changed files with 1488 additions and 168 deletions

View File

@ -298,9 +298,9 @@ alternate_icon_folders = [
"WhiteFilledIcon",
"New1",
"New2",
"PremiumCosmic",
"PremiumCherry",
"PremiumDuck",
"Premium",
"PremiumBlack",
"PremiumTurbo",
]
[

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -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";

View File

@ -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",

View File

@ -5,7 +5,6 @@ import AsyncDisplayKit
import Display
import SolidRoundedButtonNode
import SwiftSignalKit
import OverlayStatusController
import AnimationUI
import AccountContext
import TelegramPresentationData

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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)

View File

@ -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 {

View File

@ -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()

View File

@ -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)

View File

@ -48,6 +48,7 @@ swift_library(
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/ShimmerEffect:ShimmerEffect",
],
visibility = [
"//visibility:public",

View File

@ -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

View File

@ -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)

View File

@ -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),

View File

@ -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()
})
}
}
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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

View File

@ -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)
}
}
}

View File

@ -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
View 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",
],
)

View 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
}

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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: {
})

View File

@ -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)
})

View File

@ -34,6 +34,7 @@ swift_library(
"//submodules/MoreButtonNode:MoreButtonNode",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/PremiumUI:PremiumUI",
"//submodules/OverlayStatusController:OverlayStatusController",
],
visibility = [
"//visibility:public",

View File

@ -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()
}
}
}

View File

@ -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
}

View File

@ -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 {

View File

@ -167,7 +167,6 @@ public enum PresentationResourceKey: Int32 {
case chatInputMediaPanelTrendingGifsIcon
case chatInputMediaPanelStickersModeIcon
case chatInputMediaPanelPremiumIcon
case chatInputMediaStickerGridPremiumIcon
case chatInputButtonPanelButtonImage
case chatInputButtonPanelButtonHighlightedImage

View File

@ -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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ReactionsBg.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ReactionsFg.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -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 {

View File

@ -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))

View File

@ -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 {

View File

@ -26,6 +26,7 @@ swift_library(
"//submodules/UrlHandling:UrlHandling",
"//submodules/MoreButtonNode:MoreButtonNode",
"//submodules/BotPaymentsUI:BotPaymentsUI",
"//submodules/PromptUI:PromptUI",
],
visibility = [
"//visibility:public",

View File

@ -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) {