mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '03a13c0305'
This commit is contained in:
commit
7b2ead9b23
41 changed files with 1408 additions and 765 deletions
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -746,6 +746,7 @@ public enum PremiumIntroSource {
|
|||
case folders
|
||||
case chatsPerFolder
|
||||
case accounts
|
||||
case about
|
||||
case deeplink(String?)
|
||||
case profile(PeerId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Premium/Account.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Account.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "badgeuser_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
78
submodules/TelegramUI/Images.xcassets/Premium/Account.imageset/badgeuser_24.pdf
vendored
Normal file
78
submodules/TelegramUI/Images.xcassets/Premium/Account.imageset/badgeuser_24.pdf
vendored
Normal 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
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "badgedoc_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
119
submodules/TelegramUI/Images.xcassets/Premium/File.imageset/badgedoc_24.pdf
vendored
Normal file
119
submodules/TelegramUI/Images.xcassets/Premium/File.imageset/badgedoc_24.pdf
vendored
Normal 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
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/PlusOne.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/PlusOne.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "addone.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
115
submodules/TelegramUI/Images.xcassets/Premium/PlusOne.imageset/addone.pdf
vendored
Normal file
115
submodules/TelegramUI/Images.xcassets/Premium/PlusOne.imageset/addone.pdf
vendored
Normal 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
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue