This commit is contained in:
Ali 2022-05-31 01:09:01 +04:00
commit 7b2ead9b23
41 changed files with 1408 additions and 765 deletions

View file

@ -7644,3 +7644,24 @@ Sorry for the inconvenience.";
"ChatListFolder.MaxChatsInFolder" = "Sorry, you can't add more than %d chats to a folder.";
"Conversation.SaveGif" = "Save GIF";
"Premium.Limits.Title" = "Doubled Limits";
"Premium.Limits.GroupsAndChannels" = "Groups and Channels";
"Premium.Limits.PinnedChats" = "Pinned Chats";
"Premium.Limits.PublicLinks" = "Public Links";
"Premium.Limits.SavedGifs" = "Saved GIFs";
"Premium.Limits.FavedStickers" = "Favorite Stickers";
"Premium.Limits.Captions" = "Captions";
"Premium.Limits.Folders" = "Folders";
"Premium.Limits.ChatsPerFolder" = "Chats per Folder";
"Premium.Limits.Accounts" = "Connected Accounts";
"Premium.Limits.GroupsAndChannelsInfo" = "Join up to 1000 channels and large groups";
"Premium.Limits.PinnedChatsInfo" = "Pin up to 10 chats in your main chat list";
"Premium.Limits.PublicLinksInfo" = "Reserve up to 20 [t.me/name]() links";
"Premium.Limits.SavedGifsInfo" = "Save up to 400 GIFs in your Favorite GIFs";
"Premium.Limits.FavedStickersInfo" = "Save up to 10 stickers in your Favorite stickers";
"Premium.Limits.CaptionsInfo" = "Use longer descriptions for your photos and videos";
"Premium.Limits.FoldersInfo" = "Organize your chats into 20 folders";
"Premium.Limits.ChatsPerFolderInfo" = "Add up to 200 chats into each of your folders";
"Premium.Limits.AccountsInfo" = "Connect 4 accounts with different mobile numbers";

View file

@ -746,6 +746,7 @@ public enum PremiumIntroSource {
case folders
case chatsPerFolder
case accounts
case about
case deeplink(String?)
case profile(PeerId)
}

View file

@ -56,8 +56,9 @@ public final class ContactMultiselectionControllerParams {
public let filters: [ContactListFilter]
public let alwaysEnabled: Bool
public let limit: Int32?
public let reachedLimit: ((Int32) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false, limit: Int32? = nil) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.mode = mode
@ -65,6 +66,7 @@ public final class ContactMultiselectionControllerParams {
self.filters = filters
self.alwaysEnabled = alwaysEnabled
self.limit = limit
self.reachedLimit = reachedLimit
}
}

View file

@ -1377,7 +1377,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
guard let strongSelf = self else {
return
}
strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter, allFilters: filters))
strongSelf.push(chatListFilterAddChatsController(context: strongSelf.context, filter: filter, allFilters: filters, limit: limits.maxFolderChatsCount, premiumLimit: premiumLimits.maxFolderChatsCount, isPremium: isPremium))
f(.dismissWithoutContent)
})
found = true

View file

@ -548,11 +548,11 @@ private enum AdditionalExcludeCategoryId: Int {
case archived
}
func chatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter, allFilters: [ChatListFilter]) -> ViewController {
return internalChatListFilterAddChatsController(context: context, filter: filter, allFilters: allFilters, applyAutomatically: true, updated: { _ in })
func chatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter, allFilters: [ChatListFilter], limit: Int32, premiumLimit: Int32, isPremium: Bool) -> ViewController {
return internalChatListFilterAddChatsController(context: context, filter: filter, allFilters: allFilters, applyAutomatically: true, limit: limit, premiumLimit: premiumLimit, isPremium: isPremium, updated: { _ in })
}
private func internalChatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter, allFilters: [ChatListFilter], applyAutomatically: Bool, updated: @escaping (ChatListFilter) -> Void) -> ViewController {
private func internalChatListFilterAddChatsController(context: AccountContext, filter: ChatListFilter, allFilters: [ChatListFilter], applyAutomatically: Bool, limit: Int32, premiumLimit: Int32, isPremium: Bool, updated: @escaping (ChatListFilter) -> Void) -> ViewController {
guard case let .filter(_, _, _, filterData) = filter else {
return ViewController(navigationBarPresentationData: nil)
}
@ -599,8 +599,33 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
}
}
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filterData.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: 100))
var pushImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filterData.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in
if count >= premiumLimit {
let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: min(premiumLimit, count), action: {})
pushImpl?(limitController)
return
} else if count >= limit && !isPremium {
var replaceImpl: ((ViewController) -> Void)?
let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: count, action: {
let introController = PremiumIntroScreen(context: context, source: .chatsPerFolder)
replaceImpl?(introController)
})
replaceImpl = { [weak limitController] c in
limitController?.replace(with: c)
}
pushImpl?(limitController)
return
}
}))
controller.navigationPresentation = .modal
pushImpl = { [weak controller] c in
controller?.push(c)
}
let _ = combineLatest(
queue: Queue.mainQueue(),
controller.result |> take(1),
@ -616,8 +641,8 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
return
}
let (accountPeer, limits, premiumLimits) = data
let isPremium = accountPeer?.isPremium ?? false
// let (accountPeer, limits, premiumLimits) = data
// let isPremium = accountPeer?.isPremium ?? false
var includePeers: [PeerId] = []
for peerId in peerIds {
@ -630,23 +655,23 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
}
includePeers.sort()
if includePeers.count > premiumLimits.maxFolderChatsCount {
let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(includePeers.count), action: {})
controller?.push(limitController)
return
} else if includePeers.count > limits.maxFolderChatsCount && !isPremium {
var replaceImpl: ((ViewController) -> Void)?
let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(includePeers.count), action: {
let introController = PremiumIntroScreen(context: context, source: .chatsPerFolder)
replaceImpl?(introController)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
controller?.push(limitController)
return
}
// if includePeers.count > premiumLimits.maxFolderChatsCount {
// let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(includePeers.count), action: {})
// controller?.push(limitController)
// return
// } else if includePeers.count > limits.maxFolderChatsCount && !isPremium {
// var replaceImpl: ((ViewController) -> Void)?
// let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(includePeers.count), action: {
// let introController = PremiumIntroScreen(context: context, source: .chatsPerFolder)
// replaceImpl?(introController)
// })
// replaceImpl = { [weak controller] c in
// controller?.replace(with: c)
// }
// controller?.push(limitController)
//
// return
// }
var categories: ChatListFilterPeerCategories = []
for id in additionalCategoryIds {
@ -1011,7 +1036,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
let _ = (context.engine.peers.currentChatListFilters()
|> deliverOnMainQueue).start(next: { filters in
let controller = internalChatListFilterAddChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, updated: { filter in
let controller = internalChatListFilterAddChatsController(context: context, filter: filter, allFilters: filters, applyAutomatically: false, limit: limits.maxFolderChatsCount, premiumLimit: premiumLimits.maxFolderChatsCount, isPremium: isPremium, updated: { filter in
skipStateAnimation = true
updateState { state in
var state = state

View file

@ -772,7 +772,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
self.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoads, displayDimensions: CGSize(width: 60.0, height: 60.0))
if peer.isPremium {
if peer.isPremium && peer.id != item.context.account.peerId {
let context = item.context
self.cachedDataDisposable.set((context.account.postbox.peerView(id: peer.id)
|> deliverOnMainQueue).start(next: { [weak self] peerView in

View file

@ -705,6 +705,9 @@ public final class ChatListNode: ListView {
var isSelectionGestureEnabled = true
public var selectionLimit: Int32 = 100
public var reachedSelectionLimit: ((Int32) -> Void)?
public init(context: AccountContext, groupId: EngineChatList.Group, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
self.context = context
self.groupId = groupId
@ -743,27 +746,32 @@ public final class ChatListNode: ListView {
disabledPeerSelected(peer)
}
}, togglePeerSelected: { [weak self] peer in
guard let strongSelf = self else {
return
}
var didBeginSelecting = false
var count = 0
self?.updateState { state in
strongSelf.updateState { [weak self] state in
var state = state
if state.selectedPeerIds.contains(peer.id) {
state.selectedPeerIds.remove(peer.id)
} else {
if state.selectedPeerIds.count < 100 {
if state.selectedPeerIds.count < strongSelf.selectionLimit {
if state.selectedPeerIds.isEmpty {
didBeginSelecting = true
}
state.selectedPeerIds.insert(peer.id)
state.selectedPeerMap[peer.id] = peer
} else {
self?.reachedSelectionLimit?(Int32(state.selectedPeerIds.count))
}
}
count = state.selectedPeerIds.count
return state
}
self?.selectionCountChanged?(count)
strongSelf.selectionCountChanged?(count)
if didBeginSelecting {
self?.didBeginSelectingChats?()
strongSelf.didBeginSelectingChats?()
}
}, togglePeersSelection: { [weak self] peers, selected in
self?.updateState { state in

View file

@ -60,7 +60,12 @@ public final class List<ChildEnvironment: Equatable>: CombinedComponent {
)
}
return context.availableSize
switch context.component.direction {
case .horizontal:
return CGSize(width: min(context.availableSize.width, nextOrigin), height: context.availableSize.height)
case.vertical:
return CGSize(width: context.availableSize.width, height: min(context.availableSize.height, nextOrigin))
}
}
}
}

View file

@ -566,7 +566,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
var currentCredibilityIconImage: UIImage?
switch item.peer {
case let .peer(peer, _):
if let peer = peer {
if let peer = peer, peer.id != item.context.account.peerId {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if peer.isFake {

View file

@ -1257,7 +1257,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|> deliverOnMainQueue).start(next: { result in
switch result {
case .generic:
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), nil)
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
case let .limitExceeded(limit, premiumLimit):
let text: String
if limit == premiumLimit {
@ -1265,7 +1265,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} else {
text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
}
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: true, animateInAsReplacement: true, action: { action in
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: true, animateInAsReplacement: false, action: { action in
if case .info = action {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs)
controllerInteraction?.pushController(controller)
@ -1451,7 +1451,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|> deliverOnMainQueue).start(next: { result in
switch result {
case .generic:
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), nil)
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil)
case let .limitExceeded(limit, premiumLimit):
let text: String
if limit == premiumLimit {
@ -1459,7 +1459,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
} else {
text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
}
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: true, animateInAsReplacement: true, action: { action in
controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: true, animateInAsReplacement: false, action: { action in
if case .info = action {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .savedGifs)
controllerInteraction?.pushController(controller)

View file

@ -603,7 +603,9 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
var updatedLabelBadgeImage: UIImage?
var currentCredibilityIconImage: UIImage?
if item.peer.id != item.context.account.peerId {
if case .threatSelfAsSaved = item.aliasHandling, item.peer.id == item.context.account.peerId {
} else {
if item.peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, strings: item.presentationData.strings, type: .regular)
} else if item.peer.isFake {

View file

@ -78,18 +78,26 @@ final class IncreaseLimitFooterItemNode: ItemListControllerFooterItemNode {
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor
let textColor: UIColor
let backgroundColor = self.item.theme.list.itemCheckColors.fillColor
let backgroundColors: [UIColor]
let icon: UIImage?
if self.item.colorful {
backgroundColors = [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)]
textColor = .white
backgroundColors = [
UIColor(rgb: 0x0077ff),
UIColor(rgb: 0x6b93ff),
UIColor(rgb: 0x8878ff),
UIColor(rgb: 0xe46ace)
]
icon = UIImage(bundleImageName: "Premium/X2")
} else {
textColor = self.item.theme.list.itemCheckColors.foregroundColor
backgroundColors = []
icon = nil
}
self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: backgroundColor, backgroundColors: backgroundColors, foregroundColor: self.item.theme.list.itemCheckColors.foregroundColor), animated: true)
self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: backgroundColor, backgroundColors: backgroundColors, foregroundColor: textColor), animated: true)
self.buttonNode.title = self.item.title
self.buttonNode.icon = icon

View file

@ -154,7 +154,7 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
],
inactiveTitle: item.strings.Premium_Free,
inactiveValue: "",
inactiveTitleColor: .black,
inactiveTitleColor: item.theme.list.itemPrimaryTextColor,
activeTitle: item.strings.Premium_Premium,
activeValue: "\(item.premiumCount)",
activeTitleColor: .white,

View file

@ -322,14 +322,19 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
buttonText = presentationData.strings.Premium_IncreaseLimit
colorful = true
}
let footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: buttonText, colorful: colorful, action: {
if state.selectedPeers.count > 0 {
leaveActionImpl?()
} else {
let controller = PremiumIntroScreen(context: context, source: .groupsAndChannels)
pushImpl?(controller)
}
})
let footerItem: IncreaseLimitFooterItem?
if state.isSearching && state.selectedPeers.count == 0 {
footerItem = nil
} else {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: buttonText, colorful: colorful, action: {
if state.selectedPeers.count > 0 {
leaveActionImpl?()
} else {
let controller = PremiumIntroScreen(context: context, source: .groupsAndChannels)
pushImpl?(controller)
}
})
}
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, limit: limits.maxChannelsCount, premiumLimit: premiumLimits.maxChannelsCount, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, footerItem: footerItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false)
@ -353,7 +358,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
let ensureStoredPeersSignal: Signal<Never, NoError> = context.engine.peers.ensurePeersAreLocallyAvailable(peers: ensureStoredPeers.map(EnginePeer.init))
return ensureStoredPeersSignal
|> then(context.engine.peers.removePeerChats(peerIds: Array(peers.map(\.peer.id))))
|> then(context.engine.peers.removePeerChats(peerIds: Array(ensureStoredPeers.map(\.id))))
}
|> deliverOnMainQueue).start(completed: {
completed(true)

View file

@ -460,13 +460,15 @@ private final class DemoSheetContent: CombinedComponent {
let context: AccountContext
let subject: PremiumDemoScreen.Subject
let source: PremiumDemoScreen.Source
let order: [PremiumPerk]
let action: () -> Void
let dismiss: () -> Void
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, action: @escaping () -> Void, dismiss: @escaping () -> Void) {
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, action: @escaping () -> Void, dismiss: @escaping () -> Void) {
self.context = context
self.subject = subject
self.source = source
self.order = order ?? [.moreUpload, .fasterDownload, .voiceToText, .noAds, .uniqueReactions, .premiumStickers, .advancedChatManagement, .profileBadge, .animatedUserpics]
self.action = action
self.dismiss = dismiss
}
@ -481,6 +483,9 @@ private final class DemoSheetContent: CombinedComponent {
if lhs.source != rhs.source {
return false
}
if lhs.order != rhs.order {
return false
}
return true
}
@ -598,164 +603,166 @@ private final class DemoSheetContent: CombinedComponent {
if let reactions = state.reactions, let stickers = state.stickers {
let textColor = theme.actionSheet.primaryTextColor
var items: [DemoPagerComponent.Item] = [
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.moreUpload,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .bottom,
videoName: "4gb"
)),
title: strings.Premium_UploadSize,
text: strings.Premium_UploadSizeInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.fasterDownload,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "fastdownload"
)),
title: strings.Premium_FasterSpeed,
text: strings.Premium_FasterSpeedInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.voiceToText,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "voice"
)),
title: strings.Premium_VoiceToText,
text: strings.Premium_VoiceToTextInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.noAds,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .bottom,
videoName: "noads"
)),
title: strings.Premium_NoAds,
text: strings.Premium_NoAdsInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.uniqueReactions,
component: AnyComponent(
PageComponent(
content: AnyComponent(
ReactionsCarouselComponent(
context: component.context,
theme: environment.theme,
reactions: reactions
)
),
title: isStandalone ? strings.Premium_ReactionsStandalone : strings.Premium_Reactions,
text: isStandalone ? strings.Premium_ReactionsStandaloneInfo : strings.Premium_ReactionsInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.premiumStickers,
component: AnyComponent(
PageComponent(
content: AnyComponent(
StickersCarouselComponent(
context: component.context,
stickers: stickers
)
),
title: strings.Premium_Stickers,
text: strings.Premium_StickersInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.advancedChatManagement,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "fastdownload"
)),
title: strings.Premium_ChatManagement,
text: strings.Premium_ChatManagementInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.profileBadge,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "badge"
)),
title: strings.Premium_Badge,
text: strings.Premium_BadgeInfo,
textColor: textColor
)
)
)
),
DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.animatedUserpics,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "badge"
)),
title: strings.Premium_Avatar,
text: strings.Premium_AvatarInfo,
textColor: textColor
)
var availableItems: [PremiumPerk: DemoPagerComponent.Item] = [:]
availableItems[.moreUpload] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.moreUpload,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .bottom,
videoName: "4gb"
)),
title: strings.Premium_UploadSize,
text: strings.Premium_UploadSizeInfo,
textColor: textColor
)
)
)
]
)
availableItems[.fasterDownload] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.fasterDownload,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "fastdownload"
)),
title: strings.Premium_FasterSpeed,
text: strings.Premium_FasterSpeedInfo,
textColor: textColor
)
)
)
)
availableItems[.voiceToText] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.voiceToText,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "voice"
)),
title: strings.Premium_VoiceToText,
text: strings.Premium_VoiceToTextInfo,
textColor: textColor
)
)
)
)
availableItems[.noAds] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.noAds,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .bottom,
videoName: "noads"
)),
title: strings.Premium_NoAds,
text: strings.Premium_NoAdsInfo,
textColor: textColor
)
)
)
)
availableItems[.uniqueReactions] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.uniqueReactions,
component: AnyComponent(
PageComponent(
content: AnyComponent(
ReactionsCarouselComponent(
context: component.context,
theme: environment.theme,
reactions: reactions
)
),
title: isStandalone ? strings.Premium_ReactionsStandalone : strings.Premium_Reactions,
text: isStandalone ? strings.Premium_ReactionsStandaloneInfo : strings.Premium_ReactionsInfo,
textColor: textColor
)
)
)
)
availableItems[.premiumStickers] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.premiumStickers,
component: AnyComponent(
PageComponent(
content: AnyComponent(
StickersCarouselComponent(
context: component.context,
stickers: stickers
)
),
title: strings.Premium_Stickers,
text: strings.Premium_StickersInfo,
textColor: textColor
)
)
)
)
availableItems[.advancedChatManagement] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.advancedChatManagement,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "fastdownload"
)),
title: strings.Premium_ChatManagement,
text: strings.Premium_ChatManagementInfo,
textColor: textColor
)
)
)
)
availableItems[.profileBadge] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.profileBadge,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "badge"
)),
title: strings.Premium_Badge,
text: strings.Premium_BadgeInfo,
textColor: textColor
)
)
)
)
availableItems[.animatedUserpics] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.animatedUserpics,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoName: "badge"
)),
title: strings.Premium_Avatar,
text: strings.Premium_AvatarInfo,
textColor: textColor
)
)
)
)
var items: [DemoPagerComponent.Item] = component.order.compactMap { availableItems[$0] }
let index: Int
switch component.source {
case .intro:
@ -881,12 +888,14 @@ private final class DemoSheetComponent: CombinedComponent {
let context: AccountContext
let subject: PremiumDemoScreen.Subject
let source: PremiumDemoScreen.Source
let order: [PremiumPerk]?
let action: () -> Void
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, action: @escaping () -> Void) {
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, action: @escaping () -> Void) {
self.context = context
self.subject = subject
self.source = source
self.order = order
self.action = action
}
@ -900,6 +909,9 @@ private final class DemoSheetComponent: CombinedComponent {
if lhs.source != rhs.source {
return false
}
if lhs.order != rhs.order {
return false
}
return true
}
@ -919,6 +931,7 @@ private final class DemoSheetComponent: CombinedComponent {
context: context.component.context,
subject: context.component.subject,
source: context.component.source,
order: context.component.order,
action: context.component.action,
dismiss: {
animateOut.invoke(Action { _ in
@ -989,8 +1002,12 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
return self._ready
}
public init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, action: @escaping () -> Void) {
super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, action: action), navigationBarAppearance: .none)
public convenience init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, action: @escaping () -> Void) {
self.init(context: context, subject: subject, source: source, order: nil, action: action)
}
init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, order: [PremiumPerk]?, action: @escaping () -> Void) {
super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, order: order, action: action), navigationBarAppearance: .none)
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)

View file

@ -34,6 +34,7 @@ public enum PremiumSource: Equatable {
case folders
case chatsPerFolder
case accounts
case about
case deeplink(String?)
case profile(PeerId)
@ -65,8 +66,10 @@ public enum PremiumSource: Equatable {
return "double_limits__dialog_filters_chats"
case .accounts:
return "double_limits__accounts"
case .profile:
return "profile"
case .about:
return "double_limits__about"
case let .profile(id):
return "profile__\(id.id._internalGetInt64Value())"
case let .deeplink(reference):
if let reference = reference {
return "deeplink_\(reference)"
@ -77,7 +80,7 @@ public enum PremiumSource: Equatable {
}
}
private enum PremiumPerk: CaseIterable {
enum PremiumPerk: CaseIterable {
case doubleLimits
case moreUpload
case fasterDownload
@ -576,16 +579,16 @@ private final class ScrollComponent<ChildEnvironment: Equatable>: Component {
}
}
final class PerkComponent: CombinedComponent {
public let iconName: String
public let iconBackgroundColors: [UIColor]
public let title: String
public let titleColor: UIColor
public let subtitle: String
public let subtitleColor: UIColor
public let arrowColor: UIColor
private final class PerkComponent: CombinedComponent {
let iconName: String
let iconBackgroundColors: [UIColor]
let title: String
let titleColor: UIColor
let subtitle: String
let subtitleColor: UIColor
let arrowColor: UIColor
public init(
init(
iconName: String,
iconBackgroundColors: [UIColor],
title: String,
@ -603,7 +606,7 @@ final class PerkComponent: CombinedComponent {
self.arrowColor = arrowColor
}
public static func ==(lhs: PerkComponent, rhs: PerkComponent) -> Bool {
static func ==(lhs: PerkComponent, rhs: PerkComponent) -> Bool {
if lhs.iconName != rhs.iconName {
return false
}
@ -962,8 +965,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
var demoSubject: PremiumDemoScreen.Subject
switch perk {
case .doubleLimits:
// let controller = PremimLimitsListScreen(context: accountContext)
// present(controller)
let controller = PremimLimitsListScreen(context: accountContext, buttonText: isPremium ? strings.Common_OK : strings.Premium_SubscribeFor(state?.price ?? "").string, isPremium: isPremium)
present(controller)
return
case .moreUpload:
demoSubject = .moreUpload
@ -990,6 +993,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
context: accountContext,
subject: demoSubject,
source: .intro(state?.price),
order: state?.configuration.perks,
action: {
dismissImpl?()
if !isPremium {

View file

@ -38,7 +38,7 @@ func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor
}
private class PremiumLimitAnimationComponent: Component {
private let iconName: String
private let iconName: String?
private let inactiveColor: UIColor
private let activeColors: [UIColor]
private let textColor: UIColor
@ -46,7 +46,7 @@ private class PremiumLimitAnimationComponent: Component {
private let badgePosition: CGFloat
init(
iconName: String,
iconName: String?,
inactiveColor: UIColor,
activeColors: [UIColor],
textColor: UIColor,
@ -202,7 +202,9 @@ private class PremiumLimitAnimationComponent: Component {
rotateAnimation.timingFunction = CAMediaTimingFunction(name: .easeOut)
Queue.mainQueue().after(0.5, {
self.hapticFeedback.impact(.light)
if !self.badgeView.isHidden {
self.hapticFeedback.impact(.light)
}
})
let returnAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
@ -231,9 +233,10 @@ private class PremiumLimitAnimationComponent: Component {
self.inactiveBackground.backgroundColor = component.inactiveColor.cgColor
self.activeBackground.backgroundColor = component.activeColors.last?.cgColor
self.badgeIcon.image = UIImage(bundleImageName: component.iconName)?.withRenderingMode(.alwaysTemplate)
self.badgeIcon.image = component.iconName.flatMap { UIImage(bundleImageName: $0)?.withRenderingMode(.alwaysTemplate) }
self.badgeIcon.tintColor = component.textColor
self.badgeView.isHidden = self.badgeIcon.image == nil
let lineHeight: CGFloat = 30.0
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - lineHeight), size: CGSize(width: availableSize.width, height: lineHeight))
self.container.frame = containerFrame
@ -380,17 +383,17 @@ private class PremiumLimitAnimationComponent: Component {
}
public final class PremiumLimitDisplayComponent: CombinedComponent {
public let inactiveColor: UIColor
public let activeColors: [UIColor]
public let inactiveTitle: String
public let inactiveValue: String
public let inactiveTitleColor: UIColor
public let activeTitle: String
public let activeValue: String
public let activeTitleColor: UIColor
public let badgeIconName: String
public let badgeText: String?
public let badgePosition: CGFloat
let inactiveColor: UIColor
let activeColors: [UIColor]
let inactiveTitle: String
let inactiveValue: String
let inactiveTitleColor: UIColor
let activeTitle: String
let activeValue: String
let activeTitleColor: UIColor
let badgeIconName: String?
let badgeText: String?
let badgePosition: CGFloat
public init(
inactiveColor: UIColor,
@ -401,7 +404,7 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
activeTitle: String,
activeValue: String,
activeTitleColor: UIColor,
badgeIconName: String,
badgeIconName: String?,
badgeText: String?,
badgePosition: CGFloat
) {
@ -677,6 +680,7 @@ private final class LimitSheetContent: CombinedComponent {
)
var titleText = strings.Premium_LimitReached
var buttonIconName = "Premium/X2"
let iconName: String
let badgeText: String
let string: String
@ -734,6 +738,7 @@ private final class LimitSheetContent: CombinedComponent {
} else {
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
}
buttonIconName = "Premium/PlusOne"
}
var reachedMaximumLimit = badgePosition >= 1.0
if case .folders = subject, !state.isPremium {
@ -819,7 +824,7 @@ private final class LimitSheetContent: CombinedComponent {
height: 50.0,
cornerRadius: 10.0,
gloss: !reachedMaximumLimit,
iconName: !reachedMaximumLimit ? "Premium/X2" : nil,
iconName: !reachedMaximumLimit ? buttonIconName : nil,
iconPosition: .right,
action: { [weak component] in
guard let component = component else {

File diff suppressed because it is too large Load diff

View file

@ -114,12 +114,19 @@ class PremiumStarComponent: Component {
self.timer?.invalidate()
}
private let hapticFeedback = HapticFeedback()
private var delayTapsTill: Double?
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
self.previousInteractionTimestamp = CACurrentMediaTime()
let currentTime = CACurrentMediaTime()
self.previousInteractionTimestamp = currentTime
if let delayTapsTill = self.delayTapsTill, currentTime < delayTapsTill {
return
}
var left: Bool?
var top: Bool?
@ -140,6 +147,8 @@ class PremiumStarComponent: Component {
if node.animationKeys.contains("tapRotate"), let left = left {
self.playAppearanceAnimation(velocity: nil, mirror: left, explode: true)
self.hapticFeedback.impact(.medium)
return
}
@ -175,6 +184,8 @@ class PremiumStarComponent: Component {
springAnimation.duration = springAnimation.settlingDuration * 0.8
node.addAnimation(springAnimation, forKey: "tapRotate")
}
self.hapticFeedback.tap()
}
private var previousYaw: Float = 0.0
@ -387,21 +398,33 @@ class PremiumStarComponent: Component {
return
}
self.previousInteractionTimestamp = CACurrentMediaTime()
let currentTime = CACurrentMediaTime()
self.previousInteractionTimestamp = currentTime
self.delayTapsTill = currentTime + 0.85
if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particles = scene.rootNode.childNode(withName: "particles", recursively: false) {
let particleSystem = particles.particleSystems?.first
particleSystem?.particleColorVariation = SCNVector4(0.15, 0.2, 0.35, 0.3)
particleSystem?.particleVelocity = 2.2
particleSystem?.birthRate = 4.5
particleSystem?.particleLifeSpan = 2.0
node.physicsField?.isActive = true
Queue.mainQueue().after(1.0) {
node.physicsField?.isActive = false
particles.particleSystems?.first?.birthRate = 1.2
particleSystem?.particleVelocity = 1.0
particleSystem?.particleLifeSpan = 4.0
if let particleSystem = particles.particleSystems?.first {
particleSystem.particleColorVariation = SCNVector4(0.15, 0.2, 0.35, 0.3)
particleSystem.speedFactor = 2.0
particleSystem.particleVelocity = 2.2
particleSystem.birthRate = 4.0
particleSystem.particleLifeSpan = 2.0
node.physicsField?.isActive = true
Queue.mainQueue().after(1.0) {
node.physicsField?.isActive = false
particles.particleSystems?.first?.birthRate = 1.2
particleSystem.particleVelocity = 1.0
particleSystem.particleLifeSpan = 4.0
particleSystem.speedFactor = 1.0
let animation = CABasicAnimation(keyPath: "speedFactor")
animation.fromValue = 2.0
animation.toValue = 1.0
animation.duration = 0.5
animation.timingFunction = CAMediaTimingFunction(name: .easeIn)
particleSystem.addAnimation(animation, forKey: "speedFactor")
}
}
}

View file

@ -433,6 +433,11 @@ private class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.previousInteractionTimestamp = CACurrentMediaTime()
}
if let animator = self.animator {
animator.invalidate()
self.animator = nil
}
guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition) = self.scrollStartPosition else {
return
}

View file

@ -410,6 +410,11 @@ private class StickersCarouselNode: ASDisplayNode, UIScrollViewDelegate {
self.previousInteractionTimestamp = CACurrentMediaTime()
}
if let animator = self.animator {
animator.invalidate()
self.animator = nil
}
guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition) = self.scrollStartPosition else {
return
}

View file

@ -135,6 +135,9 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode {
let expandedAnimationFrame = animationFrame
if isExpanded && !self.hasAppearAnimation {
self.staticAnimationNode.completed = { [weak self] _ in
self?.mainAnimationCompletion?()
}
self.staticAnimationNode.play(fromIndex: 0)
} else if isExpanded, self.animationNode == nil {
let animationNode = AnimatedStickerNode()

View file

@ -181,12 +181,19 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC
}
if let dimensitons = self.item.file.dimensions {
let textSpacing: CGFloat = 50.0
var topOffset: CGFloat = 0.0
var textSpacing: CGFloat = 50.0
if size.width == 292.0 {
topOffset = 60.0
textSpacing -= 10.0
}
let textSize = self.textNode.measure(CGSize(width: 100.0, height: 100.0))
let imageSize = dimensitons.cgSize.aspectFitted(boundingSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
var imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: textSize.height + textSpacing), size: imageSize)
var imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: textSize.height + textSpacing - topOffset), size: imageSize)
var centerOffset: CGFloat = 0.0
if self.item.file.isPremiumSticker {
let originalImageFrame = imageFrame
@ -263,14 +270,21 @@ final class PremiumStickerPackAccessoryNode: SparseNode, PeekControllerAccessory
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
let sideInset: CGFloat = 16.0
var bottomOffset: CGFloat = 0.0
if size.width == 320.0 {
bottomOffset = 30.0
}
let cancelSize = self.cancelButton.measure(size)
self.cancelButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - cancelSize.width) / 2.0), y: size.height - cancelSize.height - 49.0), size: cancelSize)
self.cancelButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - cancelSize.width) / 2.0), y: size.height - cancelSize.height - 49.0 + bottomOffset), size: cancelSize)
let buttonWidth = size.width - sideInset * 2.0
let buttonHeight = self.proceedButton.updateLayout(width: buttonWidth, transition: transition)
self.proceedButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - buttonWidth) / 2.0), y: size.height - cancelSize.height - 49.0 - buttonHeight - 23.0), size: CGSize(width: buttonWidth, height: buttonHeight))
self.proceedButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - buttonWidth) / 2.0), y: size.height - cancelSize.height - 49.0 - buttonHeight - 23.0 + bottomOffset), size: CGSize(width: buttonWidth, height: buttonHeight))
let textSize = self.textNode.updateLayout(CGSize(width: size.width - sideInset * 4.0, height: CGFloat.greatestFiniteMagnitude))
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - cancelSize.height - 48.0 - buttonHeight - 20.0 - textSize.height - 31.0), size: textSize)
let textSideInset = size.width == 320.0 ? sideInset : sideInset * 2.0
let textSize = self.textNode.updateLayout(CGSize(width: size.width - textSideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - cancelSize.height - 48.0 - buttonHeight - 20.0 - textSize.height - 31.0 + bottomOffset), size: textSize)
}
}

View file

@ -1150,6 +1150,7 @@ public class Account {
self.managedOperationsDisposable.add(managedConfigurationUpdates(accountManager: accountManager, postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedVoipConfigurationUpdates(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedAppConfigurationUpdates(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedPremiumPromoConfigurationUpdates(postbox: self.postbox, network: self.network).start())
self.managedOperationsDisposable.add(managedAutodownloadSettingsUpdates(accountManager: accountManager, network: self.network).start())
self.managedOperationsDisposable.add(managedTermsOfServiceUpdates(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
self.managedOperationsDisposable.add(managedAppUpdateInfo(network: self.network, stateManager: self.stateManager).start())

View file

@ -0,0 +1,67 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
func updatePremiumPromoConfigurationOnce(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
return network.request(Api.functions.help.getPremiumPromo())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.help.PremiumPromo?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
guard let result = result else {
return .complete()
}
return postbox.transaction { transaction -> Void in
updatePremiumPromoConfiguration(transaction: transaction, { configuration -> PremiumPromoConfiguration in
return PremiumPromoConfiguration(apiPremiumPromo: result)
})
}
}
}
func managedPremiumPromoConfigurationUpdates(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = Signal<Void, NoError> { subscriber in
return updatePremiumPromoConfigurationOnce(postbox: postbox, network: network).start(completed: {
subscriber.putCompletion()
})
}
return (poll |> then(.complete() |> suspendAwareDelay(10.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
private func currentPremiumPromoConfiguration(transaction: Transaction) -> PremiumPromoConfiguration {
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.premiumPromo)?.get(PremiumPromoConfiguration.self) {
return entry
} else {
return PremiumPromoConfiguration.defaultValue
}
}
private func updatePremiumPromoConfiguration(transaction: Transaction, _ f: (PremiumPromoConfiguration) -> PremiumPromoConfiguration) {
let current = currentPremiumPromoConfiguration(transaction: transaction)
let updated = f(current)
if updated != current {
transaction.setPreferencesEntry(key: PreferencesKeys.premiumPromo, value: PreferencesEntry(updated))
}
}
private extension PremiumPromoConfiguration {
init(apiPremiumPromo: Api.help.PremiumPromo) {
switch apiPremiumPromo {
case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles):
self.status = statusText
self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities)
var videos: [String: TelegramMediaFile] = [:]
for (key, document) in zip(videoSections, videoFiles) {
if let file = telegramMediaFileFromApiDocument(document) {
videos[key] = file
}
}
self.videos = videos
}
}
}

View file

@ -232,6 +232,7 @@ private enum PreferencesKeyValues: Int32 {
case chatListFiltersFeaturedState = 22
case secretChatSettings = 23
case reactionSettings = 24
case premiumPromo = 25
}
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
@ -360,6 +361,12 @@ public struct PreferencesKeys {
key.setInt32(0, value: PreferencesKeyValues.reactionSettings.rawValue)
return key
}()
public static let premiumPromo: ValueBoxKey = {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: PreferencesKeyValues.premiumPromo.rawValue)
return key
}()
}
private enum SharedDataKeyValues: Int32 {

View file

@ -0,0 +1,75 @@
import Foundation
import Postbox
public struct PremiumPromoConfiguration: Codable, Equatable {
enum CodingKeys: String, CodingKey {
case status
case statusEntities
case videos
}
private struct DictionaryPair: Codable {
var key: String
var value: TelegramMediaFile
init(_ key: String, value: TelegramMediaFile) {
self.key = key
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.key = try container.decode(String.self, forKey: "k")
self.value = try container.decode(TelegramMediaFile.self, forKey: "v")
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.key, forKey: "k")
try container.encode(self.value, forKey: "v")
}
}
public var status: String
public var statusEntities: [MessageTextEntity]
public var videos: [String: TelegramMediaFile]
public static var defaultValue: PremiumPromoConfiguration {
return PremiumPromoConfiguration(status: "", statusEntities: [], videos: [:])
}
init(status: String, statusEntities: [MessageTextEntity], videos: [String: TelegramMediaFile]) {
self.status = status
self.statusEntities = statusEntities
self.videos = videos
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.status = try container.decode(String.self, forKey: .status)
self.statusEntities = try container.decode([MessageTextEntity].self, forKey: .statusEntities)
var videos: [String: TelegramMediaFile] = [:]
let pairs = try container.decode([DictionaryPair].self, forKey: .videos)
for pair in pairs {
videos[pair.key] = pair.value
}
self.videos = videos
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.status, forKey: .status)
try container.encode(self.statusEntities, forKey: .statusEntities)
var pairs: [DictionaryPair] = []
for (key, file) in self.videos {
pairs.append(DictionaryPair(key, value: file))
}
try container.encode(pairs, forKey: .videos)
}
}

View file

@ -354,5 +354,26 @@ public extension TelegramEngine.EngineData.Item {
return localizationListState
}
}
public struct PremiumPromo: TelegramEngineDataItem, PostboxViewDataItem {
public typealias Result = PremiumPromoConfiguration
public init() {
}
var key: PostboxViewKey {
return .preferences(keys: Set([PreferencesKeys.premiumPromo]))
}
func extract(view: PostboxView) -> Result {
guard let view = view as? PreferencesView else {
preconditionFailure()
}
guard let premiumPromoConfiguration = view.values[PreferencesKeys.premiumPromo]?.get(PremiumPromoConfiguration.self) else {
return PremiumPromoConfiguration.defaultValue
}
return premiumPromoConfiguration
}
}
}
}

View file

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

View file

@ -0,0 +1,78 @@
%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 3.729980 3.500000 cm
0.000000 0.000000 0.000000 scn
8.269714 10.000000 m
10.478853 10.000000 12.269714 11.790861 12.269714 14.000000 c
12.269714 16.209139 10.478853 18.000000 8.269714 18.000000 c
6.060575 18.000000 4.269714 16.209139 4.269714 14.000000 c
4.269714 11.790861 6.060575 10.000000 8.269714 10.000000 c
h
16.238016 3.829396 m
15.145898 5.884616 12.897719 8.000000 8.269734 8.000000 c
3.641746 8.000000 1.393565 5.884617 0.301445 3.829397 c
-0.735194 1.878584 1.060591 0.000000 3.269730 0.000000 c
13.269731 0.000000 l
15.478869 0.000000 17.274656 1.878582 16.238016 3.829396 c
h
f*
n
Q
endstream
endobj
3 0 obj
664
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
0000000754 00000 n
0000000776 00000 n
0000000949 00000 n
0000001023 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1082
%%EOF

View file

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "badgedoc_24.pdf",
"idiom" : "universal"
}
],

View file

@ -0,0 +1,119 @@
%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 4.000000 3.000000 cm
0.000000 0.000000 0.000000 scn
5.330000 18.000000 m
5.807298 18.000000 6.123703 17.999550 6.367812 17.982895 c
6.604466 17.966749 6.711959 17.938187 6.777740 17.910938 c
7.064423 17.792191 7.292191 17.564423 7.410939 17.277740 c
7.438186 17.211960 7.466748 17.104465 7.482895 16.867813 c
7.499550 16.623703 7.500000 16.307299 7.500000 15.830000 c
7.500000 13.530003 l
7.500000 13.496872 l
7.499987 12.965037 7.499976 12.516354 7.530005 12.148819 c
7.561447 11.763987 7.629904 11.395631 7.808452 11.045210 c
8.079773 10.512712 8.512709 10.079777 9.045207 9.808455 c
9.395627 9.629908 9.763983 9.561451 10.148815 9.530008 c
10.516351 9.499980 10.965034 9.499990 11.496870 9.500004 c
11.530001 9.500004 l
13.829997 9.500004 l
14.307296 9.500004 14.623703 9.499555 14.867812 9.482899 c
15.104466 9.466752 15.211960 9.438190 15.277740 9.410942 c
15.564423 9.292194 15.792191 9.064426 15.910938 8.777744 c
15.938186 8.711964 15.966748 8.604470 15.982895 8.367816 c
15.999551 8.123706 16.000000 7.807300 16.000000 7.330000 c
16.000000 4.800000 l
16.000000 3.119843 16.000000 2.279763 15.673019 1.638029 c
15.385400 1.073542 14.926457 0.614601 14.361972 0.326981 c
13.720237 0.000000 12.880157 0.000000 11.200000 0.000000 c
4.800000 0.000000 l
3.119843 0.000000 2.279764 0.000000 1.638029 0.326981 c
1.073542 0.614601 0.614601 1.073542 0.326980 1.638029 c
0.000000 2.279763 0.000000 3.119843 0.000000 4.800000 c
0.000000 13.200001 l
0.000000 14.880157 0.000000 15.720236 0.326980 16.361971 c
0.614601 16.926458 1.073542 17.385399 1.638029 17.673019 c
2.279764 18.000000 3.119843 18.000000 4.800000 18.000000 c
5.330000 18.000000 l
h
15.621112 11.042418 m
15.558301 11.176268 15.488032 11.306717 15.410561 11.433140 c
15.163194 11.836806 14.817290 12.182710 14.125484 12.874516 c
10.874516 16.125484 l
10.182710 16.817291 9.836806 17.163193 9.433140 17.410561 c
9.306716 17.488033 9.176266 17.558304 9.042415 17.621115 c
9.097363 17.412268 9.124138 17.199299 9.139045 16.980810 c
9.160017 16.673437 9.160009 16.300030 9.160000 15.857494 c
9.160000 15.830000 l
9.160000 13.530003 l
9.160000 12.956255 9.160645 12.575861 9.184492 12.283996 c
9.207546 12.001820 9.248083 11.876238 9.287522 11.798835 c
9.399694 11.578686 9.578682 11.399698 9.798831 11.287526 c
9.876234 11.248087 10.001816 11.207550 10.283993 11.184496 c
10.575858 11.160649 10.956251 11.160004 11.530001 11.160004 c
13.829997 11.160004 l
13.857491 11.160004 l
14.300028 11.160013 14.673436 11.160021 14.980811 11.139048 c
15.199297 11.124141 15.412265 11.097366 15.621112 11.042418 c
h
f*
n
Q
endstream
endobj
3 0 obj
2637
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.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
0000002727 00000 n
0000002750 00000 n
0000002923 00000 n
0000002997 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
3056
%%EOF

View file

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

View file

@ -0,0 +1,115 @@
%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.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 11.600000 m
0.000000 13.840210 0.000000 14.960315 0.435974 15.815962 c
0.819467 16.568611 1.431390 17.180532 2.184038 17.564026 c
3.039685 18.000000 4.159790 18.000000 6.400001 18.000000 c
17.600000 18.000000 l
19.840210 18.000000 20.960316 18.000000 21.815962 17.564026 c
22.568611 17.180532 23.180532 16.568611 23.564026 15.815962 c
24.000000 14.960315 24.000000 13.840210 24.000000 11.600000 c
24.000000 6.400000 l
24.000000 4.159790 24.000000 3.039685 23.564026 2.184038 c
23.180532 1.431389 22.568611 0.819468 21.815962 0.435974 c
20.960316 0.000000 19.840210 0.000000 17.600000 0.000000 c
6.400000 0.000000 l
4.159790 0.000000 3.039685 0.000000 2.184038 0.435974 c
1.431390 0.819468 0.819467 1.431389 0.435974 2.184038 c
0.000000 3.039685 0.000000 4.159790 0.000000 6.400001 c
0.000000 11.600000 l
h
16.820312 3.856445 m
17.428711 3.856445 17.838867 4.266602 17.838867 4.888672 c
17.838867 12.859375 l
17.838867 13.570312 17.401367 14.014648 16.669922 14.014648 c
16.246094 14.014648 15.917969 13.953125 15.439453 13.625000 c
13.484375 12.271484 l
13.163086 12.045898 13.040039 11.813477 13.040039 11.499023 c
13.040039 11.061523 13.347656 10.760742 13.764648 10.760742 c
13.976562 10.760742 14.120117 10.808594 14.311523 10.945312 c
15.753906 11.950195 l
15.794922 11.950195 l
15.794922 4.888672 l
15.794922 4.266602 16.211914 3.856445 16.820312 3.856445 c
h
9.388672 6.127930 m
9.388672 5.540039 9.053711 5.177734 8.500000 5.177734 c
7.939453 5.177734 7.604492 5.540039 7.604492 6.127930 c
7.604492 8.083008 l
5.738281 8.083008 l
5.177734 8.083008 4.822266 8.411133 4.822266 8.958008 c
4.822266 9.498047 5.177734 9.826172 5.738281 9.826172 c
7.604492 9.826172 l
7.604492 11.794922 l
7.604492 12.375977 7.946289 12.745117 8.500000 12.745117 c
9.053711 12.745117 9.388672 12.375977 9.388672 11.794922 c
9.388672 9.826172 l
11.248047 9.826172 l
11.815430 9.826172 12.170898 9.498047 12.170898 8.958008 c
12.170898 8.411133 11.808594 8.083008 11.241211 8.083008 c
9.388672 8.083008 l
9.388672 6.127930 l
h
f*
n
Q
endstream
endobj
3 0 obj
2145
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 18.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
0000002235 00000 n
0000002258 00000 n
0000002431 00000 n
0000002505 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2564
%%EOF

View file

@ -956,8 +956,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}, action: { _, f in
let _ = (saveToCameraRoll(context: context, postbox: context.account.postbox, mediaReference: mediaReference)
|> deliverOnMainQueue).start(completed: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: isVideo ? presentationData.strings.Gallery_VideoSaved : presentationData.strings.Gallery_ImageSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }), nil)
Queue.mainQueue().after(0.2) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: isVideo ? presentationData.strings.Gallery_VideoSaved : presentationData.strings.Gallery_ImageSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }), nil)
}
})
f(.default)
})))
@ -1218,24 +1220,26 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let _ = (toggleGifSaved(account: context.account, fileReference: .message(message: MessageReference(message), media: file), saved: true)
|> deliverOnMainQueue).start(next: { result in
switch result {
case .generic:
controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil)
case let .limitExceeded(limit, premiumLimit):
let text: String
if limit == premiumLimit {
text = presentationData.strings.Premium_MaxSavedGifsFinalText
} else {
text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
}
controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in
if case .info = action {
let controller = PremiumIntroScreen(context: context, source: .savedGifs)
controllerInteraction.navigationController()?.pushViewController(controller)
return true
Queue.mainQueue().after(0.2) {
switch result {
case .generic:
controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
case let .limitExceeded(limit, premiumLimit):
let text: String
if limit == premiumLimit {
text = presentationData.strings.Premium_MaxSavedGifsFinalText
} else {
text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
}
return false
}), nil)
controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .info = action {
let controller = PremiumIntroScreen(context: context, source: .savedGifs)
controllerInteraction.navigationController()?.pushViewController(controller)
return true
}
return false
}), nil)
}
}
})

View file

@ -1469,7 +1469,7 @@ final class ChatMediaInputNode: ChatInputNode {
|> deliverOnMainQueue).start(next: { result in
switch result {
case .generic:
controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil)
controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
case let .limitExceeded(limit, premiumLimit):
let text: String
if limit == premiumLimit {
@ -1477,7 +1477,7 @@ final class ChatMediaInputNode: ChatInputNode {
} else {
text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
}
controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in
controllerInteraction.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .info = action {
let controller = PremiumIntroScreen(context: context, source: .savedGifs)
controllerInteraction.navigationController()?.pushViewController(controller)

View file

@ -218,7 +218,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
}
override func loadDisplayNode() {
self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, options: self.options, filters: self.filters)
self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, options: self.options, filters: self.filters, limit: self.limit, reachedSelectionLimit: self.params.reachedLimit)
switch self.contactsNode.contentNode {
case let .contacts(contactsNode):
self._listReady.set(contactsNode.ready)
@ -266,6 +266,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
}
}
case let .chats(chatsNode):
let reachedLimit = strongSelf.params.reachedLimit
chatsNode.updateState { initialState in
var state = initialState
if state.selectedPeerIds.contains(peer.id) {
@ -277,6 +278,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
}
updatedCount = state.selectedPeerIds.count
if let limit = limit, let count = updatedCount, count > limit {
reachedLimit?(Int32(count))
updatedCount = nil
removedTokenId = nil
addedToken = nil

View file

@ -66,7 +66,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
private var presentationData: PresentationData
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter]) {
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter], limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?) {
self.navigationBar = navigationBar
self.context = context
@ -89,6 +89,10 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode {
placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle
let chatListNode = ChatListNode(context: context, groupId: .root, previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
if let limit = limit {
chatListNode.selectionLimit = limit
chatListNode.reachedSelectionLimit = reachedSelectionLimit
}
chatListNode.accessibilityPageScrolledString = { row, count in
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
}

View file

@ -201,7 +201,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
|> deliverOnMainQueue).start(next: { result in
switch result {
case .generic:
interfaceInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil)
interfaceInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
case let .limitExceeded(limit, premiumLimit):
let text: String
if limit == premiumLimit {
@ -209,7 +209,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
} else {
text = presentationData.strings.Premium_MaxSavedGifsText("\(premiumLimit)").string
}
interfaceInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { action in
interfaceInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: presentationData.strings.Premium_MaxSavedGifsTitle("\(limit)").string, text: text), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .info = action {
let controller = PremiumIntroScreen(context: context, source: .savedGifs)
interfaceInteraction?.getNavigationController()?.pushViewController(controller)

View file

@ -1491,6 +1491,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .chatsPerFolder
case .accounts:
mappedSource = .accounts
case .about:
mappedSource = .about
case let .deeplink(reference):
mappedSource = .deeplink(reference)
case let .profile(peerId):

View file

@ -526,8 +526,8 @@ class StickerPaneTrendingListGridItemNode: GridItemNode {
self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme), for: [])
}
let leftInset: CGFloat = 12.0
let rightInset: CGFloat = 16.0
let leftInset: CGFloat = 9.0
let rightInset: CGFloat = 18.0 + UIScreenPixel
let topOffset: CGFloat = 9.0
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.StickerPacksSettings_FeaturedPacks.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))